Migration wizard Refresh 11th Feb 2021 (#14257)

* Adding summary page,
Storing ongoing migrations,
localizing some string,
made changes to how dropdowns work
updated database backup page

* Moved classes into different files
Fixed a lot of typos
Fixed some UI margins
This commit is contained in:
Aasim Khan
2021-02-11 21:18:44 -08:00
committed by GitHub
parent 7739f25f7f
commit b5479d0246
16 changed files with 1121 additions and 583 deletions

View File

@@ -8,13 +8,14 @@ import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../models/strings';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
export class AccountsSelectionPage extends MigrationWizardPage {
private _azureAccountsDropdown!: azdata.DropDownComponent;
private _accountsMap: Map<string, azdata.Account> = new Map();
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.ACCOUNTS_SELECTION_PAGE_TITLE), migrationStateModel);
this.wizardPage.description = constants.ACCOUNTS_SELECTION_PAGE_DESCRIPTION;
}
protected async registerContent(view: azdata.ModelView): Promise<void> {
@@ -30,32 +31,36 @@ export class AccountsSelectionPage extends MigrationWizardPage {
private createAzureAccountsDropdown(view: azdata.ModelView): azdata.FormComponent {
this._azureAccountsDropdown = view.modelBuilder.dropDown().withValidation((c) => {
if ((<azdata.CategoryValue>c.value).displayName === constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR) {
this.wizard.message = {
text: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
level: azdata.window.MessageLevel.Error
};
return false;
}
return true;
}).component();
this._azureAccountsDropdown = view.modelBuilder.dropDown()
.withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
})
.withValidation((c) => {
if ((<azdata.CategoryValue>c.value).displayName === constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR) {
this.wizard.message = {
text: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
level: azdata.window.MessageLevel.Error
};
return false;
}
return true;
}).component();
this._azureAccountsDropdown.onValueChanged(async (value) => {
if (this._azureAccountsDropdown.value) {
const selectedAccount = (this._azureAccountsDropdown.value as azdata.CategoryValue).name;
this.migrationStateModel.azureAccount = this._accountsMap.get(selectedAccount)!;
if (value.selected) {
this.migrationStateModel.azureAccount = this.migrationStateModel.getAccount(value.index);
this.migrationStateModel.subscriptions = undefined!;
}
});
const addAccountButton = view.modelBuilder.button()
.withProperties<azdata.ButtonProperties>({
label: constants.ACCOUNT_ADD_BUTTON_LABEL,
width: '100px'
const linkAccountButton = view.modelBuilder.hyperlink()
.withProps({
label: constants.ACCOUNT_LINK_BUTTON_LABEL,
url: ''
})
.component();
addAccountButton.onDidClick(async (event) => {
linkAccountButton.onDidClick(async (event) => {
await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount');
await this.populateAzureAccountsDropdown();
});
@@ -64,7 +69,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
.withLayout({
flexFlow: 'column'
})
.withItems([this._azureAccountsDropdown, addAccountButton], { CSSStyles: { 'margin': '10px', } })
.withItems([this._azureAccountsDropdown, linkAccountButton], { CSSStyles: { 'margin': '2px', } })
.component();
return {
@@ -75,27 +80,12 @@ export class AccountsSelectionPage extends MigrationWizardPage {
private async populateAzureAccountsDropdown(): Promise<void> {
this._azureAccountsDropdown.loading = true;
let accounts = await azdata.accounts.getAllAccounts();
if (accounts.length === 0) {
this._azureAccountsDropdown.value = {
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
name: ''
};
return;
try {
this._azureAccountsDropdown.values = await this.migrationStateModel.getAccountValues();
this.migrationStateModel.azureAccount = this.migrationStateModel.getAccount(0);
} finally {
this._azureAccountsDropdown.loading = false;
}
this._azureAccountsDropdown.values = accounts.map((account): azdata.CategoryValue => {
let accountCategoryValue = {
displayName: account.displayInfo.displayName,
name: account.displayInfo.userId
};
this._accountsMap.set(accountCategoryValue.name, account);
return accountCategoryValue;
});
this.migrationStateModel.azureAccount = accounts[0];
this._azureAccountsDropdown.loading = false;
}
public async onPageEnter(): Promise<void> {

View File

@@ -5,12 +5,13 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getSubscriptions, Subscription, getMigrationControllerAuthKeys } from '../api/azure';
import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../api/azure';
import { MigrationStateModel } from '../models/stateMachine';
import * as constants from '../models/strings';
import * as os from 'os';
import { azureResource } from 'azureResource';
import { IntergrationRuntimePage } from './integrationRuntimePage';
import { IconPathHelper } from '../constants/iconPathHelper';
export class CreateMigrationControllerDialog {
@@ -22,17 +23,18 @@ export class CreateMigrationControllerDialog {
private _statusLoadingComponent!: azdata.LoadingComponent;
private migrationControllerAuthKeyTable!: azdata.DeclarativeTableComponent;
private _connectionStatus!: azdata.TextComponent;
private _connectionStatus!: azdata.InfoBoxComponent;
private _copyKey1Button!: azdata.ButtonComponent;
private _copyKey2Button!: azdata.ButtonComponent;
private _refreshKey1Button!: azdata.ButtonComponent;
private _refreshKey2Button!: azdata.ButtonComponent;
private _setupContainer!: azdata.FlexContainer;
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
private _subscriptionMap: Map<string, Subscription> = new Map();
constructor(private migrationStateModel: MigrationStateModel, private irPage: IntergrationRuntimePage) {
this._dialogObject = azdata.window.createModelViewDialog(constants.IR_PAGE_TITLE, 'MigrationControllerDialog', 'wide');
this._dialogObject = azdata.window.createModelViewDialog(constants.IR_PAGE_TITLE, 'MigrationControllerDialog', 'medium');
}
initialize() {
@@ -44,7 +46,7 @@ export class CreateMigrationControllerDialog {
this._view = view;
this._formSubmitButton = view.modelBuilder.button().withProps({
label: constants.SUBMIT,
label: constants.CREATE,
width: '80px'
}).component();
@@ -52,7 +54,7 @@ export class CreateMigrationControllerDialog {
this._statusLoadingComponent.loading = true;
this._formSubmitButton.enabled = false;
const subscription = this._subscriptionMap.get((this.migrationControllerSubscriptionDropdown.value as azdata.CategoryValue).name)!;
const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name;
const controllerName = this.migrationControllerNameText.value;
@@ -128,7 +130,7 @@ export class CreateMigrationControllerDialog {
this._dialogObject.okButton.enabled = false;
azdata.window.openDialog(this._dialogObject);
this._dialogObject.cancelButton.onClick((e) => {
this.migrationStateModel.migrationController = undefined;
this.migrationStateModel.migrationController = undefined!;
});
this._dialogObject.okButton.onClick((e) => {
this.irPage.populateMigrationController();
@@ -155,7 +157,8 @@ export class CreateMigrationControllerDialog {
}).component();
this.migrationControllerSubscriptionDropdown = this._view.modelBuilder.dropDown().withProps({
required: true
required: true,
enabled: false
}).component();
this.migrationControllerSubscriptionDropdown.onValueChanged((e) => {
@@ -224,37 +227,21 @@ export class CreateMigrationControllerDialog {
private async populateSubscriptions(): Promise<void> {
this.migrationControllerSubscriptionDropdown.loading = true;
this.migrationControllerResourceGroupDropdown.loading = true;
const subscriptions = await getSubscriptions(this.migrationStateModel.azureAccount);
let subscriptionDropdownValues: azdata.CategoryValue[] = [];
if (subscriptions && subscriptions.length > 0) {
subscriptions.forEach((subscription) => {
this._subscriptionMap.set(subscription.id, subscription);
subscriptionDropdownValues.push({
name: subscription.id,
displayName: subscription.name + ' - ' + subscription.id,
});
});
} else {
subscriptionDropdownValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
this.migrationControllerSubscriptionDropdown.values = subscriptionDropdownValues;
this.migrationControllerSubscriptionDropdown.values = [
{
displayName: this.migrationStateModel._targetSubscription.name,
name: ''
}
];
this.migrationControllerSubscriptionDropdown.loading = false;
this.populateResourceGroups();
}
private async populateResourceGroups(): Promise<void> {
this.migrationControllerResourceGroupDropdown.loading = true;
let subscription = this._subscriptionMap.get((this.migrationControllerSubscriptionDropdown.value as azdata.CategoryValue).name)!;
let subscription = this.migrationStateModel._targetSubscription;
const resourceGroups = await getResourceGroups(this.migrationStateModel.azureAccount, subscription);
let resourceGroupDropdownValues: azdata.CategoryValue[] = [];
if (resourceGroups && resourceGroups.length > 0) {
@@ -278,79 +265,66 @@ export class CreateMigrationControllerDialog {
private createControllerStatus(): azdata.FlexContainer {
const informationTextBox = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION
}).component();
const expressSetupTitle = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_OPTION1_HEADING,
const setupIRHeadingText = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING,
CSSStyles: {
'font-weight': 'bold'
}
}).component();
const expressSetupLink = this._view.modelBuilder.hyperlink().withProps({
label: constants.CONTROLLER_OPTION1_SETUP_LINK_TEXT,
url: ''
const setupIRdescription = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION,
}).component();
expressSetupLink.onDidClick((e) => {
vscode.window.showInformationMessage(constants.FEATURE_NOT_AVAILABLE);
});
const irSetupStep1Text = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_STEP1,
links: [
{
text: constants.CONTROLLER_STEP1_LINK,
url: 'https://www.microsoft.com/download/details.aspx?id=39717'
}
]
}).component();
const manualSetupTitle = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_OPTION2_HEADING,
const irSetupStep2Text = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_STEP2
}).component();
const irSetupStep3Text = this._view.modelBuilder.hyperlink().withProps({
label: constants.CONTROLLER_STEP3,
url: '',
CSSStyles: {
'font-weight': 'bold'
'margin-top': '10px',
'margin-bottom': '10px'
}
}).component();
const manualSetupButton = this._view.modelBuilder.hyperlink().withProps({
label: constants.CONTROLLER_OPTION2_STEP1,
url: 'https://www.microsoft.com/download/details.aspx?id=39717'
}).component();
const manualSetupSecondDescription = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_OPTION2_STEP2
}).component();
const connectionStatusTitle = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_CONNECTION_STATUS,
CSSStyles: {
'font-weight': 'bold'
}
}).component();
this._connectionStatus = this._view.modelBuilder.text().withProps({
value: ''
}).component();
const refreshButton = this._view.modelBuilder.button().withProps({
label: constants.REFRESH,
secondary: true
}).component();
const refreshLoadingIndicator = this._view.modelBuilder.loadingComponent().withProps({
loading: false
}).component();
refreshButton.onDidClick(async (e) => {
irSetupStep3Text.onDidClick(async (e) => {
refreshLoadingIndicator.loading = true;
this._connectionStatus.updateCssStyles({
'display': 'none'
});
try {
await this.refreshStatus();
} catch (e) {
console.log(e);
}
this._connectionStatus.updateCssStyles({
'display': 'inline'
});
refreshLoadingIndicator.loading = false;
});
const connectionStatusContainer = this._view.modelBuilder.flexContainer().withItems(
[
this._connectionStatus,
refreshButton,
refreshLoadingIndicator
]
).component();
this._connectionStatus = this._view.modelBuilder.infoBox().component();
this._connectionStatus.CSSStyles = {
'width': '350px'
};
const refreshLoadingIndicator = this._view.modelBuilder.loadingComponent().withProps({
loading: false
}).component();
this.migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({
@@ -358,54 +332,54 @@ export class CreateMigrationControllerDialog {
{
displayName: constants.NAME,
valueType: azdata.DeclarativeDataType.string,
width: '100px',
width: '50px',
isReadOnly: true,
rowCssStyles: {
'text-align': 'center'
}
},
{
displayName: constants.AUTH_KEY_COLUMN_HEADER,
valueType: azdata.DeclarativeDataType.string,
width: '300px',
width: '500px',
isReadOnly: true,
rowCssStyles: {
overflow: 'scroll'
}
},
{
displayName: '',
valueType: azdata.DeclarativeDataType.component,
width: '15px',
isReadOnly: true,
},
{
displayName: '',
valueType: azdata.DeclarativeDataType.component,
width: '100px',
width: '15px',
isReadOnly: true,
}
],
CSSStyles: {
'margin-top': '25px'
'margin-top': '5px'
}
}).component();
const refreshKeyButton = this._view.modelBuilder.button().withProps({
label: constants.REFRESH_KEYS,
CSSStyles: {
'margin-top': '10px'
},
width: '100px',
secondary: true
}).component();
refreshKeyButton.onDidClick(async (e) => {
this.refreshAuthTable();
});
this._setupContainer = this._view.modelBuilder.flexContainer().withItems(
[
informationTextBox,
expressSetupTitle,
expressSetupLink,
manualSetupTitle,
manualSetupButton,
manualSetupSecondDescription,
refreshKeyButton,
setupIRHeadingText,
setupIRdescription,
irSetupStep1Text,
irSetupStep2Text,
this.migrationControllerAuthKeyTable,
connectionStatusTitle,
connectionStatusContainer
]
irSetupStep3Text,
this._connectionStatus,
refreshLoadingIndicator
], {
CSSStyles: {
'margin-bottom': '5px'
}
}
).withLayout({
flexFlow: 'column'
}).component();
@@ -415,32 +389,42 @@ export class CreateMigrationControllerDialog {
}
private async refreshStatus(): Promise<void> {
const subscription = this._subscriptionMap.get((this.migrationControllerSubscriptionDropdown.value as azdata.CategoryValue).name)!;
const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name;
const controllerStatus = await getMigrationController(this.migrationStateModel.azureAccount, subscription, resourceGroup, region, this.migrationStateModel.migrationController!.name);
const controllerMonitoringStatus = await getMigrationControllerMonitoringData(this.migrationStateModel.azureAccount, subscription, resourceGroup, region, this.migrationStateModel.migrationController!.name);
this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => {
return node.nodeName;
});
if (controllerStatus) {
const state = controllerStatus.properties.integrationRuntimeState;
if (state === 'Online') {
this._connectionStatus.value = constants.CONTRLLER_READY(this.migrationStateModel.migrationController!.name, os.hostname());
this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_READY(this.migrationStateModel.migrationController!.name, this.migrationStateModel._nodeNames.join(', ')),
style: 'success'
});
this._dialogObject.okButton.enabled = true;
} else {
this._connectionStatus.value = constants.CONTRLLER_NOT_READY(this.migrationStateModel.migrationController!.name);
this._connectionStatus.text = constants.CONTROLLER_NOT_READY(this.migrationStateModel.migrationController!.name);
this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_NOT_READY(this.migrationStateModel.migrationController!.name),
style: 'warning'
});
this._dialogObject.okButton.enabled = false;
}
}
}
private async refreshAuthTable(): Promise<void> {
const subscription = this._subscriptionMap.get((this.migrationControllerSubscriptionDropdown.value as azdata.CategoryValue).name)!;
const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name;
const keys = await getMigrationControllerAuthKeys(this.migrationStateModel.azureAccount, subscription, resourceGroup, region, this.migrationStateModel.migrationController!.name);
this._copyKey1Button = this._view.modelBuilder.button().withProps({
label: constants.COPY_KEY,
secondary: true
iconPath: IconPathHelper.copy
}).component();
this._copyKey1Button.onDidClick((e) => {
@@ -449,8 +433,7 @@ export class CreateMigrationControllerDialog {
});
this._copyKey2Button = this._view.modelBuilder.button().withProps({
label: constants.COPY_KEY,
secondary: true
iconPath: IconPathHelper.copy
}).component();
this._copyKey2Button.onDidClick((e) => {
@@ -458,28 +441,50 @@ export class CreateMigrationControllerDialog {
vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP);
});
this._refreshKey1Button = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh
}).component();
this._refreshKey1Button.onDidClick((e) => {
this.refreshAuthTable();
});
this._refreshKey2Button = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh
}).component();
this._refreshKey2Button.onDidClick((e) => {
this.refreshAuthTable();
});
this.migrationControllerAuthKeyTable.updateProperties({
dataValues: [
[
{
value: constants.CONTROLELR_KEY1_LABEL
value: constants.CONTROLLER_KEY1_LABEL
},
{
value: keys.keyName1
},
{
value: this._copyKey1Button
},
{
value: this._refreshKey1Button
}
],
[
{
value: constants.CONTROLELR_KEY2_LABEL
value: constants.CONTROLLER_KEY2_LABEL
},
{
value: keys.keyName2
},
{
value: this._copyKey2Button
},
{
value: this._refreshKey2Button
}
]
]

View File

@@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { azureResource } from 'azureResource';
import { EOL } from 'os';
import { getAvailableStorageAccounts, getBlobContainers, getFileShares, getSubscriptions, StorageAccount, Subscription } from '../api/azure';
import { getStorageAccountAccessKeys } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { BlobContainer, FileShare, MigrationCutover, MigrationStateModel, NetworkContainerType, NetworkShare, StateChangeEvent } from '../models/stateMachine';
import { MigrationCutover, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../models/strings';
export class DatabaseBackupPage extends MigrationWizardPage {
@@ -30,14 +29,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private _fileShareStorageAccountDropdown!: azdata.DropDownComponent;
private _fileShareFileShareDropdown!: azdata.DropDownComponent;
private _networkShare = {} as NetworkShare;
private _fileShare = {} as FileShare;
private _blob = {} as BlobContainer;
private _subscriptionDropdownValues: azdata.CategoryValue[] = [];
private _subscriptionMap: Map<string, Subscription> = new Map();
private _storageAccountMap: Map<string, StorageAccount> = new Map();
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.DATABASE_BACKUP_PAGE_TITLE), migrationStateModel);
this.wizardPage.description = constants.DATABASE_BACKUP_PAGE_DESCRIPTION;
@@ -65,12 +56,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
title: '',
component: networkContainer
},
this.migrationCutoverContainer(view),
this.emailNotificationContainer(view),
this.migrationModeContainer(view),
]
);
await view.initializeModel(form.component());
this.toggleNetworkContainerFields(NetworkContainerType.NETWORK_SHARE, this._networkShare);
}
private createBackupLocationComponent(view: azdata.ModelView): azdata.FormComponent {
@@ -79,13 +68,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const networkShareButton = view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
checked: true
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL
}).component();
networkShareButton.onDidChangeCheckedState((e) => {
if (e) {
this.toggleNetworkContainerFields(NetworkContainerType.NETWORK_SHARE, this._networkShare);
this.toggleNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
}
});
@@ -97,7 +85,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
blobContainerButton.onDidChangeCheckedState((e) => {
if (e) {
this.toggleNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER, this._blob);
this.toggleNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
}
});
@@ -109,7 +97,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
fileShareButton.onDidChangeCheckedState((e) => {
if (e) {
this.toggleNetworkContainerFields(NetworkContainerType.FILE_SHARE, this._fileShare);
this.toggleNetworkContainerFields(NetworkContainerType.FILE_SHARE);
}
});
@@ -139,8 +127,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true,
}).component();
this._fileShareSubscriptionDropdown.onValueChanged(async (value) => {
if (this._fileShareSubscriptionDropdown.value) {
this._fileShare.subscriptionId = (this._fileShareSubscriptionDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index);
this.migrationStateModel._storageAccounts = undefined!;
await this.loadFileShareStorageDropdown();
}
});
@@ -155,8 +144,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._fileShareStorageAccountDropdown.onValueChanged(async (value) => {
if (this._fileShareStorageAccountDropdown.value) {
this._fileShare.storageAccountId = (this._fileShareStorageAccountDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
this.migrationStateModel._fileShares = undefined!;
await this.loadFileShareDropdown();
}
});
@@ -171,8 +161,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._fileShareFileShareDropdown.onValueChanged((value) => {
if (this._fileShareFileShareDropdown.value) {
this._fileShare.fileShareId = (this._fileShareFileShareDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.fileShare = this.migrationStateModel.getFileShare(value.index);
}
});
@@ -189,6 +179,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
]
).withLayout({
flexFlow: 'column'
}).withProps({
display: 'none'
}).component();
return flexContainer;
@@ -205,8 +197,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._blobContainerSubscriptionDropdown.onValueChanged(async (value) => {
if (this._blobContainerSubscriptionDropdown.value) {
this._blob.subscriptionId = (this._blobContainerSubscriptionDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index);
this.migrationStateModel._storageAccounts = undefined!;
await this.loadblobStorageDropdown();
}
});
@@ -221,8 +214,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (this._blobContainerStorageAccountDropdown.value) {
this._blob.storageAccountId = (this._blobContainerStorageAccountDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
this.migrationStateModel._blobContainers = undefined!;
await this.loadBlobContainerDropdown();
}
});
@@ -236,8 +230,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._blobContainerBlobDropdown.onValueChanged((value) => {
if (this._blobContainerBlobDropdown.value) {
this._blob.containerId = (this._blobContainerBlobDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(value.index);
}
});
@@ -253,6 +247,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
]
).withLayout({
flexFlow: 'column'
}).withProps({
display: 'none'
}).component();
return flexContainer;
@@ -278,7 +274,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.withValidation((component) => {
if (this.migrationStateModel.databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
if (component.value) {
if (!/^(\\)(\\[\w\.-_]+){2,}(\\?)$/.test(component.value)) {
if (!/(?<=\\\\)[^\\]*/.test(component.value)) {
return false;
}
}
@@ -286,7 +282,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return true;
}).component();
this._networkShareLocationText.onTextChanged((value) => {
this._networkShare.networkShareLocation = value;
this.migrationStateModel.databaseBackup.networkShareLocation = value;
});
const windowsUserAccountLabel = view.modelBuilder.text()
@@ -303,7 +299,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.withValidation((component) => {
if (this.migrationStateModel.databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
if (component.value) {
if (!/^[a-zA-Z][a-zA-Z0-9\-\.]{0,61}[a-zA-Z]\\\w[\w\.\- ]*$/.test(component.value)) {
if (!/(?<=\\).*$/.test(component.value)) {
return false;
}
}
@@ -311,7 +307,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return true;
}).component();
this._windowsUserAccountText.onTextChanged((value) => {
this._networkShare.windowsUser = value;
this.migrationStateModel.databaseBackup.windowsUser = value;
});
const passwordLabel = view.modelBuilder.text()
@@ -326,7 +322,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._passwordText.onTextChanged((value) => {
this._networkShare.password = value;
this.migrationStateModel.databaseBackup.password = value;
});
const azureAccountHelpText = view.modelBuilder.text()
@@ -344,8 +340,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._networkShareContainerSubscriptionDropdown.onValueChanged(async (value) => {
if (this._networkShareContainerSubscriptionDropdown.value) {
this._networkShare.storageSubscriptionId = (this._networkShareContainerSubscriptionDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index);
this.migrationStateModel._storageAccounts = undefined!;
await this.loadNetworkShareStorageDropdown();
}
});
@@ -360,8 +357,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
required: true
}).component();
this._networkShareContainerStorageAccountDropdown.onValueChanged((value) => {
if (this._networkShareContainerStorageAccountDropdown.value) {
this._networkShare.storageAccountId = (this._networkShareContainerStorageAccountDropdown.value as azdata.CategoryValue).name;
if (value.selected) {
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
}
});
@@ -382,72 +379,57 @@ export class DatabaseBackupPage extends MigrationWizardPage {
]
).withLayout({
flexFlow: 'column'
}).withProps({
display: 'none'
}).component();
return flexContainer;
}
private emailNotificationContainer(view: azdata.ModelView): azdata.FormComponent {
const emailCheckbox = view.modelBuilder.checkBox().withProps({
label: constants.DATABASE_BACKUP_EMAIL_NOTIFICATION_CHECKBOX_LABEL
}).component();
emailCheckbox.onChanged((value) => {
if (value !== undefined) {
this.migrationStateModel.databaseBackup.emailNotification = value;
}
});
return {
title: constants.DATABASE_BACKUP_EMAIL_NOTIFICATION_LABEL,
component: emailCheckbox
};
}
private migrationCutoverContainer(view: azdata.ModelView): azdata.FormComponent {
private migrationModeContainer(view: azdata.ModelView): azdata.FormComponent {
const description = view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_MIGRATION_CUTOVER_DESCRIPTION
value: constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION
}).component();
const buttonGroup = 'cutoverContainer';
const automaticButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_CUTOVER_AUTOMATIC_LABEL,
const onlineButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
name: buttonGroup,
checked: true
}).component();
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.AUTOMATIC;
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.ONLINE;
automaticButton.onDidChangeCheckedState((e) => {
onlineButton.onDidChangeCheckedState((e) => {
if (e) {
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.AUTOMATIC;
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.ONLINE;
}
});
const manualButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_CUTOVER_MANUAL_LABEL,
const offlineButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
name: buttonGroup
}).component();
manualButton.onDidChangeCheckedState((e) => {
offlineButton.onDidChangeCheckedState((e) => {
if (e) {
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.MANUAL;
this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.OFFLINE;
}
});
const flexContainer = view.modelBuilder.flexContainer().withItems(
[
description,
automaticButton,
manualButton
onlineButton,
offlineButton
]
).withLayout({
flexFlow: 'column'
}).component();
return {
title: constants.DATABASE_BACKUP_MIGRATION_CUTOVER_LABEL,
title: constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL,
component: flexContainer
};
}
@@ -506,13 +488,14 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
public async onPageLeave(): Promise<void> {
this.migrationStateModel.databaseBackup.storageKey = (await getStorageAccountAccessKeys(this.migrationStateModel.azureAccount, this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount)).keyName1;
console.log(this.migrationStateModel.databaseBackup);
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private toggleNetworkContainerFields(containerType: NetworkContainerType, networkContainer: NetworkShare | BlobContainer | FileShare): void {
this.migrationStateModel.databaseBackup.networkContainer = networkContainer;
private toggleNetworkContainerFields(containerType: NetworkContainerType): void {
this.migrationStateModel.databaseBackup.networkContainerType = containerType;
this._fileShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.FILE_SHARE) ? 'inline' : 'none' });
this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' });
@@ -526,169 +509,105 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._passwordText.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
});
this._networkShareLocationText.validate();
this._windowsUserAccountText.validate();
this._passwordText.validate();
this._networkShareContainerSubscriptionDropdown.validate();
this._networkShareContainerStorageAccountDropdown.validate();
this._blobContainerSubscriptionDropdown.validate();
this._blobContainerStorageAccountDropdown.validate();
this._blobContainerBlobDropdown.validate();
this._fileShareSubscriptionDropdown.validate();
this._fileShareStorageAccountDropdown.validate();
this._fileShareFileShareDropdown.validate();
}
private async getSubscriptionValues(): Promise<void> {
this._networkShareContainerSubscriptionDropdown.loading = true;
this._fileShareSubscriptionDropdown.loading = true;
this._blobContainerSubscriptionDropdown.loading = true;
let subscriptions: azureResource.AzureResourceSubscription[] = [];
try {
subscriptions = await getSubscriptions(this.migrationStateModel.azureAccount);
subscriptions.forEach((subscription) => {
this._subscriptionMap.set(subscription.id, subscription);
this._subscriptionDropdownValues.push({
name: subscription.id,
displayName: subscription.name + ' - ' + subscription.id,
});
});
if (!this._subscriptionDropdownValues) {
this._subscriptionDropdownValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
this._fileShareSubscriptionDropdown.values = this._subscriptionDropdownValues;
this._networkShareContainerSubscriptionDropdown.values = this._subscriptionDropdownValues;
this._blobContainerSubscriptionDropdown.values = this._subscriptionDropdownValues;
this._networkShare.storageSubscriptionId = this._subscriptionDropdownValues[0].name;
this._fileShare.subscriptionId = this._subscriptionDropdownValues[0].name;
this._blob.subscriptionId = this._subscriptionDropdownValues[0].name;
this._fileShareSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
this._networkShareContainerSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
this._blobContainerSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(0);
} catch (error) {
console.log(error);
this.setEmptyDropdownPlaceHolder(this._fileShareSubscriptionDropdown, constants.NO_SUBSCRIPTIONS_FOUND);
this.setEmptyDropdownPlaceHolder(this._networkShareContainerSubscriptionDropdown, constants.NO_SUBSCRIPTIONS_FOUND);
this.setEmptyDropdownPlaceHolder(this._blobContainerSubscriptionDropdown, constants.NO_SUBSCRIPTIONS_FOUND);
this.migrationStateModel._storageAccounts = undefined!;
} finally {
await this.loadNetworkShareStorageDropdown();
await this.loadFileShareStorageDropdown();
await this.loadblobStorageDropdown();
this._networkShareContainerSubscriptionDropdown.loading = false;
this._fileShareSubscriptionDropdown.loading = false;
this._blobContainerSubscriptionDropdown.loading = false;
}
this._networkShareContainerSubscriptionDropdown.loading = false;
this._fileShareSubscriptionDropdown.loading = false;
this._blobContainerSubscriptionDropdown.loading = false;
await this.loadNetworkShareStorageDropdown();
await this.loadFileShareStorageDropdown();
await this.loadblobStorageDropdown();
this._networkShareContainerSubscriptionDropdown.validate();
this._networkShareContainerStorageAccountDropdown.validate();
}
private async loadNetworkShareStorageDropdown(): Promise<void> {
this._networkShareContainerStorageAccountDropdown.loading = true;
const subscriptionId = (<azdata.CategoryValue>this._networkShareContainerSubscriptionDropdown.value).name;
if (!subscriptionId.length) {
this.setEmptyDropdownPlaceHolder(this._networkShareContainerStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
} else {
const storageAccounts = await this.loadStorageAccounts(this._networkShare.storageSubscriptionId);
if (storageAccounts && storageAccounts.length) {
this._networkShareContainerStorageAccountDropdown.values = storageAccounts.map(s => <azdata.CategoryValue>{ name: s.id, displayName: s.name });
this._networkShare.storageAccountId = storageAccounts[0].id;
}
else {
this.setEmptyDropdownPlaceHolder(this._networkShareContainerStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
}
try {
this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription);
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0);
} finally {
this._networkShareContainerStorageAccountDropdown.loading = false;
}
this._networkShareContainerStorageAccountDropdown.loading = false;
}
private async loadFileShareStorageDropdown(): Promise<void> {
this._fileShareStorageAccountDropdown.loading = true;
this._fileShareFileShareDropdown.loading = true;
try {
this._fileShareStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription);
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0);
} catch (error) {
this.migrationStateModel._fileShares = undefined!;
} finally {
await this.loadFileShareDropdown();
this._fileShareStorageAccountDropdown.loading = false;
this._fileShareFileShareDropdown.loading = false;
const subscriptionId = (<azdata.CategoryValue>this._fileShareSubscriptionDropdown.value).name;
if (!subscriptionId.length) {
this.setEmptyDropdownPlaceHolder(this._fileShareStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
} else {
const storageAccounts = await this.loadStorageAccounts(this._fileShare.subscriptionId);
if (storageAccounts && storageAccounts.length) {
this._fileShareStorageAccountDropdown.values = storageAccounts.map(s => <azdata.CategoryValue>{ name: s.id, displayName: s.name });
this._fileShare.storageAccountId = storageAccounts[0].id;
}
else {
this.setEmptyDropdownPlaceHolder(this._fileShareStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
this._fileShareStorageAccountDropdown.loading = false;
}
}
this._fileShareStorageAccountDropdown.loading = false;
await this.loadFileShareDropdown();
}
private async loadblobStorageDropdown(): Promise<void> {
this._blobContainerStorageAccountDropdown.loading = true;
this._blobContainerBlobDropdown.loading = true;
try {
this._blobContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription);
this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0);
} catch (error) {
this.migrationStateModel._blobContainers = undefined!;
} finally {
await this.loadBlobContainerDropdown();
this._blobContainerStorageAccountDropdown.loading = false;
this._blobContainerBlobDropdown.loading = true;
const subscriptionId = (<azdata.CategoryValue>this._blobContainerSubscriptionDropdown.value).name;
if (!subscriptionId.length) {
this.setEmptyDropdownPlaceHolder(this._blobContainerStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
} else {
const storageAccounts = await this.loadStorageAccounts(this._blob.subscriptionId);
if (storageAccounts.length) {
this._blobContainerStorageAccountDropdown.values = storageAccounts.map(s => <azdata.CategoryValue>{ name: s.id, displayName: s.name });
this._blob.storageAccountId = storageAccounts[0].id;
} else {
this.setEmptyDropdownPlaceHolder(this._blobContainerStorageAccountDropdown, constants.NO_STORAGE_ACCOUNT_FOUND);
}
}
this._blobContainerStorageAccountDropdown.loading = false;
await this.loadBlobContainerDropdown();
}
private async loadStorageAccounts(subscriptionId: string): Promise<StorageAccount[]> {
const storageAccounts = await getAvailableStorageAccounts(this.migrationStateModel.azureAccount, this._subscriptionMap.get(subscriptionId)!);
storageAccounts.forEach(s => {
this._storageAccountMap.set(s.id, s);
});
return storageAccounts;
}
private async loadFileShareDropdown(): Promise<void> {
this._fileShareFileShareDropdown.loading = true;
const storageAccountId = (<azdata.CategoryValue>this._fileShareStorageAccountDropdown.value).name;
if (!storageAccountId.length) {
this.setEmptyDropdownPlaceHolder(this._fileShareFileShareDropdown, constants.NO_FILESHARES_FOUND);
} else {
const fileShares = await getFileShares(this.migrationStateModel.azureAccount, this._subscriptionMap.get(this._fileShare.subscriptionId)!, this._storageAccountMap.get(storageAccountId)!);
if (fileShares && fileShares.length) {
this._fileShareFileShareDropdown.values = fileShares.map(f => <azdata.CategoryValue>{ name: f.id, displayName: f.name });
this._fileShare.fileShareId = fileShares[0].id!;
} else {
this.setEmptyDropdownPlaceHolder(this._fileShareFileShareDropdown, constants.NO_FILESHARES_FOUND);
}
try {
this._fileShareFileShareDropdown.values = await this.migrationStateModel.getFileShareValues(this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount);
this.migrationStateModel.databaseBackup.fileShare = this.migrationStateModel.getFileShare(0);
} catch (error) {
console.log(error);
} finally {
this._fileShareFileShareDropdown.loading = false;
}
this._fileShareFileShareDropdown.loading = false;
}
private async loadBlobContainerDropdown(): Promise<void> {
this._blobContainerBlobDropdown.loading = true;
const storageAccountId = (<azdata.CategoryValue>this._blobContainerStorageAccountDropdown.value).name;
if (!storageAccountId.length) {
this.setEmptyDropdownPlaceHolder(this._blobContainerBlobDropdown, constants.NO_BLOBCONTAINERS_FOUND);
} else {
const blobContainers = await getBlobContainers(this.migrationStateModel.azureAccount, this._subscriptionMap.get(this._blob.subscriptionId)!, this._storageAccountMap.get(storageAccountId)!);
if (blobContainers && blobContainers.length) {
this._blobContainerBlobDropdown.values = blobContainers.map(f => <azdata.CategoryValue>{ name: f.id, displayName: f.name });
this._blob.containerId = blobContainers[0].id!;
} else {
this.setEmptyDropdownPlaceHolder(this._blobContainerBlobDropdown, constants.NO_BLOBCONTAINERS_FOUND);
}
try {
this._blobContainerBlobDropdown.values = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount);
this.migrationStateModel.databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(0);
} catch (error) {
console.log(error);
} finally {
this._blobContainerBlobDropdown.loading = false;
}
this._blobContainerBlobDropdown.loading = false;
}
private setEmptyDropdownPlaceHolder(dropDown: azdata.DropDownComponent, placeholder: string): void {
dropDown.values = [{
displayName: placeholder,
name: ''
}];
}
}

View File

@@ -4,23 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import { CreateMigrationControllerDialog } from './createMigrationControllerDialog';
import * as constants from '../models/strings';
import * as os from 'os';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
export class IntergrationRuntimePage extends MigrationWizardPage {
private migrationControllerDropdown!: azdata.DropDownComponent;
private defaultSetupRadioButton!: azdata.RadioButtonComponent;
private customSetupRadioButton!: azdata.RadioButtonComponent;
private startSetupButton!: azdata.ButtonComponent;
private cancelSetupButton!: azdata.ButtonComponent;
private _connectionStatus!: azdata.TextComponent;
private createMigrationContainer!: azdata.FlexContainer;
private _connectionStatus!: azdata.InfoBoxComponent;
private _view!: azdata.ModelView;
private _form!: azdata.FormBuilder;
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.IR_PAGE_TITLE), migrationStateModel);
@@ -29,79 +25,19 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
const createNewController = view.modelBuilder.button().withProps({
label: constants.NEW,
width: '100px',
secondary: true
const createNewController = view.modelBuilder.hyperlink().withProps({
label: constants.CREATE_NEW,
url: ''
}).component();
createNewController.onDidClick((e) => {
this.createMigrationContainer.display = 'inline';
const dialog = new CreateMigrationControllerDialog(this.migrationStateModel, this);
dialog.initialize();
});
const setupButtonGroup = 'setupOptions';
this._connectionStatus = view.modelBuilder.infoBox().component();
this.defaultSetupRadioButton = view.modelBuilder.radioButton().withProps({
label: constants.DEFAULT_SETUP_BUTTON,
name: setupButtonGroup
}).component();
this.defaultSetupRadioButton.checked = true;
this.customSetupRadioButton = view.modelBuilder.radioButton().withProps({
label: constants.CUSTOM_SETUP_BUTTON,
name: setupButtonGroup
}).component();
this.startSetupButton = view.modelBuilder.button().withProps({
label: constants.CREATE,
width: '100px',
secondary: true
}).component();
this.startSetupButton.onDidClick((e) => {
if (this.defaultSetupRadioButton.checked) {
vscode.window.showInformationMessage(constants.FEATURE_NOT_AVAILABLE);
} else {
this.createMigrationContainer.display = 'none';
const dialog = new CreateMigrationControllerDialog(this.migrationStateModel, this);
dialog.initialize();
}
});
this.cancelSetupButton = view.modelBuilder.button().withProps({
label: constants.CANCEL,
width: '100px',
secondary: true
}).component();
this.cancelSetupButton.onDidClick((e) => {
this.createMigrationContainer.display = 'none';
});
const setupButtonsContainer = view.modelBuilder.flexContainer().withItems([
this.startSetupButton,
this.cancelSetupButton
],
{ CSSStyles: { 'margin': '10px', } }
).withLayout({
flexFlow: 'row'
}).component();
this.createMigrationContainer = view.modelBuilder.flexContainer().withItems(
[
this.defaultSetupRadioButton,
this.customSetupRadioButton,
setupButtonsContainer
]
).withLayout({
flexFlow: 'column'
}).component();
this._connectionStatus = view.modelBuilder.text().component();
this.createMigrationContainer.display = 'none';
const form = view.modelBuilder.formContainer()
this._form = view.modelBuilder.formContainer()
.withFormItems(
[
{
@@ -110,16 +46,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
{
component: createNewController
},
{
component: this.createMigrationContainer
},
{
component: this._connectionStatus
}
]
);
await view.initializeModel(form.component());
await view.initializeModel(this._form.component());
}
public async onPageEnter(): Promise<void> {
@@ -148,6 +81,9 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
public async onPageLeave(): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -164,16 +100,22 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
]
}).component();
const noteText = this._view.modelBuilder.text().withProps({
value: constants.IR_PAGE_NOTE
}).component();
const migrationControllerDropdownLabel = this._view.modelBuilder.text().withProps({
value: constants.SELECT_A_MIGRATION_CONTROLLER
}).component();
this.migrationControllerDropdown = this._view.modelBuilder.dropDown().withProps({
required: true,
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
descriptionText,
noteText,
migrationControllerDropdownLabel,
this.migrationControllerDropdown
]).withLayout({
@@ -183,16 +125,34 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
public async populateMigrationController(controllerStatus?: string): Promise<void> {
this.migrationControllerDropdown.loading = true;
let migrationContollerValues: azdata.CategoryValue[] = [];
// TODO: Replace with this code when APIs are deployed.
// try{
// this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetManagedInstance);
// this.migrationStateModel.migrationController = this.migrationStateModel.getMigrationController(0);
// } catch (e) {
// } finally {
// this.migrationControllerDropdown.loading = false;
// }
if (this.migrationStateModel.migrationController) {
this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_READY(this.migrationStateModel.migrationController!.name, this.migrationStateModel._nodeNames.join(', ')),
style: 'success'
});
this._form.addFormItem({
component: this._connectionStatus
});
migrationContollerValues = [
{
displayName: this.migrationStateModel.migrationController.name,
name: this.migrationStateModel.migrationController.name
name: ''
}
];
this._connectionStatus.value = constants.CONTRLLER_READY(this.migrationStateModel.migrationController!.name, os.hostname());
}
else {
migrationContollerValues = [
@@ -201,7 +161,9 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
name: ''
}
];
this._connectionStatus.value = '';
this._form.removeFormItem({
component: this._connectionStatus
});
}
this.migrationControllerDropdown.values = migrationContollerValues;
this.migrationControllerDropdown.loading = false;

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../models/strings';
export class SummaryPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
private _flexContainer!: azdata.FlexContainer;
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.SUMMARY_PAGE_TITLE), migrationStateModel);
}
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
this._flexContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
const form = view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this._flexContainer
}
]
);
await view.initializeModel(form.component());
}
public async onPageEnter(): Promise<void> {
this._flexContainer.addItems(
[
this.createHeadingTextComponent(constants.AZURE_ACCOUNT_LINKED),
this.createHeadingTextComponent(this.migrationStateModel.azureAccount.displayInfo.displayName),
this.createHeadingTextComponent(constants.MIGRATION_TARGET),
this.createInformationRow(constants.TYPE, constants.SUMMARY_MI_TYPE),
this.createInformationRow(constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
this.createInformationRow(constants.SUMMARY_MI_TYPE, this.migrationStateModel._targetManagedInstance.name),
this.createInformationRow(constants.SUMMARY_DATABASE_COUNT_LABEL, '1'),
this.createHeadingTextComponent(constants.DATABASE_BACKUP_PAGE_TITLE),
this.createNetworkContainerRows(),
this.createHeadingTextComponent(constants.IR_PAGE_TITLE),
this.createInformationRow(constants.IR_PAGE_TITLE, this.migrationStateModel.migrationController?.name!),
this.createInformationRow(constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')),
]
);
}
public async onPageLeave(): Promise<void> {
this._flexContainer.clearItems();
this.wizard.registerNavigationValidator(async (pageChangeInfo) => {
return true;
});
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private createInformationRow(label: string, value: string): azdata.FlexContainer {
return this._view.modelBuilder.flexContainer()
.withLayout(
{
flexFlow: 'row',
alignItems: 'center',
})
.withItems(
[
this.creaetLabelTextComponent(label),
this.createTextCompononent(value)
],
{
CSSStyles: { 'margin-right': '5px' }
})
.component();
}
private createHeadingTextComponent(value: string): azdata.TextComponent {
const component = this.createTextCompononent(value);
component.updateCssStyles({
'font-size': '13px',
'font-weight': 'bold'
});
return component;
}
private creaetLabelTextComponent(value: string): azdata.TextComponent {
const component = this.createTextCompononent(value);
component.updateCssStyles({
'color': '#595959',
'width': '250px'
});
return component;
}
private createTextCompononent(value: string): azdata.TextComponent {
return this._view.modelBuilder.text().withProps({
value: value
}).component();
}
private createNetworkContainerRows(): azdata.FlexContainer {
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
switch (this.migrationStateModel.databaseBackup.networkContainerType) {
case NetworkContainerType.NETWORK_SHARE:
flexContainer.addItems(
[
this.createInformationRow(constants.TYPE, constants.NETWORK_SHARE),
this.createInformationRow(constants.PATH, this.migrationStateModel.databaseBackup.networkShareLocation),
this.createInformationRow(constants.USER_ACCOUNT, this.migrationStateModel.databaseBackup.windowsUser),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.subscription.name),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name),
]
);
break;
case NetworkContainerType.FILE_SHARE:
flexContainer.addItems(
[
this.createInformationRow(constants.TYPE, constants.FILE_SHARE),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.subscription.name),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name),
this.createInformationRow(constants.FILE_SHARE, this.migrationStateModel.databaseBackup.fileShare.name),
]
);
break;
case NetworkContainerType.BLOB_CONTAINER:
flexContainer.addItems(
[
this.createInformationRow(constants.TYPE, constants.BLOB_CONTAINER),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.blobContainer.subscription.name),
this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name),
this.createInformationRow(constants.BLOB_CONTAINER, this.migrationStateModel.databaseBackup.blobContainer.name),
]
);
}
return flexContainer;
}
}

