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

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 6.3V16H5V13H1V0H7.7L10.7 3H11.7L15 6.3ZM5 3H9.3L7.3 1H2V12H5V3ZM14 7H11V4H6V15H14V7ZM12 6H13.3L12 4.7V6Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1328 0.296875C10.9974 0.53125 11.7891 0.898438 12.5078 1.39844C13.2266 1.89323 13.8438 2.48177 14.3594 3.16406C14.8802 3.84115 15.2839 4.59375 15.5703 5.42188C15.8568 6.24479 16 7.10417 16 8C16 8.73438 15.9036 9.44271 15.7109 10.125C15.5234 10.8073 15.2552 11.4453 14.9062 12.0391C14.5625 12.6328 14.1458 13.1745 13.6562 13.6641C13.1719 14.1484 12.6328 14.5651 12.0391 14.9141C11.4453 15.2578 10.8073 15.526 10.125 15.7188C9.44271 15.9062 8.73438 16 8 16C7.26562 16 6.55729 15.9062 5.875 15.7188C5.19271 15.526 4.55469 15.2578 3.96094 14.9141C3.36719 14.5651 2.82552 14.1484 2.33594 13.6641C1.85156 13.1745 1.4349 12.6328 1.08594 12.0391C0.742188 11.4453 0.473958 10.8099 0.28125 10.1328C0.09375 9.45052 0 8.73958 0 8C0 7.27083 0.0963542 6.5625 0.289062 5.875C0.481771 5.1875 0.755208 4.54167 1.10938 3.9375C1.46875 3.32812 1.90365 2.77604 2.41406 2.28125C2.92448 1.78125 3.5 1.35417 4.14062 1H2V0H6V4H5V1.67969C4.39062 1.97135 3.83854 2.33854 3.34375 2.78125C2.85417 3.21875 2.4349 3.71354 2.08594 4.26562C1.73698 4.8125 1.46875 5.40365 1.28125 6.03906C1.09375 6.67448 1 7.32812 1 8C1 8.64062 1.08333 9.26042 1.25 9.85938C1.41667 10.4531 1.65104 11.0104 1.95312 11.5312C2.26042 12.0469 2.6276 12.5182 3.05469 12.9453C3.48177 13.3724 3.95312 13.7396 4.46875 14.0469C4.98958 14.349 5.54688 14.5833 6.14062 14.75C6.73438 14.9167 7.35417 15 8 15C8.64062 15 9.25781 14.9167 9.85156 14.75C10.4505 14.5833 11.0078 14.349 11.5234 14.0469C12.0443 13.7396 12.5182 13.3724 12.9453 12.9453C13.3724 12.5182 13.737 12.0469 14.0391 11.5312C14.3464 11.0104 14.5833 10.4531 14.75 9.85938C14.9167 9.26562 15 8.64583 15 8C15 7.21875 14.8724 6.46615 14.6172 5.74219C14.3672 5.01823 14.0156 4.35938 13.5625 3.76562C13.1094 3.17188 12.5677 2.65885 11.9375 2.22656C11.3125 1.78906 10.6224 1.46615 9.86719 1.25781L10.1328 0.296875Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -88,9 +88,19 @@ export async function getMigrationController(account: azdata.Account, subscripti
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) {
throw response.errors.toString();
throw new Error(response.errors.toString());
}
return response.response.data;
}
export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise<MigrationController[]> {
const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
return response.response.data;
}
@@ -103,7 +113,7 @@ export async function createMigrationController(account: azdata.Account, subscri
};
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host);
if (response.errors.length > 0) {
throw response.errors.toString();
throw new Error(response.errors.toString());
}
return response.response.data;
}
@@ -114,7 +124,7 @@ export async function getMigrationControllerAuthKeys(account: azdata.Account, su
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}/ListAuthKeys?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host);
if (response.errors.length > 0) {
throw response.errors.toString();
throw new Error(response.errors.toString());
}
return {
keyName1: response?.response?.data?.keyName1 ?? '',
@@ -122,6 +132,47 @@ export async function getMigrationControllerAuthKeys(account: azdata.Account, su
};
}
export async function getStorageAccountAccessKeys(account: azdata.Account, subscription: Subscription, storageAccount: StorageAccount): Promise<GetStorageAccountAccessKeysResult> {
const api = await getAzureCoreAPI();
const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/listKeys?api-version=2019-06-01`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
return {
keyName1: response?.response?.data?.keys[0].value ?? '',
keyName2: response?.response?.data?.keys[0].value ?? '',
};
}
export async function getMigrationControllerMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<GetMigrationControllerMonitoringData> {
const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}/monitoringData?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
console.log(response);
return response.response.data;
}
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, managedInstance: string, migrationControllerName: string, requestBody: StartDatabaseMigrationRequest): Promise<any> {
const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/managedInstances/${managedInstance}/providers/Microsoft.DataMigration/databaseMigrations/${migrationControllerName}?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
return {
errors: response.errors,
status: response.response.status,
databaseMigration: response.response.data
};
}
/**
* For now only east us euap is supported. Actual API calls will be added in the public release.
*/
@@ -175,3 +226,62 @@ export interface GetMigrationControllerAuthKeysResult {
keyName1: string,
keyName2: string
}
export interface GetStorageAccountAccessKeysResult {
keyName1: string,
keyName2: string
}
export interface GetMigrationControllerMonitoringData {
name: string,
nodes: MigrationControllerNode[];
}
export interface MigrationControllerNode {
availableMemoryInMB: number,
concurrentJobsLimit: number
concurrentJobsRunning: number,
cpuUtilization: number,
nodeName: string
receivedBytes: number
sentBytes: number
}
export interface StartDatabaseMigrationRequest {
location: string,
properties: {
SourceDatabaseName: string,
MigrationController: string,
BackupConfiguration: {
TargetLocation: {
StorageAccountResourceId: string,
AccountKey: string,
}
SourceLocation: {
FileShare: {
Path: string,
Username: string,
Password: string,
}
},
},
SourceSqlConnection: {
DataSource: string,
Username: string,
Password: string
},
Scope: string
}
}
export interface DatabaseMigration {
properties: {
name: string,
provisioningState: string,
sourceDatabaseName: string,
migrationOperationId: string,
},
id: string,
name: string,
type: string
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export interface IconPath {
dark: string;
light: string;
}
export class IconPathHelper {
private static context: vscode.ExtensionContext;
public static copy: IconPath;
public static refresh: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.context = context;
IconPathHelper.copy = {
light: IconPathHelper.context.asAbsolutePath('images/copy.svg'),
dark: IconPathHelper.context.asAbsolutePath('images/copy.svg')
};
IconPathHelper.refresh = {
light: context.asAbsolutePath('images/refresh.svg'),
dark: context.asAbsolutePath('images/refresh.svg')
};
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as loc from './models/strings';
import * as loc from '../models/strings';
export class NotebookPathHelper {
private static context: vscode.ExtensionContext;

View File

@@ -7,15 +7,16 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { WizardController } from './wizard/wizardController';
import { AssessmentResultsDialog } from './dialog/assessmentResults/assessmentResultsDialog';
import { MigrationNotebookInfo, NotebookPathHelper } from './contants';
import { promises as fs } from 'fs';
import * as loc from './models/strings';
import { MigrationNotebookInfo, NotebookPathHelper } from './constants/notebookPathHelper';
import { IconPathHelper } from './constants/iconPathHelper';
class SQLMigration {
constructor(private readonly context: vscode.ExtensionContext) {
NotebookPathHelper.setExtensionContext(context);
IconPathHelper.setExtensionContext(context);
}
async start(): Promise<void> {

View File

@@ -0,0 +1,65 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { azureResource } from 'azureResource';
import { DatabaseMigration, SqlManagedInstance } from '../api/azure';
import * as azdata from 'azdata';
export class MigrationLocalStorage {
private static context: vscode.ExtensionContext;
private static mementoToken: string = 'sqlmigration.databaseMigrations';
public static setExtensionContext(context: vscode.ExtensionContext): void {
MigrationLocalStorage.context = context;
}
public static getMigrations(connectionProfile: azdata.connection.ConnectionProfile): MigrationContext[] {
let dataBaseMigrations: MigrationContext[] = [];
try {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
dataBaseMigrations = migrationMementos.filter((memento) => {
return memento.connection.serverName === connectionProfile.serverName;
}).map((memento) => {
return memento;
});
} catch (e) {
console.log(e);
}
return dataBaseMigrations;
}
public static saveMigration(connection: azdata.connection.ConnectionProfile, migration: DatabaseMigration, targetMI: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription): void {
try {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
migrationMementos.push({
connection: connection,
migration: migration,
targetMI: targetMI,
subscription: subscription,
azureAccount: azureAccount
});
this.context.globalState.update(this.mementoToken, migrationMementos);
} catch (e) {
console.log(e);
}
}
public static clearMigrations() {
this.context.globalState.update(this.mementoToken, ([] as MigrationContext[]));
}
}
export interface MigrationContext {
connection: azdata.connection.ConnectionProfile,
migration: DatabaseMigration,
targetMI: SqlManagedInstance,
azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription
}

View File

@@ -4,10 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { azureResource } from 'azureResource';
import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import { MigrationController } from '../api/azure';
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getMigrationControllers, getSubscriptions, MigrationController, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount } from '../api/azure';
import { SKURecommendations } from './externalContract';
import * as constants from '../models/strings';
import { MigrationLocalStorage } from './migrationLocalStorage';
export enum State {
INIT,
@@ -28,8 +31,8 @@ export enum State {
}
export enum MigrationCutover {
MANUAL,
AUTOMATIC
ONLINE,
OFFLINE
}
export enum NetworkContainerType {
@@ -42,28 +45,20 @@ export interface NetworkShare {
networkShareLocation: string;
windowsUser: string;
password: string;
storageSubscriptionId: string;
storageAccountId: string;
}
export interface BlobContainer {
subscriptionId: string;
storageAccountId: string;
containerId: string;
}
export interface FileShare {
subscriptionId: string;
storageAccountId: string;
fileShareId: string;
resourceGroupId: string;
}
export interface DatabaseBackupModel {
emailNotification: boolean;
migrationCutover: MigrationCutover;
networkContainerType: NetworkContainerType;
networkContainer: NetworkShare | BlobContainer | FileShare;
networkShareLocation: string;
windowsUser: string;
password: string;
subscription: azureResource.AzureResourceSubscription;
storageAccount: StorageAccount;
storageKey: string;
azureSecurityToken: string;
fileShare: azureResource.FileShare;
blobContainer: azureResource.BlobContainer;
}
export interface Model {
readonly sourceConnectionId: string;
@@ -80,14 +75,31 @@ export interface StateChangeEvent {
}
export class MigrationStateModel implements Model, vscode.Disposable {
public azureAccounts!: azdata.Account[];
public azureAccount!: azdata.Account;
public subscriptions!: azureResource.AzureResourceSubscription[];
public _targetSubscription!: azureResource.AzureResourceSubscription;
public _targetManagedInstances!: SqlManagedInstance[];
public _targetManagedInstance!: SqlManagedInstance;
public databaseBackup!: DatabaseBackupModel;
public _storageAccounts!: StorageAccount[];
public _fileShares!: azureResource.FileShare[];
public _blobContainers!: azureResource.BlobContainer[];
public migrationController!: MigrationController;
public migrationControllers!: MigrationController[];
public _nodeNames!: string[];
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
private _currentState: State;
private _gatheringInformationError: string | undefined;
private _skuRecommendations: SKURecommendations | undefined;
private _assessmentResults: mssql.SqlMigrationAssessmentResultItem[] | undefined;
private _azureAccount!: azdata.Account;
private _databaseBackup!: DatabaseBackupModel;
private _migrationController!: MigrationController | undefined;
constructor(
private readonly _extensionContext: vscode.ExtensionContext,
@@ -98,22 +110,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this.databaseBackup = {} as DatabaseBackupModel;
}
public get azureAccount(): azdata.Account {
return this._azureAccount;
}
public set azureAccount(account: azdata.Account) {
this._azureAccount = account;
}
public get databaseBackup(): DatabaseBackupModel {
return this._databaseBackup;
}
public set databaseBackup(dbBackup: DatabaseBackupModel) {
this._databaseBackup = dbBackup;
}
public get sourceConnectionId(): string {
return this._sourceConnectionId;
}
@@ -158,14 +154,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return this._stateChangeEventEmitter.event;
}
public set migrationController(controller: MigrationController | undefined) {
this._migrationController = controller;
}
public get migrationController(): MigrationController | undefined {
return this._migrationController;
}
dispose() {
this._stateChangeEventEmitter.dispose();
}
@@ -173,4 +161,314 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public getExtensionPath(): string {
return this._extensionContext.extensionPath;
}
public async getAccountValues(): Promise<azdata.CategoryValue[]> {
let accountValues: azdata.CategoryValue[] = [];
try {
this.azureAccounts = await azdata.accounts.getAllAccounts();
if (this.azureAccounts.length === 0) {
accountValues = [{
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
name: ''
}];
}
accountValues = this.azureAccounts.map((account): azdata.CategoryValue => {
return {
displayName: account.displayInfo.displayName,
name: account.displayInfo.userId
};
});
} catch (e) {
console.log(e);
accountValues = [{
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
name: ''
}];
}
return accountValues;
}
public getAccount(index: number): azdata.Account {
return this.azureAccounts[index];
}
public async getSubscriptionsDropdownValues(): Promise<azdata.CategoryValue[]> {
let subscriptionsValues: azdata.CategoryValue[] = [];
try {
if (!this.subscriptions) {
this.subscriptions = await getSubscriptions(this.azureAccount);
}
this.subscriptions.forEach((subscription) => {
subscriptionsValues.push({
name: subscription.id,
displayName: `${subscription.name} - ${subscription.id}`
});
});
if (subscriptionsValues.length === 0) {
subscriptionsValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
subscriptionsValues = [
{
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
name: ''
}
];
}
return subscriptionsValues;
}
public getSubscription(index: number): azureResource.AzureResourceSubscription {
return this.subscriptions[index];
}
public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let managedInstanceValues: azdata.CategoryValue[] = [];
try {
if (!this._targetManagedInstances) {
this._targetManagedInstances = await getAvailableManagedInstanceProducts(this.azureAccount, subscription);
}
this._targetManagedInstances.forEach((managedInstance) => {
managedInstanceValues.push({
name: managedInstance.id,
displayName: `${managedInstance.name}`
});
});
if (managedInstanceValues.length === 0) {
managedInstanceValues = [
{
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
managedInstanceValues = [
{
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
name: ''
}
];
}
return managedInstanceValues;
}
public getManagedInstance(index: number): SqlManagedInstance {
return this._targetManagedInstances[index];
}
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let storageAccountValues: azdata.CategoryValue[] = [];
try {
if (!this._storageAccounts) {
this._storageAccounts = await getAvailableStorageAccounts(this.azureAccount, subscription);
}
this._storageAccounts.forEach((storageAccount) => {
storageAccountValues.push({
name: storageAccount.id,
displayName: `${storageAccount.name}`
});
});
if (storageAccountValues.length === 0) {
storageAccountValues = [
{
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
storageAccountValues = [
{
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
name: ''
}
];
}
return storageAccountValues;
}
public getStorageAccount(index: number): StorageAccount {
return this._storageAccounts[index];
}
public async getFileShareValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let fileShareValues: azdata.CategoryValue[] = [];
try {
if (!this._fileShares) {
this._fileShares = await getFileShares(this.azureAccount, subscription, storageAccount);
}
this._fileShares.forEach((fileShare) => {
fileShareValues.push({
name: fileShare.id,
displayName: `${fileShare.name}`
});
});
if (fileShareValues.length === 0) {
fileShareValues = [
{
displayName: constants.NO_FILESHARES_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
fileShareValues = [
{
displayName: constants.NO_FILESHARES_FOUND,
name: ''
}
];
}
return fileShareValues;
}
public getFileShare(index: number): azureResource.FileShare {
return this._fileShares[index];
}
public async getBlobContainerValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let blobContainerValues: azdata.CategoryValue[] = [];
try {
if (!this._blobContainers) {
this._blobContainers = await getBlobContainers(this.azureAccount, subscription, storageAccount);
}
this._blobContainers.forEach((blobContainer) => {
blobContainerValues.push({
name: blobContainer.id,
displayName: `${blobContainer.name}`
});
});
if (blobContainerValues.length === 0) {
blobContainerValues = [
{
displayName: constants.NO_BLOBCONTAINERS_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
blobContainerValues = [
{
displayName: constants.NO_BLOBCONTAINERS_FOUND,
name: ''
}
];
}
return blobContainerValues;
}
public getBlobContainer(index: number): azureResource.BlobContainer {
return this._blobContainers[index];
}
public async getMigrationControllerValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise<azdata.CategoryValue[]> {
let migrationControllerValues: azdata.CategoryValue[] = [];
try {
if (!this.migrationControllers) {
this.migrationControllers = await getMigrationControllers(this.azureAccount, subscription, managedInstance.resourceGroup!, managedInstance.location);
}
this.migrationControllers.forEach((migrationController) => {
migrationControllerValues.push({
name: migrationController.id,
displayName: `${migrationController.name}`
});
});
if (migrationControllerValues.length === 0) {
migrationControllerValues = [
{
displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR,
name: ''
}
];
}
} catch (e) {
console.log(e);
migrationControllerValues = [
{
displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR,
name: ''
}
];
}
return migrationControllerValues;
}
public getMigrationController(index: number): MigrationController {
return this.migrationControllers[index];
}
public async startMigration() {
const sqlConnections = await azdata.connection.getConnections();
const currentConnection = sqlConnections.find((value) => {
if (value.connectionId === this.sourceConnectionId) {
return true;
} else {
return false;
}
});
const connectionPassword = await azdata.connection.getCredentials(this.sourceConnectionId);
const requestBody: StartDatabaseMigrationRequest = {
location: this.migrationController?.properties.location!,
properties: {
SourceDatabaseName: currentConnection?.databaseName!,
MigrationController: this.migrationController?.id!,
BackupConfiguration: {
TargetLocation: {
StorageAccountResourceId: this.databaseBackup.storageAccount.id,
AccountKey: this.databaseBackup.storageKey,
},
SourceLocation: {
FileShare: {
Path: this.databaseBackup.networkShareLocation,
Username: this.databaseBackup.windowsUser,
Password: this.databaseBackup.password,
}
},
},
SourceSqlConnection: {
DataSource: currentConnection?.serverName!,
Username: currentConnection?.userName!,
Password: connectionPassword.password
},
Scope: this._targetManagedInstance.id
}
};
console.log(requestBody);
const response = await startDatabaseMigration(
this.azureAccount,
this._targetSubscription,
this._targetManagedInstance.resourceGroup!,
this.migrationController?.properties.location!,
this._targetManagedInstance.name,
this.migrationController?.name!,
requestBody
);
console.log(response);
if (!response.error) {
MigrationLocalStorage.saveMigration(currentConnection!, response, this._targetManagedInstance, this.azureAccount, this._targetSubscription);
}
vscode.window.showInformationMessage(constants.MIGRATION_STARTED);
}
}

View File

@@ -38,9 +38,10 @@ export const CONGRATULATIONS = localize('sql.migration.generic.congratulations',
// Accounts page
export const ACCOUNTS_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.account.title', "Select your Azure account");
export const ACCOUNTS_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.account.title', "Azure Account");
export const ACCOUNTS_SELECTION_PAGE_DESCRIPTION = localize('sql.migration.wizard.account.description', "Select an Azure account linked to Azure Data Studio or link one now.");
export const ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR = localize('sql.migration.wizard.account.noaccount.error', "There is no linked account. Please add an account.");
export const ACCOUNT_ADD_BUTTON_LABEL = localize('sql.migration.wizard.account.add.button.label', "Add account");
export const ACCOUNT_LINK_BUTTON_LABEL = localize('sql.migration.wizard.account.add.button.label', "Link account");
export function accountLinkedMessage(count: number): string {
return count === 1 ? localize('sql.migration.wizard.account.count.single.message', '{0} account linked', count) : localize('sql.migration.wizard.account.count.multiple.message', '{0} accounts linked', count);
}
@@ -70,10 +71,10 @@ export const DATABASE_BACKUP_FILE_SHARE_SUBSCRIPTION_LABEL = localize('sql.migra
export const DATABASE_BACKUP_FILE_SHARE_STORAGE_ACCOUNT_LABEL = localize('sql.migration.file.share.storage.account.label', "Select the storage account that contains the file share.");
export const DATABASE_BACKUP_FILE_SHARE_LABEL = localize('sql.migration.file.share.label', "Select the file share that contains the backup files.");
export const DATABASE_BACKUP_FILE_SHARE_PLACEHOLDER = localize('sql.migration.file.share.placeholder', "Select share");
export const DATABASE_BACKUP_MIGRATION_CUTOVER_LABEL = localize('sql.migration.database.migration.cutover.label', "Migration Cutover");
export const DATABASE_BACKUP_MIGRATION_CUTOVER_DESCRIPTION = localize('sql.migration.database.migration.cutover.description', "Select how you want to cutover when the migration is complete.");
export const DATABASE_BACKUP_MIGRATION_CUTOVER_AUTOMATIC_LABEL = localize('sql.migration.database.migration.cutover.automatic.label', "Automatically cutover when migration is complete");
export const DATABASE_BACKUP_MIGRATION_CUTOVER_MANUAL_LABEL = localize('sql.migration.database.migration.cutover.manual.label', "Manually cutover when migration is complete");
export const DATABASE_BACKUP_MIGRATION_MODE_LABEL = localize('sql.migration.database.migration.mode.label', "Migration mode");
export const DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION = localize('sql.migration.database.migration.mode.description', "Choose from the following migration modes to migrate to your Azure SQL target based on your downtime requirements.");
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL = localize('sql.migration.database.migration.mode.online.label', "Online migration: Application downtime is limited to cut over at the end of migration.");
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL = localize('sql.migration.database.migration.mode.offline.label', "Offline migration: Application downtime will start when the migration starts.");
export const DATABASE_BACKUP_EMAIL_NOTIFICATION_LABEL = localize('sql.migration.database.backup.email.notification.label', "Email notifications");
export const DATABASE_BACKUP_EMAIL_NOTIFICATION_CHECKBOX_LABEL = localize('sql.migration.database.backup.email.notification.checkbox.label', "Notify me when migration is complete");
export const NO_SUBSCRIPTIONS_FOUND = localize('sql.migration.no.subscription.found', "No subscription found");
@@ -81,7 +82,7 @@ export const NO_STORAGE_ACCOUNT_FOUND = localize('sql.migration.no.storageAccoun
export const NO_FILESHARES_FOUND = localize('sql.migration.no.fileShares.found', "No file shares found");
export const NO_BLOBCONTAINERS_FOUND = localize('sql.migration.no.blobContainers.found', "No blob containers found");
export const INVALID_SUBSCRIPTION_ERROR = localize('sql.migration.invalid.subscription.error', "Please select a valid subscription to proceed.");
export const INVALID_STORAGE_ACCOUNT_ERROR = localize('sql.migration.invalid.storageAccout.error', "Please select a valid storage account to proceed.");
export const INVALID_STORAGE_ACCOUNT_ERROR = localize('sql.migration.invalid.storageAccount.error', "Please select a valid storage account to proceed.");
export const INVALID_FILESHARE_ERROR = localize('sql.migration.invalid.fileShare.error', "Please select a valid file share to proceed.");
export const INVALID_BLOBCONTAINER_ERROR = localize('sql.migration.invalid.blobContainer.error', "Please select a valid blob container to proceed.");
export const INVALID_NETWORK_SHARE_LOCATION = localize('sql.migration.invalid.network.share.location', "Invalid network share location format. Example: {0}", '\\\\Servername.domainname.com\\Backupfolder');
@@ -91,33 +92,35 @@ export const INVALID_USER_ACCOUNT = localize('sql.migration.invalid.user.account
// integration runtime page
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Migration Controller");
export const IR_PAGE_DESCRIPTION = localize('sql.migration.ir.page.description', "A migration controller is an ARM (Azure Resource Manager) resource created in your Azure subscription and it is needed to coordinate and monitor data migration activities. If one already exists in your subscription, you can reuse it here. Alternatively you can create a new one by clicking New. {0}");
export const IR_PAGE_NOTE = localize('sql.migration.ir.page.note', "Note: Migration Controller will run in your Azure subscription in the chosen resource group and does not incur any cost for running it.");
export const SELECT_A_MIGRATION_CONTROLLER = localize('sql.migration.controller', "Select a migration controller");
export const DEFAULT_SETUP_BUTTON = localize('sql.migration.default.setup.button', "Setup with defaults: Add migration controller with one click express setup using default options.");
export const CUSTOM_SETUP_BUTTON = localize('sql.migration.custom.setup.button', "Custom setup: Add migration controller after customizing most options.");
export const MIGRATION_CONTROLLER_NOT_FOUND_ERROR = localize('sql.migration.ir.page.migration.controller.not.found', "No Migration Controllers found. Please create a new one");
export const CREATE_NEW = localize('sql.migration.create.new', "Create new");
// create migration controller dialog
export const CONTROLLER_DIALOG_DESCRIPTION = localize('sql.migration.controller.container.description', "A migration controller is an ARM (Azure Resource Manager) resource created in your Azure subscription and it is needed to coordinate and monitor data migration activities. {0}");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_LOADING_HELP = localize('sql.migration.controller.container.loading.help', "Loading Controller");
export const CONTROLLER_DIALOG_CREATE_CONTROLLER_FORM_HEADING = localize('sql.migration.controller.dialog.create.controller.form.heading', "Enter the information below to add a new migration controller.");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION = localize('sql.migration.contoller.container.description', "Migration Controller uses self-hosted Integration Runtime offered by Azure Data Factory for data movement and other migration activities. Follow the instructions below to setup self-hosted Integration Runtime.");
export const CONTROLLER_OPTION1_HEADING = localize('sql.migration.controller.setup.option1.heading', "Option 1: Express setup");
export const CONTROLLER_OPTION1_SETUP_LINK_TEXT = localize('sql.migration.controller.setup.option1.link.text', "Open the express setup for this computer");
export const CONTROLLER_OPTION2_HEADING = localize('sql.migration.controller.setup.option2.heading', "Option 2: Express setup");
export const CONTROLLER_OPTION2_STEP1 = localize('sql.migration.option2.step1', "Step 1: Download and install integration runtime");
export const CONTROLLER_OPTION2_STEP2 = localize('sql.migration.option2.step2', "Step 2: Use this key to register your integration runtime");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING = localize('sql.migration.controller.container.heading', "Setup Integration Runtime");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION = localize('sql.migration.controller.container.container.description', "Follow the instructions below to setup self-hosted Integration Runtime.");
export const CONTROLLER_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}");
export const CONTROLLER_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime");
export const CONTROLLER_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime");
export const CONTROLLER_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection");
export const CONTROLLER_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection Status");
export const CONTROLELR_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1");
export const CONTROLELR_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2");
export const CONTROLLER_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1");
export const CONTROLLER_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2");
export const CONTROLLER_KEY_COPIED_HELP = localize('sql.migration.key.copied', "Key copied");
export const REFRESH_KEYS = localize('sql.migration.refresh.keys', "Refresh keys");
export const COPY_KEY = localize('sql.migration.copy.key', "Copy key");
export const AUTH_KEY_COLUMN_HEADER = localize('sql.migration.authkeys.header', "Authentication key");
export function CONTRLLER_NOT_READY(controllerName: string): string {
return localize('sql.migration.controller.not.ready', "Migration Controller {0} is not connected to self-hosted Integration Runtime on any node. Click Refresh", controllerName);
export function CONTROLLER_NOT_READY(controllerName: string): string {
return localize('sql.migration.controller.not.ready', "Migration Controller {0} is not connected to self-hosted Integration Runtime on any node.", controllerName);
}
export function CONTRLLER_READY(controllerName: string, host: string): string {
return localize('sql.migration.controller.ready', "Migration Controller '{0}' is connected to self-hosted Integration Runtime on the node - '{1}'.`", controllerName, host);
export function CONTROLLER_READY(controllerName: string, host: string): string {
return localize('sql.migration.controller.ready', "Migration Controller '{0}' is connected to self-hosted Integration Runtime on the node - {1}", controllerName, host);
}
export const RESOURCE_GROUP_NOT_FOUND = localize('sql.migration.resource.group.not.found', "No resource Groups found");
export const INVALID_RESOURCE_GROUP_ERROR = localize('sql.migration.invalid.resourceGroup.error', "Please select a valid resource group to proceed.");
@@ -143,8 +146,26 @@ export const REFRESH = localize('sql.migration.refresh', "Refresh");
export const SUBMIT = localize('sql.migration.submit', "Submit");
export const CREATE = localize('sql.migration.create', "Create");
export const CANCEL = localize('sql.migration.cancel', "Cancel");
export const TYPE = localize('sql.migration.type', "Type");
export const PATH = localize('sql.migration.path', "Path");
export const USER_ACCOUNT = localize('sql.migration.path.user.account', "User Account");
//Summary Page
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
export const AZURE_ACCOUNT_LINKED = localize('sql.migration.summary.azure.account.linked', "Azure account linked");
export const MIGRATION_TARGET = localize('sql.migration.summary.migration.target', "Migration target");
export const SUMMARY_MI_TYPE = localize('sql.migration.summary.mi.type', "Azure SQL Managed Instance");
export const SUMMARY_VM_TYPE = localize('sql.migration.summary.vm.type', "Azure SQL Virtual Machine");
export const SUMMARY_DATABASE_COUNT_LABEL = localize('sql.migration.summary.database.count', "Number of database to be migrated");
export const SUMMARY_AZURE_STORAGE_SUBSCRIPTION = localize('sql.migration.summary.azure.storage.subscription', "Azure storage subscription");
export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.storage', "Azure storage");
export const SUMMARY_IR_NODE = localize('sql.migration.ir.node', "Integration Runtime node");
export const NETWORK_SHARE = localize('sql.migration.network.share', "Network Share");
export const BLOB_CONTAINER = localize('sql.migration.blob.container', "Blob Container");
export const FILE_SHARE = localize('sql.migration.file.share', "File Share");
export const MIGRATION_STARTED = localize('sql.migration.started.notification', "Migration in progress");
// Open notebook quick pick string
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform");
export const NOTEBOOK_INLINE_MIGRATION_TITLE = localize('sql.migration.inline.migration.notebook.title', "Inline migration");

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();
});
}
}