View File

@@ -4,19 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { azureResource } from 'azureResource';
import { getAvailableManagedInstanceProducts, getSubscriptions, SqlManagedInstance, Subscription } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../models/strings';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
export class TempTargetSelectionPage extends MigrationWizardPage {
private _managedInstanceSubscriptionDropdown!: azdata.DropDownComponent;
private _managedInstanceDropdown!: azdata.DropDownComponent;
private _subscriptionDropdownValues: azdata.CategoryValue[] = [];
private _subscriptionMap: Map<string, Subscription> = new Map();
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.TARGET_SELECTION_PAGE_TITLE), migrationStateModel);
@@ -24,17 +20,37 @@ export class TempTargetSelectionPage extends MigrationWizardPage {
protected async registerContent(view: azdata.ModelView): Promise<void> {
const managedInstanceSubscriptionDropdownLabel = view.modelBuilder.text().withProps({
value: constants.SUBSCRIPTION
}).component();
this._managedInstanceSubscriptionDropdown = view.modelBuilder.dropDown().component();
const managedInstanceSubscriptionDropdownLabel = view.modelBuilder.text()
.withProps({
value: constants.SUBSCRIPTION
}).component();
this._managedInstanceSubscriptionDropdown = view.modelBuilder.dropDown()
.withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._managedInstanceSubscriptionDropdown.onValueChanged((e) => {
this.populateManagedInstanceDropdown();
if (e.selected) {
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index);
this.migrationStateModel._targetManagedInstances = undefined!;
this.populateManagedInstanceDropdown();
}
});
const managedInstanceDropdownLabel = view.modelBuilder.text().withProps({
value: constants.MANAGED_INSTANCE
}).component();
this._managedInstanceDropdown = view.modelBuilder.dropDown().component();
this._managedInstanceDropdown = view.modelBuilder.dropDown()
.withProps({
width: WIZARD_INPUT_COMPONENT_WIDTH
}).component();
this._managedInstanceDropdown.onValueChanged((e) => {
if (e.selected) {
this.migrationStateModel.migrationControllers = undefined!;
this.migrationStateModel._targetManagedInstance = this.migrationStateModel.getManagedInstance(e.index);
}
});
const targetContainer = view.modelBuilder.flexContainer().withItems(
[
@@ -61,6 +77,8 @@ export class TempTargetSelectionPage extends MigrationWizardPage {
this.populateSubscriptionDropdown();
}
public async onPageLeave(): Promise<void> {
console.log(this.migrationStateModel._targetSubscription);
console.log(this.migrationStateModel._targetManagedInstance);
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
@@ -68,71 +86,26 @@ export class TempTargetSelectionPage extends MigrationWizardPage {
private async populateSubscriptionDropdown(): Promise<void> {
this._managedInstanceSubscriptionDropdown.loading = true;
this._managedInstanceDropdown.loading = true;
let subscriptions: azureResource.AzureResourceSubscription[] = [];
try {
subscriptions = await getSubscriptions(this.migrationStateModel.azureAccount);
subscriptions.forEach((subscription) => {
this._subscriptionMap.set(subscription.id, subscription);
this._subscriptionDropdownValues.push({
name: subscription.id,
displayName: subscription.name + ' - ' + subscription.id,
});
});
if (!this._subscriptionDropdownValues || this._subscriptionDropdownValues.length === 0) {
this._subscriptionDropdownValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
this._managedInstanceSubscriptionDropdown.values = this._subscriptionDropdownValues;
} catch (error) {
this.setEmptyDropdownPlaceHolder(this._managedInstanceSubscriptionDropdown, constants.NO_SUBSCRIPTIONS_FOUND);
this._managedInstanceSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(0);
} catch (e) {
this.migrationStateModel._targetManagedInstances = undefined!;
} finally {
this.populateManagedInstanceDropdown();
this._managedInstanceSubscriptionDropdown.loading = false;
this._managedInstanceDropdown.loading = false;
}
this.populateManagedInstanceDropdown();
this._managedInstanceSubscriptionDropdown.loading = false;
}
private async populateManagedInstanceDropdown(): Promise<void> {
this._managedInstanceDropdown.loading = true;
let mis: SqlManagedInstance[] = [];
let miValues: azdata.CategoryValue[] = [];
try {
const subscriptionId = (<azdata.CategoryValue>this._managedInstanceSubscriptionDropdown.value).name;
mis = await getAvailableManagedInstanceProducts(this.migrationStateModel.azureAccount, this._subscriptionMap.get(subscriptionId)!);
mis.forEach((mi) => {
miValues.push({
name: mi.name,
displayName: mi.name
});
});
if (!miValues || miValues.length === 0) {
miValues = [
{
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
name: ''
}
];
}
this._managedInstanceDropdown.values = miValues;
} catch (error) {
this.setEmptyDropdownPlaceHolder(this._managedInstanceDropdown, constants.NO_MANAGED_INSTANCE_FOUND);
this._managedInstanceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription);
this.migrationStateModel._targetManagedInstance = this.migrationStateModel.getManagedInstance(0);
} finally {
this._managedInstanceDropdown.loading = false;
}
this._managedInstanceDropdown.loading = false;
}
private setEmptyDropdownPlaceHolder(dropDown: azdata.DropDownComponent, placeholder: string): void {
dropDown.values = [{
displayName: placeholder,
name: ''
}];
}
}

View File

@@ -15,7 +15,9 @@ import { DatabaseBackupPage } from './databaseBackupPage';
import { AccountsSelectionPage } from './accountsSelectionPage';
import { IntergrationRuntimePage } from './integrationRuntimePage';
import { TempTargetSelectionPage } from './tempTargetSelectionPage';
import { SummaryPage } from './summaryPage';
export const WIZARD_INPUT_COMPONENT_WIDTH = '400px';
export class WizardController {
constructor(private readonly extensionContext: vscode.ExtensionContext) {
@@ -34,7 +36,6 @@ export class WizardController {
const wizard = azdata.window.createWizard(WIZARD_TITLE, 'wide');
wizard.generateScriptButton.enabled = false;
wizard.generateScriptButton.hidden = true;
// Disabling unused pages
const sourceConfigurationPage = new SourceConfigurationPage(wizard, stateModel);
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
// const subscriptionSelectionPage = new SubscriptionSelectionPage(wizard, stateModel);
@@ -42,6 +43,7 @@ export class WizardController {
const tempTargetSelectionPage = new TempTargetSelectionPage(wizard, stateModel);
const databaseBackupPage = new DatabaseBackupPage(wizard, stateModel);
const integrationRuntimePage = new IntergrationRuntimePage(wizard, stateModel);
const summaryPage = new SummaryPage(wizard, stateModel);
const pages: MigrationWizardPage[] = [
// subscriptionSelectionPage,
@@ -50,7 +52,8 @@ export class WizardController {
sourceConfigurationPage,
skuRecommendationPage,
databaseBackupPage,
integrationRuntimePage
integrationRuntimePage,
summaryPage
];
wizard.pages = pages.map(p => p.getwizardPage());
@@ -79,5 +82,9 @@ export class WizardController {
await Promise.all(wizardSetupPromises);
await pages[0].onPageEnter();
wizard.doneButton.onClick(async (e) => {
await stateModel.startMigration();
});
}
}