mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Enable offline migration mode on sql migration extension (#16459)
This commit is contained in:
@@ -132,6 +132,14 @@ export async function getBlobContainers(account: azdata.Account, subscription: S
|
|||||||
return blobContainers!;
|
return blobContainers!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getBlobs(account: azdata.Account, subscription: Subscription, storageAccount: StorageAccount, containerName: string): Promise<azureResource.Blob[]> {
|
||||||
|
const api = await getAzureCoreAPI();
|
||||||
|
let result = await api.getBlobs(account, subscription, storageAccount, containerName, true);
|
||||||
|
let blobNames = result.blobs;
|
||||||
|
sortResourceArrayByName(blobNames);
|
||||||
|
return blobNames!;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
|
export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
|
||||||
const api = await getAzureCoreAPI();
|
const api = await getAzureCoreAPI();
|
||||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`;
|
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`;
|
||||||
@@ -320,7 +328,7 @@ export async function getLocationDisplayName(location: string): Promise<string>
|
|||||||
return await api.getRegionDisplayName(location);
|
return await api.getRegionDisplayName(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription | SqlMigrationService;
|
type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.Blob | azureResource.AzureResourceSubscription | SqlMigrationService;
|
||||||
function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void {
|
function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void {
|
||||||
if (!resourceArray) {
|
if (!resourceArray) {
|
||||||
return;
|
return;
|
||||||
@@ -405,7 +413,10 @@ export interface StartDatabaseMigrationRequest {
|
|||||||
password: string
|
password: string
|
||||||
},
|
},
|
||||||
scope: string,
|
scope: string,
|
||||||
autoCutoverConfiguration?: AutoCutoverConfiguration
|
autoCutoverConfiguration?: {
|
||||||
|
autoCutover?: boolean,
|
||||||
|
lastBackupName?: string
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,6 +480,7 @@ export interface BackupConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoCutoverConfiguration {
|
export interface AutoCutoverConfiguration {
|
||||||
|
autoCutover: boolean;
|
||||||
lastBackupName: string;
|
lastBackupName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { CategoryValue, DropDownComponent } from 'azdata';
|
import { CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
||||||
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { DAYS, HRS, MINUTE, SEC } from '../constants/strings';
|
import { DAYS, HRS, MINUTE, SEC } from '../constants/strings';
|
||||||
import { AdsMigrationStatus } from '../dialog/migrationStatus/migrationStatusDialogModel';
|
import { AdsMigrationStatus } from '../dialog/migrationStatus/migrationStatusDialogModel';
|
||||||
import { MigrationContext } from '../models/migrationLocalStorage';
|
import { MigrationContext } from '../models/migrationLocalStorage';
|
||||||
@@ -199,3 +200,21 @@ export function getSessionIdHeader(sessionId: string): { [key: string]: string }
|
|||||||
'SqlMigrationSessionId': sessionId
|
'SqlMigrationSessionId': sessionId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMigrationStatusImage(status: string): IconPath {
|
||||||
|
switch (status) {
|
||||||
|
case 'InProgress':
|
||||||
|
return IconPathHelper.inProgressMigration;
|
||||||
|
case 'Succeeded':
|
||||||
|
return IconPathHelper.completedMigration;
|
||||||
|
case 'Creating':
|
||||||
|
return IconPathHelper.notStartedMigration;
|
||||||
|
case 'Completing':
|
||||||
|
return IconPathHelper.completingCutover;
|
||||||
|
case 'Canceling':
|
||||||
|
return IconPathHelper.cancel;
|
||||||
|
case 'Failed':
|
||||||
|
default:
|
||||||
|
return IconPathHelper.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,11 +133,23 @@ export const NO_LOCATION_FOUND = localize('sql.migration.no.location.found', "No
|
|||||||
export const NO_STORAGE_ACCOUNT_FOUND = localize('sql.migration.no.storageAccount.found', "No storage account found");
|
export const NO_STORAGE_ACCOUNT_FOUND = localize('sql.migration.no.storageAccount.found', "No storage account found");
|
||||||
export const NO_FILESHARES_FOUND = localize('sql.migration.no.fileShares.found', "No file shares found");
|
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 NO_BLOBCONTAINERS_FOUND = localize('sql.migration.no.blobContainers.found', "No blob containers found");
|
||||||
|
export const NO_BLOBFILES_FOUND = localize('sql.migration.no.blobFiles.found', "No blob files found");
|
||||||
export const INVALID_SUBSCRIPTION_ERROR = localize('sql.migration.invalid.subscription.error', "Please select a valid subscription to proceed.");
|
export const INVALID_SUBSCRIPTION_ERROR = localize('sql.migration.invalid.subscription.error', "Please select a valid subscription to proceed.");
|
||||||
export const INVALID_LOCATION_ERROR = localize('sql.migration.invalid.location.error', "Please select a valid location to proceed.");
|
export const INVALID_LOCATION_ERROR = localize('sql.migration.invalid.location.error', "Please select a valid location 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_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_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 function INVALID_BLOB_RESOURCE_GROUP_ERROR(sourceDb: string): string {
|
||||||
|
return localize('sql.migration.invalid.blob.resourceGroup.error', "Please select a valid resource group for source database '{0}' to proceed.", sourceDb);
|
||||||
|
}
|
||||||
|
export function INVALID_BLOB_STORAGE_ACCOUNT_ERROR(sourceDb: string): string {
|
||||||
|
return localize('sql.migration.invalid.blob.storageAccount.error', "Please select a valid storage account for source database '{0}' to proceed.", sourceDb);
|
||||||
|
}
|
||||||
|
export function INVALID_BLOB_CONTAINER_ERROR(sourceDb: string): string {
|
||||||
|
return localize('sql.migration.invalid.blob.container.error', "Please select a valid blob container for source database '{0}' to proceed.", sourceDb);
|
||||||
|
}
|
||||||
|
export function INVALID_BLOB_LAST_BACKUP_FILE_ERROR(sourceDb: string): string {
|
||||||
|
return localize('sql.migration.invalid.blob.lastBackupFile.error', "Please select a valid last backup file for source database '{0}' to proceed.", sourceDb);
|
||||||
|
}
|
||||||
export const INVALID_NETWORK_SHARE_LOCATION = localize('sql.migration.invalid.network.share.location', "Invalid network share location format. Example: {0}", '\\\\Servername.domainname.com\\Backupfolder');
|
export const INVALID_NETWORK_SHARE_LOCATION = localize('sql.migration.invalid.network.share.location', "Invalid network share location format. Example: {0}", '\\\\Servername.domainname.com\\Backupfolder');
|
||||||
export const INVALID_USER_ACCOUNT = localize('sql.migration.invalid.user.account', "Invalid user account format. Example: {0}", 'Domain\\username');
|
export const INVALID_USER_ACCOUNT = localize('sql.migration.invalid.user.account', "Invalid user account format. Example: {0}", 'Domain\\username');
|
||||||
export function TARGET_NETWORK_SHARE_LOCATION(dbName: string): string {
|
export function TARGET_NETWORK_SHARE_LOCATION(dbName: string): string {
|
||||||
@@ -162,6 +174,9 @@ export function SQL_SOURCE_DETAILS(authMethod: MigrationSourceAuthenticationType
|
|||||||
return localize('sql.migration.source.details.sqlAuth', "Enter the SQL Authentication credential used for connecting to SQL Server Instance {0}. This credential will be used to for connecting to SQL Server instance and identifying valid backup file(s)", serverName);
|
return localize('sql.migration.source.details.sqlAuth', "Enter the SQL Authentication credential used for connecting to SQL Server Instance {0}. This credential will be used to for connecting to SQL Server instance and identifying valid backup file(s)", serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const SELECT_RESOURCE_GROUP = localize('sql.migration.blob.resourceGroup.select', "Select a resource group value first.");
|
||||||
|
export const SELECT_STORAGE_ACCOUNT = localize('sql.migration.blob.storageAccount.select', "Select a storage account value first.");
|
||||||
|
export const SELECT_BLOB_CONTAINER = localize('sql.migration.blob.container.select', "Select a blob container value first.");
|
||||||
|
|
||||||
// integration runtime page
|
// integration runtime page
|
||||||
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
|
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
|
||||||
@@ -270,6 +285,7 @@ export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.stora
|
|||||||
export const SUMMARY_IR_NODE = localize('sql.migration.ir.node', "Integration Runtime node");
|
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 NETWORK_SHARE = localize('sql.migration.network.share', "Network Share");
|
||||||
export const BLOB_CONTAINER = localize('sql.migration.blob.container.title', "Blob Container");
|
export const BLOB_CONTAINER = localize('sql.migration.blob.container.title', "Blob Container");
|
||||||
|
export const BLOB_CONTAINER_LAST_BACKUP_FILE = localize('sql.migration.blob.container.last.backup.file.label', "Last Backup File");
|
||||||
export const BLOB_CONTAINER_RESOURCE_GROUP = localize('sql.migration.blob.container.label', "Blob container resource group");
|
export const BLOB_CONTAINER_RESOURCE_GROUP = localize('sql.migration.blob.container.label', "Blob container resource group");
|
||||||
export const BLOB_CONTAINER_STORAGE_ACCOUNT = localize('sql.migration.blob.container.storage.account.label', "Blob container storage account");
|
export const BLOB_CONTAINER_STORAGE_ACCOUNT = localize('sql.migration.blob.container.storage.account.label', "Blob container storage account");
|
||||||
export const FILE_SHARE = localize('sql.migration.file.share.title', "File Share");
|
export const FILE_SHARE = localize('sql.migration.file.share.title', "File Share");
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import { IconPathHelper } from '../../constants/iconPathHelper';
|
|||||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||||
import { MigrationCutoverDialogModel, MigrationStatus } from './migrationCutoverDialogModel';
|
import { MigrationCutoverDialogModel, MigrationStatus } from './migrationCutoverDialogModel';
|
||||||
import * as loc from '../../constants/strings';
|
import * as loc from '../../constants/strings';
|
||||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, SupportedAutoRefreshIntervals } from '../../api/utils';
|
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals } from '../../api/utils';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
|
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
|
||||||
|
import { MigrationMode } from '../../models/stateMachine';
|
||||||
|
|
||||||
const refreshFrequency: SupportedAutoRefreshIntervals = 30000;
|
const refreshFrequency: SupportedAutoRefreshIntervals = 30000;
|
||||||
|
const statusImageSize: number = 14;
|
||||||
|
|
||||||
export class MigrationCutoverDialog {
|
export class MigrationCutoverDialog {
|
||||||
private _dialogObject!: azdata.window.Dialog;
|
private _dialogObject!: azdata.window.Dialog;
|
||||||
@@ -27,20 +29,21 @@ export class MigrationCutoverDialog {
|
|||||||
private _refreshLoader!: azdata.LoadingComponent;
|
private _refreshLoader!: azdata.LoadingComponent;
|
||||||
private _copyDatabaseMigrationDetails!: azdata.ButtonComponent;
|
private _copyDatabaseMigrationDetails!: azdata.ButtonComponent;
|
||||||
|
|
||||||
private _serverName!: azdata.TextComponent;
|
private _sourceDatabaseInfoField!: InfoFieldSchema;
|
||||||
private _serverVersion!: azdata.TextComponent;
|
private _sourceDetailsInfoField!: InfoFieldSchema;
|
||||||
private _sourceDatabase!: azdata.TextComponent;
|
private _sourceVersionInfoField!: InfoFieldSchema;
|
||||||
private _targetDatabase!: azdata.TextComponent;
|
private _targetDatabaseInfoField!: InfoFieldSchema;
|
||||||
private _targetServer!: azdata.TextComponent;
|
private _targetServerInfoField!: InfoFieldSchema;
|
||||||
private _targetVersion!: azdata.TextComponent;
|
private _targetVersionInfoField!: InfoFieldSchema;
|
||||||
private _migrationStatus!: azdata.TextComponent;
|
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||||
private _fullBackupFile!: azdata.TextComponent;
|
private _fullBackupFileOnInfoField!: InfoFieldSchema;
|
||||||
private _backupLocation!: azdata.TextComponent;
|
private _backupLocationInfoField!: InfoFieldSchema;
|
||||||
private _lastAppliedLSN!: azdata.TextComponent;
|
private _lastLSNInfoField!: InfoFieldSchema;
|
||||||
private _lastAppliedBackupFile!: azdata.TextComponent;
|
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
||||||
private _lastAppliedBackupTakenOn!: azdata.TextComponent;
|
private _lastAppliedBackupTakenOnInfoField!: InfoFieldSchema;
|
||||||
|
|
||||||
private _fileCount!: azdata.TextComponent;
|
private _fileCount!: azdata.TextComponent;
|
||||||
private fileTable!: azdata.TableComponent;
|
private _fileTable!: azdata.TableComponent;
|
||||||
private _autoRefreshHandle!: any;
|
private _autoRefreshHandle!: any;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
@@ -58,151 +61,6 @@ export class MigrationCutoverDialog {
|
|||||||
tab.registerContent(async (view: azdata.ModelView) => {
|
tab.registerContent(async (view: azdata.ModelView) => {
|
||||||
try {
|
try {
|
||||||
this._view = view;
|
this._view = view;
|
||||||
const sourceDatabase = this.createInfoField(loc.SOURCE_DATABASE, '');
|
|
||||||
const sourceDetails = this.createInfoField(loc.SOURCE_SERVER, '');
|
|
||||||
const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, '');
|
|
||||||
|
|
||||||
this._sourceDatabase = sourceDatabase.text;
|
|
||||||
this._serverName = sourceDetails.text;
|
|
||||||
this._serverVersion = sourceVersion.text;
|
|
||||||
|
|
||||||
const flexServer = view.modelBuilder.flexContainer().withLayout({
|
|
||||||
flexFlow: 'column'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
flexServer.addItem(sourceDatabase.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexServer.addItem(sourceDetails.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexServer.addItem(sourceVersion.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const targetDatabase = this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
|
||||||
const targetServer = this.createInfoField(loc.TARGET_SERVER, '');
|
|
||||||
const targetVersion = this.createInfoField(loc.TARGET_VERSION, '');
|
|
||||||
|
|
||||||
this._targetDatabase = targetDatabase.text;
|
|
||||||
this._targetServer = targetServer.text;
|
|
||||||
this._targetVersion = targetVersion.text;
|
|
||||||
|
|
||||||
const flexTarget = view.modelBuilder.flexContainer().withLayout({
|
|
||||||
flexFlow: 'column'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
flexTarget.addItem(targetDatabase.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexTarget.addItem(targetServer.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexTarget.addItem(targetVersion.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const migrationStatus = this.createInfoField(loc.MIGRATION_STATUS, '');
|
|
||||||
const fullBackupFileOn = this.createInfoField(loc.FULL_BACKUP_FILES, '');
|
|
||||||
const backupLocation = this.createInfoField(loc.BACKUP_LOCATION, '');
|
|
||||||
|
|
||||||
this._migrationStatus = migrationStatus.text;
|
|
||||||
this._fullBackupFile = fullBackupFileOn.text;
|
|
||||||
this._backupLocation = backupLocation.text;
|
|
||||||
|
|
||||||
const flexStatus = view.modelBuilder.flexContainer().withLayout({
|
|
||||||
flexFlow: 'column'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
flexStatus.addItem(migrationStatus.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexStatus.addItem(fullBackupFileOn.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexStatus.addItem(backupLocation.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const lastSSN = this.createInfoField(loc.LAST_APPLIED_LSN, '');
|
|
||||||
const lastAppliedBackup = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
|
||||||
const lastAppliedBackupOn = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '');
|
|
||||||
|
|
||||||
this._lastAppliedLSN = lastSSN.text;
|
|
||||||
this._lastAppliedBackupFile = lastAppliedBackup.text;
|
|
||||||
this._lastAppliedBackupTakenOn = lastAppliedBackupOn.text;
|
|
||||||
|
|
||||||
const flexFile = view.modelBuilder.flexContainer().withLayout({
|
|
||||||
flexFlow: 'column'
|
|
||||||
}).component();
|
|
||||||
flexFile.addItem(lastSSN.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexFile.addItem(lastAppliedBackup.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
flexFile.addItem(lastAppliedBackupOn.flexContainer, {
|
|
||||||
CSSStyles: {
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const flexInfo = view.modelBuilder.flexContainer().withProps({
|
|
||||||
width: 1000
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
flexInfo.addItem(flexServer, {
|
|
||||||
flex: '0',
|
|
||||||
CSSStyles: {
|
|
||||||
'flex': '0',
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flexInfo.addItem(flexTarget, {
|
|
||||||
flex: '0',
|
|
||||||
CSSStyles: {
|
|
||||||
'flex': '0',
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flexInfo.addItem(flexStatus, {
|
|
||||||
flex: '0',
|
|
||||||
CSSStyles: {
|
|
||||||
'flex': '0',
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flexInfo.addItem(flexFile, {
|
|
||||||
flex: '0',
|
|
||||||
CSSStyles: {
|
|
||||||
'flex': '0',
|
|
||||||
'width': this._infoFieldWidth
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._fileCount = view.modelBuilder.text().withProps({
|
this._fileCount = view.modelBuilder.text().withProps({
|
||||||
width: '500px',
|
width: '500px',
|
||||||
@@ -212,7 +70,7 @@ export class MigrationCutoverDialog {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.fileTable = view.modelBuilder.table().withProps({
|
this._fileTable = view.modelBuilder.table().withProps({
|
||||||
ariaLabel: loc.ACTIVE_BACKUP_FILES,
|
ariaLabel: loc.ACTIVE_BACKUP_FILES,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -259,18 +117,23 @@ export class MigrationCutoverDialog {
|
|||||||
data: [],
|
data: [],
|
||||||
width: '1100px',
|
width: '1100px',
|
||||||
height: '300px',
|
height: '300px',
|
||||||
fontSize: '12px'
|
fontSize: '12px',
|
||||||
|
CSSStyles: {
|
||||||
|
'display': 'none',
|
||||||
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
let formItems = [
|
||||||
|
{ component: this.migrationContainerHeader() },
|
||||||
|
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
|
||||||
|
{ component: this.migrationInfoGrid() },
|
||||||
|
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
|
||||||
|
{ component: this._fileCount },
|
||||||
|
{ component: this._fileTable }
|
||||||
|
];
|
||||||
|
|
||||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||||
[
|
formItems,
|
||||||
{ component: this.migrationContainerHeader() },
|
|
||||||
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
|
|
||||||
{ component: flexInfo },
|
|
||||||
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
|
|
||||||
{ component: this._fileCount },
|
|
||||||
{ component: this.fileTable }
|
|
||||||
],
|
|
||||||
{ horizontal: false }
|
{ horizontal: false }
|
||||||
);
|
);
|
||||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||||
@@ -367,7 +230,8 @@ export class MigrationCutoverDialog {
|
|||||||
width: '150px',
|
width: '150px',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'font-size': '13px'
|
'font-size': '13px',
|
||||||
|
'display': 'none'
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
@@ -389,6 +253,7 @@ export class MigrationCutoverDialog {
|
|||||||
label: loc.CANCEL_MIGRATION,
|
label: loc.CANCEL_MIGRATION,
|
||||||
height: '20px',
|
height: '20px',
|
||||||
width: '150px',
|
width: '150px',
|
||||||
|
enabled: false,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'font-size': '13px'
|
'font-size': '13px'
|
||||||
}
|
}
|
||||||
@@ -491,14 +356,84 @@ export class MigrationCutoverDialog {
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
private migrationInfoGrid(): azdata.FlexContainer {
|
||||||
const classVariable = this;
|
const addInfoFieldToContainer = (infoField: InfoFieldSchema, container: azdata.FlexContainer): void => {
|
||||||
clearInterval(this._autoRefreshHandle);
|
container.addItem(infoField.flexContainer, {
|
||||||
if (interval !== -1) {
|
CSSStyles: {
|
||||||
this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshStatus(); }, interval);
|
width: this._infoFieldWidth,
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const flexServer = this._view.modelBuilder.flexContainer().withLayout({
|
||||||
|
flexFlow: 'column'
|
||||||
|
}).component();
|
||||||
|
this._sourceDatabaseInfoField = this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||||
|
this._sourceDetailsInfoField = this.createInfoField(loc.SOURCE_SERVER, '');
|
||||||
|
this._sourceVersionInfoField = this.createInfoField(loc.SOURCE_VERSION, '');
|
||||||
|
addInfoFieldToContainer(this._sourceDatabaseInfoField, flexServer);
|
||||||
|
addInfoFieldToContainer(this._sourceDetailsInfoField, flexServer);
|
||||||
|
addInfoFieldToContainer(this._sourceVersionInfoField, flexServer);
|
||||||
|
|
||||||
|
const flexTarget = this._view.modelBuilder.flexContainer().withLayout({
|
||||||
|
flexFlow: 'column'
|
||||||
|
}).component();
|
||||||
|
this._targetDatabaseInfoField = this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||||
|
this._targetServerInfoField = this.createInfoField(loc.TARGET_SERVER, '');
|
||||||
|
this._targetVersionInfoField = this.createInfoField(loc.TARGET_VERSION, '');
|
||||||
|
addInfoFieldToContainer(this._targetDatabaseInfoField, flexTarget);
|
||||||
|
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
|
||||||
|
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
|
||||||
|
|
||||||
|
const flexStatus = this._view.modelBuilder.flexContainer().withLayout({
|
||||||
|
flexFlow: 'column'
|
||||||
|
}).component();
|
||||||
|
this._migrationStatusInfoField = this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||||
|
this._fullBackupFileOnInfoField = this.createInfoField(loc.FULL_BACKUP_FILES, '', true);
|
||||||
|
this._backupLocationInfoField = this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||||
|
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
|
||||||
|
addInfoFieldToContainer(this._fullBackupFileOnInfoField, flexStatus);
|
||||||
|
addInfoFieldToContainer(this._backupLocationInfoField, flexStatus);
|
||||||
|
|
||||||
|
const flexFile = this._view.modelBuilder.flexContainer().withLayout({
|
||||||
|
flexFlow: 'column'
|
||||||
|
}).component();
|
||||||
|
this._lastLSNInfoField = this.createInfoField(loc.LAST_APPLIED_LSN, '', true);
|
||||||
|
this._lastAppliedBackupInfoField = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||||
|
this._lastAppliedBackupTakenOnInfoField = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', true);
|
||||||
|
addInfoFieldToContainer(this._lastLSNInfoField, flexFile);
|
||||||
|
addInfoFieldToContainer(this._lastAppliedBackupInfoField, flexFile);
|
||||||
|
addInfoFieldToContainer(this._lastAppliedBackupTakenOnInfoField, flexFile);
|
||||||
|
|
||||||
|
const flexInfoProps = {
|
||||||
|
flex: '0',
|
||||||
|
CSSStyles: {
|
||||||
|
'flex': '0',
|
||||||
|
'width': this._infoFieldWidth
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flexInfo = this._view.modelBuilder.flexContainer().withProps({
|
||||||
|
width: 1000
|
||||||
|
}).component();
|
||||||
|
flexInfo.addItem(flexServer, flexInfoProps);
|
||||||
|
flexInfo.addItem(flexTarget, flexInfoProps);
|
||||||
|
flexInfo.addItem(flexStatus, flexInfoProps);
|
||||||
|
flexInfo.addItem(flexFile, flexInfoProps);
|
||||||
|
|
||||||
|
return flexInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
||||||
|
const shouldRefresh = (status: string | undefined) => !status || ['InProgress', 'Creating', 'Completing', 'Creating'].includes(status);
|
||||||
|
if (shouldRefresh(this.getMigrationStatus())) {
|
||||||
|
const classVariable = this;
|
||||||
|
clearInterval(this._autoRefreshHandle);
|
||||||
|
if (interval !== -1) {
|
||||||
|
this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshStatus(); }, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshStatus(): Promise<void> {
|
private async refreshStatus(): Promise<void> {
|
||||||
if (this.isRefreshing) {
|
if (this.isRefreshing) {
|
||||||
@@ -506,10 +441,14 @@ export class MigrationCutoverDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (this._isProvisioned() && this._isOnlineMigration()) {
|
||||||
|
this._cutoverButton.updateCssStyles({
|
||||||
|
'display': 'inline'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
this._refreshLoader.loading = true;
|
this._refreshLoader.loading = true;
|
||||||
this._cutoverButton.enabled = false;
|
|
||||||
this._cancelButton.enabled = false;
|
|
||||||
await this._model.fetchStatus();
|
await this._model.fetchStatus();
|
||||||
const errors = [];
|
const errors = [];
|
||||||
errors.push(this._model.migrationOpStatus.error?.message);
|
errors.push(this._model.migrationOpStatus.error?.message);
|
||||||
@@ -534,81 +473,92 @@ export class MigrationCutoverDialog {
|
|||||||
targetServerVersion = loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
|
targetServerVersion = loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrationStatusTextValue = this._model.migrationStatus.properties.migrationStatus ? this._model.migrationStatus.properties.migrationStatus : this._model.migrationStatus.properties.provisioningState;
|
|
||||||
|
|
||||||
let lastAppliedSSN: string;
|
let lastAppliedSSN: string;
|
||||||
let lastAppliedBackupFileTakenOn: string;
|
let lastAppliedBackupFileTakenOn: string;
|
||||||
|
|
||||||
|
|
||||||
const tableData: ActiveBackupFileSchema[] = [];
|
const tableData: ActiveBackupFileSchema[] = [];
|
||||||
|
|
||||||
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach((activeBackupSet) => {
|
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach((activeBackupSet) => {
|
||||||
|
|
||||||
tableData.push(
|
if (this._shouldDisplayBackupFileTable()) {
|
||||||
{
|
tableData.push(
|
||||||
fileName: activeBackupSet.listOfBackupFiles[0].fileName,
|
{
|
||||||
type: activeBackupSet.backupType,
|
fileName: activeBackupSet.listOfBackupFiles[0].fileName,
|
||||||
status: activeBackupSet.listOfBackupFiles[0].status,
|
type: activeBackupSet.backupType,
|
||||||
dataUploaded: `${convertByteSizeToReadableUnit(activeBackupSet.listOfBackupFiles[0].dataWritten)}/ ${convertByteSizeToReadableUnit(activeBackupSet.listOfBackupFiles[0].totalSize)}`,
|
status: activeBackupSet.listOfBackupFiles[0].status,
|
||||||
copyThroughput: (activeBackupSet.listOfBackupFiles[0].copyThroughput / 1024).toFixed(2),
|
dataUploaded: `${convertByteSizeToReadableUnit(activeBackupSet.listOfBackupFiles[0].dataWritten)}/ ${convertByteSizeToReadableUnit(activeBackupSet.listOfBackupFiles[0].totalSize)}`,
|
||||||
backupStartTime: activeBackupSet.backupStartDate,
|
copyThroughput: (activeBackupSet.listOfBackupFiles[0].copyThroughput) ? (activeBackupSet.listOfBackupFiles[0].copyThroughput / 1024).toFixed(2) : '-',
|
||||||
firstLSN: activeBackupSet.firstLSN,
|
backupStartTime: activeBackupSet.backupStartDate,
|
||||||
lastLSN: activeBackupSet.lastLSN
|
firstLSN: activeBackupSet.firstLSN,
|
||||||
|
lastLSN: activeBackupSet.lastLSN
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._sourceDatabase.value = sourceDatabaseName;
|
this._sourceDatabaseInfoField.text.value = sourceDatabaseName;
|
||||||
this._serverName.value = sqlServerName;
|
this._sourceDetailsInfoField.text.value = sqlServerName;
|
||||||
this._serverVersion.value = `${sqlServerVersion} ${sqlServerInfo.serverVersion}`;
|
this._sourceVersionInfoField.text.value = `${sqlServerVersion} ${sqlServerInfo.serverVersion}`;
|
||||||
|
|
||||||
this._targetDatabase.value = targetDatabaseName;
|
this._targetDatabaseInfoField.text.value = targetDatabaseName;
|
||||||
this._targetServer.value = targetServerName;
|
this._targetServerInfoField.text.value = targetServerName;
|
||||||
this._targetVersion.value = targetServerVersion;
|
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||||
|
|
||||||
|
const migrationStatusTextValue = this.getMigrationStatus();
|
||||||
|
this._migrationStatusInfoField.text.value = migrationStatusTextValue ?? '-';
|
||||||
|
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migrationStatusTextValue);
|
||||||
|
|
||||||
|
this._fullBackupFileOnInfoField.text.value = this._model.migrationStatus?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? '-';
|
||||||
|
this.showInfoField(this._fullBackupFileOnInfoField);
|
||||||
|
|
||||||
this._migrationStatus.value = migrationStatusTextValue ?? '---';
|
|
||||||
this._fullBackupFile.value = this._model.migrationStatus?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? '-';
|
|
||||||
let backupLocation;
|
let backupLocation;
|
||||||
|
const isBlobMigration = this._isBlobMigration();
|
||||||
const isBlobMigration = this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob !== undefined;
|
|
||||||
// Displaying storage accounts and blob container for azure blob backups.
|
// Displaying storage accounts and blob container for azure blob backups.
|
||||||
if (isBlobMigration) {
|
if (isBlobMigration) {
|
||||||
backupLocation = `${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.storageAccountResourceId.split('/').pop()} - ${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.blobContainerName}`;
|
backupLocation = `${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.storageAccountResourceId.split('/').pop()} - ${this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob?.blobContainerName}`;
|
||||||
this._fileCount.display = 'none';
|
|
||||||
this.fileTable.updateCssStyles({
|
|
||||||
'display': 'none'
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
backupLocation = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare?.path! ?? '-';
|
backupLocation = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare?.path! ?? '-';
|
||||||
}
|
}
|
||||||
this._backupLocation.value = backupLocation ?? '-';
|
this._backupLocationInfoField.text.value = backupLocation ?? '-';
|
||||||
|
|
||||||
this._lastAppliedLSN.value = lastAppliedSSN! ?? '-';
|
this._lastLSNInfoField.text.value = lastAppliedSSN! ?? '-';
|
||||||
this._lastAppliedBackupFile.value = this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename ?? '-';
|
this._lastAppliedBackupInfoField.text.value = this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename ?? '-';
|
||||||
this._lastAppliedBackupTakenOn.value = lastAppliedBackupFileTakenOn! ? convertIsoTimeToLocalTime(lastAppliedBackupFileTakenOn).toLocaleString() : '-';
|
this._lastAppliedBackupTakenOnInfoField.text.value = lastAppliedBackupFileTakenOn! ? convertIsoTimeToLocalTime(lastAppliedBackupFileTakenOn).toLocaleString() : '-';
|
||||||
|
this.showInfoField(this._lastLSNInfoField);
|
||||||
|
this.showInfoField(this._lastAppliedBackupTakenOnInfoField);
|
||||||
|
|
||||||
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
if (this._shouldDisplayBackupFileTable()) {
|
||||||
|
this._fileCount.updateCssStyles({
|
||||||
|
display: 'inline'
|
||||||
|
});
|
||||||
|
this._fileTable.updateCssStyles({
|
||||||
|
display: 'inline'
|
||||||
|
});
|
||||||
|
|
||||||
// Sorting files in descending order of backupStartTime
|
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
||||||
tableData.sort((file1, file2) => new Date(file1.backupStartTime) > new Date(file2.backupStartTime) ? - 1 : 1);
|
|
||||||
|
|
||||||
this.fileTable.data = tableData.map((row) => {
|
// Sorting files in descending order of backupStartTime
|
||||||
return [
|
tableData.sort((file1, file2) => new Date(file1.backupStartTime) > new Date(file2.backupStartTime) ? - 1 : 1);
|
||||||
row.fileName,
|
|
||||||
row.type,
|
this._fileTable.data = tableData.map((row) => {
|
||||||
row.status,
|
return [
|
||||||
row.dataUploaded,
|
row.fileName,
|
||||||
row.copyThroughput,
|
row.type,
|
||||||
convertIsoTimeToLocalTime(row.backupStartTime).toLocaleString(),
|
row.status,
|
||||||
row.firstLSN,
|
row.dataUploaded,
|
||||||
row.lastLSN
|
row.copyThroughput,
|
||||||
];
|
convertIsoTimeToLocalTime(row.backupStartTime).toLocaleString(),
|
||||||
});
|
row.firstLSN,
|
||||||
|
row.lastLSN
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (migrationStatusTextValue === MigrationStatus.InProgress) {
|
if (migrationStatusTextValue === MigrationStatus.InProgress) {
|
||||||
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length ?? 0;
|
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length ?? 0;
|
||||||
@@ -628,14 +578,21 @@ export class MigrationCutoverDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createInfoField(label: string, value: string): {
|
private createInfoField(label: string, value: string, defaultHidden: boolean = false, iconPath?: azdata.IconPath): {
|
||||||
flexContainer: azdata.FlexContainer,
|
flexContainer: azdata.FlexContainer,
|
||||||
text: azdata.TextComponent
|
text: azdata.TextComponent,
|
||||||
|
icon?: azdata.ImageComponent
|
||||||
} {
|
} {
|
||||||
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||||
flexFlow: 'column'
|
flexFlow: 'column'
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
if (defaultHidden) {
|
||||||
|
flexContainer.updateCssStyles({
|
||||||
|
'display': 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const labelComponent = this._view.modelBuilder.text().withProps({
|
const labelComponent = this._view.modelBuilder.text().withProps({
|
||||||
value: label,
|
value: label,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
@@ -657,12 +614,79 @@ export class MigrationCutoverDialog {
|
|||||||
'font-size': '12px'
|
'font-size': '12px'
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
flexContainer.addItem(textComponent);
|
|
||||||
|
let iconComponent;
|
||||||
|
if (iconPath) {
|
||||||
|
iconComponent = this._view.modelBuilder.image().withProps({
|
||||||
|
iconPath: (iconPath === ' ') ? undefined : iconPath,
|
||||||
|
iconHeight: statusImageSize,
|
||||||
|
iconWidth: statusImageSize,
|
||||||
|
height: statusImageSize,
|
||||||
|
width: statusImageSize,
|
||||||
|
CSSStyles: {
|
||||||
|
'margin': '7px 3px 0 0',
|
||||||
|
'padding': '0'
|
||||||
|
}
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const iconTextComponent = this._view.modelBuilder.flexContainer()
|
||||||
|
.withItems([
|
||||||
|
iconComponent,
|
||||||
|
textComponent
|
||||||
|
]).withProps({
|
||||||
|
CSSStyles: {
|
||||||
|
'margin': '0',
|
||||||
|
'padding': '0'
|
||||||
|
},
|
||||||
|
display: 'inline-flex'
|
||||||
|
}).component();
|
||||||
|
flexContainer.addItem(iconTextComponent);
|
||||||
|
} else {
|
||||||
|
flexContainer.addItem(textComponent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
flexContainer: flexContainer,
|
flexContainer: flexContainer,
|
||||||
text: textComponent
|
text: textComponent,
|
||||||
|
icon: iconComponent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showInfoField(infoField: InfoFieldSchema): void {
|
||||||
|
if (infoField.text.value !== '-') {
|
||||||
|
infoField.flexContainer.updateCssStyles({
|
||||||
|
'display': 'inline'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isProvisioned(): boolean {
|
||||||
|
const { migrationStatus, provisioningState } = this._model._migration.migrationContext.properties;
|
||||||
|
return provisioningState === 'Succeeded' || migrationStatus === 'Completing' || migrationStatus === 'Canceling';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isOnlineMigration(): boolean {
|
||||||
|
let migrationMode = null;
|
||||||
|
if (this._isProvisioned()) {
|
||||||
|
migrationMode = this._model._migration.migrationContext.properties.autoCutoverConfiguration?.autoCutover?.valueOf() ? MigrationMode.OFFLINE : MigrationMode.ONLINE;
|
||||||
|
}
|
||||||
|
return migrationMode === MigrationMode.ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isBlobMigration(): boolean {
|
||||||
|
return this._model._migration.migrationContext.properties.backupConfiguration.sourceLocation?.azureBlob !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldDisplayBackupFileTable(): boolean {
|
||||||
|
return this._isProvisioned() && this._isOnlineMigration() && !this._isBlobMigration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMigrationStatus(): string {
|
||||||
|
if (this._model.migrationStatus) {
|
||||||
|
return this._model.migrationStatus.properties.migrationStatus ?? this._model.migrationStatus.properties.provisioningState;
|
||||||
|
}
|
||||||
|
return this._model._migration.migrationContext.properties.migrationStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActiveBackupFileSchema {
|
interface ActiveBackupFileSchema {
|
||||||
@@ -675,3 +699,9 @@ interface ActiveBackupFileSchema {
|
|||||||
firstLSN: string,
|
firstLSN: string,
|
||||||
lastLSN: string
|
lastLSN: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InfoFieldSchema {
|
||||||
|
flexContainer: azdata.FlexContainer,
|
||||||
|
text: azdata.TextComponent,
|
||||||
|
icon?: azdata.ImageComponent
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { MigrationContext, MigrationLocalStorage } from '../../models/migrationL
|
|||||||
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
||||||
import { AdsMigrationStatus, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
import { AdsMigrationStatus, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
||||||
import * as loc from '../../constants/strings';
|
import * as loc from '../../constants/strings';
|
||||||
import { convertTimeDifferenceToDuration, filterMigrations, SupportedAutoRefreshIntervals } from '../../api/utils';
|
import { convertTimeDifferenceToDuration, filterMigrations, getMigrationStatusImage, SupportedAutoRefreshIntervals } from '../../api/utils';
|
||||||
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
||||||
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
|
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
|
||||||
import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel';
|
import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel';
|
||||||
@@ -286,7 +286,7 @@ export class MigrationStatusDialog {
|
|||||||
return [
|
return [
|
||||||
{ value: this._getDatabaserHyperLink(migration) },
|
{ value: this._getDatabaserHyperLink(migration) },
|
||||||
{ value: this._getMigrationStatus(migration) },
|
{ value: this._getMigrationStatus(migration) },
|
||||||
{ value: loc.ONLINE },
|
{ value: this._getMigrationMode(migration) },
|
||||||
{ value: this._getMigrationTargetType(migration) },
|
{ value: this._getMigrationTargetType(migration) },
|
||||||
{ value: migration.targetManagedInstance.name },
|
{ value: migration.targetManagedInstance.name },
|
||||||
{ value: migration.controller.name },
|
{ value: migration.controller.name },
|
||||||
@@ -299,14 +299,7 @@ export class MigrationStatusDialog {
|
|||||||
{ value: this._getMigrationTime(migration.migrationContext.properties.endedOn) },
|
{ value: this._getMigrationTime(migration.migrationContext.properties.endedOn) },
|
||||||
{
|
{
|
||||||
value: {
|
value: {
|
||||||
commands: [
|
commands: this._getMenuCommands(migration),
|
||||||
'sqlmigration.cutover',
|
|
||||||
'sqlmigration.view.database',
|
|
||||||
'sqlmigration.view.target',
|
|
||||||
'sqlmigration.view.service',
|
|
||||||
'sqlmigration.copy.migration',
|
|
||||||
'sqlmigration.cancel.migration',
|
|
||||||
],
|
|
||||||
context: migration.migrationContext.id
|
context: migration.migrationContext.id
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -377,6 +370,27 @@ export class MigrationStatusDialog {
|
|||||||
: loc.SQL_VIRTUAL_MACHINE;
|
: loc.SQL_VIRTUAL_MACHINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getMigrationMode(migration: MigrationContext): string {
|
||||||
|
if (migration.migrationContext.properties.provisioningState === 'Creating') {
|
||||||
|
return '---';
|
||||||
|
}
|
||||||
|
return migration.migrationContext.properties.autoCutoverConfiguration?.autoCutover?.valueOf() ? loc.OFFLINE : loc.ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMenuCommands(migration: MigrationContext): string[] {
|
||||||
|
let menuCommands = [
|
||||||
|
'sqlmigration.view.database',
|
||||||
|
'sqlmigration.view.target',
|
||||||
|
'sqlmigration.view.service',
|
||||||
|
'sqlmigration.copy.migration',
|
||||||
|
'sqlmigration.cancel.migration',
|
||||||
|
];
|
||||||
|
if (this._getMigrationMode(migration) === loc.ONLINE) {
|
||||||
|
menuCommands.unshift('sqlmigration.cutover');
|
||||||
|
}
|
||||||
|
return menuCommands;
|
||||||
|
}
|
||||||
|
|
||||||
private _getMigrationStatus(migration: MigrationContext): azdata.FlexContainer {
|
private _getMigrationStatus(migration: MigrationContext): azdata.FlexContainer {
|
||||||
const properties = migration.migrationContext.properties;
|
const properties = migration.migrationContext.properties;
|
||||||
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
||||||
@@ -404,7 +418,7 @@ export class MigrationStatusDialog {
|
|||||||
// migration status icon
|
// migration status icon
|
||||||
this._view.modelBuilder.image()
|
this._view.modelBuilder.image()
|
||||||
.withProps({
|
.withProps({
|
||||||
iconPath: this._statusImageMap(status),
|
iconPath: getMigrationStatusImage(status),
|
||||||
iconHeight: statusImageSize,
|
iconHeight: statusImageSize,
|
||||||
iconWidth: statusImageSize,
|
iconWidth: statusImageSize,
|
||||||
height: statusImageSize,
|
height: statusImageSize,
|
||||||
@@ -568,24 +582,6 @@ export class MigrationStatusDialog {
|
|||||||
return this._statusTable;
|
return this._statusTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _statusImageMap(status: string): azdata.IconPath {
|
|
||||||
switch (status) {
|
|
||||||
case 'InProgress':
|
|
||||||
return IconPathHelper.inProgressMigration;
|
|
||||||
case 'Succeeded':
|
|
||||||
return IconPathHelper.completedMigration;
|
|
||||||
case 'Creating':
|
|
||||||
return IconPathHelper.notStartedMigration;
|
|
||||||
case 'Completing':
|
|
||||||
return IconPathHelper.completingCutover;
|
|
||||||
case 'Canceling':
|
|
||||||
return IconPathHelper.cancel;
|
|
||||||
case 'Failed':
|
|
||||||
default:
|
|
||||||
return IconPathHelper.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _statusInfoMap(status: string): azdata.IconPath {
|
private _statusInfoMap(status: string): azdata.IconPath {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'InProgress':
|
case 'InProgress':
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { MigrationStateModel, NetworkContainerType } from '../../models/stateMachine';
|
import { MigrationMode, MigrationStateModel, NetworkContainerType } from '../../models/stateMachine';
|
||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
|
|
||||||
export class TargetDatabaseSummaryDialog {
|
export class TargetDatabaseSummaryDialog {
|
||||||
@@ -15,8 +15,8 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
constructor(private _model: MigrationStateModel) {
|
constructor(private _model: MigrationStateModel) {
|
||||||
let dialogWidth: azdata.window.DialogWidth;
|
let dialogWidth: azdata.window.DialogWidth;
|
||||||
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
||||||
this._tableLength = 600;
|
this._tableLength = 800;
|
||||||
dialogWidth = 'medium';
|
dialogWidth = 900;
|
||||||
} else {
|
} else {
|
||||||
this._tableLength = 200;
|
this._tableLength = 200;
|
||||||
dialogWidth = 'narrow';
|
dialogWidth = 'narrow';
|
||||||
@@ -109,6 +109,14 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
|
}, {
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
displayName: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: columnWidth,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyle,
|
||||||
|
hidden: this._model._databaseBackup.migrationMode === MigrationMode.ONLINE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +139,12 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
}, {
|
}, {
|
||||||
value: this._model._databaseBackup.blobs[index].blobContainer.name
|
value: this._model._databaseBackup.blobs[index].blobContainer.name
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this._model._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
tableRow.push({
|
||||||
|
value: this._model._databaseBackup.blobs[index].lastBackupFile!
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tableRows.push(tableRow);
|
tableRows.push(tableRow);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export abstract class MigrationWizardPage {
|
|||||||
return this.wizardPage;
|
return this.wizardPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract onPageEnter(): Promise<void>;
|
public abstract onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void>;
|
||||||
public abstract onPageLeave(): Promise<void>;
|
public abstract onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void>;
|
||||||
|
|
||||||
private readonly stateChanges: (() => Promise<void>)[] = [];
|
private readonly stateChanges: (() => Promise<void>)[] = [];
|
||||||
protected async onStateChangeEvent(e: StateChangeEvent) {
|
protected async onStateChangeEvent(e: StateChangeEvent) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { azureResource } from 'azureResource';
|
|||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as mssql from '../../../mssql';
|
import * as mssql from '../../../mssql';
|
||||||
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer, getLocations, getResourceGroups, getLocationDisplayName, getSqlManagedInstanceDatabases } from '../api/azure';
|
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer, getLocations, getResourceGroups, getLocationDisplayName, getSqlManagedInstanceDatabases, getBlobs } from '../api/azure';
|
||||||
import { SKURecommendations } from './externalContract';
|
import { SKURecommendations } from './externalContract';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { MigrationLocalStorage } from './migrationLocalStorage';
|
import { MigrationLocalStorage } from './migrationLocalStorage';
|
||||||
@@ -80,6 +80,7 @@ export interface Blob {
|
|||||||
storageAccount: StorageAccount;
|
storageAccount: StorageAccount;
|
||||||
blobContainer: azureResource.BlobContainer;
|
blobContainer: azureResource.BlobContainer;
|
||||||
storageKey: string;
|
storageKey: string;
|
||||||
|
lastBackupFile?: string; // _todo: does it make sense to store the last backup file here?
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Model {
|
export interface Model {
|
||||||
@@ -122,6 +123,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _storageAccounts!: StorageAccount[];
|
public _storageAccounts!: StorageAccount[];
|
||||||
public _fileShares!: azureResource.FileShare[];
|
public _fileShares!: azureResource.FileShare[];
|
||||||
public _blobContainers!: azureResource.BlobContainer[];
|
public _blobContainers!: azureResource.BlobContainer[];
|
||||||
|
public _lastFileNames!: azureResource.Blob[];
|
||||||
public _refreshNetworkShareLocation!: azureResource.BlobContainer[];
|
public _refreshNetworkShareLocation!: azureResource.BlobContainer[];
|
||||||
public _targetDatabaseNames!: string[];
|
public _targetDatabaseNames!: string[];
|
||||||
|
|
||||||
@@ -739,6 +741,40 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
return this._blobContainers[index];
|
return this._blobContainers[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getBlobLastBackupFileNameValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount, blobContainer: azureResource.BlobContainer): Promise<azdata.CategoryValue[]> {
|
||||||
|
let blobLastBackupFileValues: azdata.CategoryValue[] = [];
|
||||||
|
try {
|
||||||
|
this._lastFileNames = await getBlobs(this._azureAccount, subscription, storageAccount, blobContainer.name);
|
||||||
|
if (this._lastFileNames.length === 0) {
|
||||||
|
blobLastBackupFileValues = [
|
||||||
|
{
|
||||||
|
displayName: constants.NO_BLOBFILES_FOUND,
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this._lastFileNames.forEach((blob) => {
|
||||||
|
blobLastBackupFileValues.push({
|
||||||
|
name: blob.name,
|
||||||
|
displayName: `${blob.name}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
blobLastBackupFileValues = [
|
||||||
|
{
|
||||||
|
displayName: constants.NO_BLOBFILES_FOUND,
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return blobLastBackupFileValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBlobLastBackupFileName(index: number): string {
|
||||||
|
return this._lastFileNames[index].name;
|
||||||
|
}
|
||||||
|
|
||||||
public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance, resourceGroupName: string): Promise<azdata.CategoryValue[]> {
|
public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance, resourceGroupName: string): Promise<azdata.CategoryValue[]> {
|
||||||
let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
|
let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
|
||||||
@@ -797,7 +833,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
username: this._sqlServerUsername,
|
username: this._sqlServerUsername,
|
||||||
password: this._sqlServerPassword
|
password: this._sqlServerPassword
|
||||||
},
|
},
|
||||||
scope: this._targetServerInstance.id
|
scope: this._targetServerInstance.id,
|
||||||
|
autoCutoverConfiguration: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -815,6 +852,13 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
requestBody.properties.autoCutoverConfiguration = {
|
||||||
|
autoCutover: (this._databaseBackup.migrationMode === MigrationMode.OFFLINE ? true : false),
|
||||||
|
lastBackupName: this._databaseBackup.blobs[i]?.lastBackupFile
|
||||||
|
};
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkContainerType.NETWORK_SHARE:
|
case NetworkContainerType.NETWORK_SHARE:
|
||||||
requestBody.properties.backupConfiguration = {
|
requestBody.properties.backupConfiguration = {
|
||||||
@@ -830,6 +874,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
requestBody.properties.autoCutoverConfiguration = {
|
||||||
|
autoCutover: (this._databaseBackup.migrationMode === MigrationMode.OFFLINE ? true : false)
|
||||||
|
};
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
|
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
|
|||||||
selectDropDownIndex(this._azureAccountsDropdown, 0);
|
selectDropDownIndex(this._azureAccountsDropdown, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator(async pageChangeInfo => {
|
this.wizard.registerNavigationValidator(async pageChangeInfo => {
|
||||||
try {
|
try {
|
||||||
if (!this.migrationStateModel._azureAccount?.isStale) {
|
if (!this.migrationStateModel._azureAccount?.isStale) {
|
||||||
@@ -221,7 +221,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||||
|
|||||||
@@ -8,17 +8,25 @@ import * as vscode from 'vscode';
|
|||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { getStorageAccountAccessKeys } from '../api/azure';
|
import { getStorageAccountAccessKeys } from '../api/azure';
|
||||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||||
import { Blob, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||||
import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
|
import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
|
||||||
|
|
||||||
const WIZARD_TABLE_COLUMN_WIDTH = '200px';
|
const WIZARD_TABLE_COLUMN_WIDTH = '150px';
|
||||||
|
|
||||||
|
const blobResourceGroupErrorStrings = [constants.RESOURCE_GROUP_NOT_FOUND];
|
||||||
|
const blobStorageAccountErrorStrings = [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP];
|
||||||
|
const blobContainerErrorStrings = [constants.NO_BLOBCONTAINERS_FOUND, constants.SELECT_STORAGE_ACCOUNT];
|
||||||
|
const blobFileErrorStrings = [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLOB_CONTAINER];
|
||||||
|
|
||||||
export class DatabaseBackupPage extends MigrationWizardPage {
|
export class DatabaseBackupPage extends MigrationWizardPage {
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
|
|
||||||
|
private _networkShareButton!: azdata.RadioButtonComponent;
|
||||||
|
private _blobContainerButton!: azdata.RadioButtonComponent;
|
||||||
|
|
||||||
private _networkShareContainer!: azdata.FlexContainer;
|
private _networkShareContainer!: azdata.FlexContainer;
|
||||||
private _windowsUserAccountText!: azdata.InputBoxComponent;
|
private _windowsUserAccountText!: azdata.InputBoxComponent;
|
||||||
private _passwordText!: azdata.InputBoxComponent;
|
private _passwordText!: azdata.InputBoxComponent;
|
||||||
@@ -33,6 +41,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
private _blobContainerResourceGroupDropdowns!: azdata.DropDownComponent[];
|
private _blobContainerResourceGroupDropdowns!: azdata.DropDownComponent[];
|
||||||
private _blobContainerStorageAccountDropdowns!: azdata.DropDownComponent[];
|
private _blobContainerStorageAccountDropdowns!: azdata.DropDownComponent[];
|
||||||
private _blobContainerDropdowns!: azdata.DropDownComponent[];
|
private _blobContainerDropdowns!: azdata.DropDownComponent[];
|
||||||
|
private _blobContainerLastBackupFileDropdowns!: azdata.DropDownComponent[];
|
||||||
|
|
||||||
private _networkShareStorageAccountDetails!: azdata.FlexContainer;
|
private _networkShareStorageAccountDetails!: azdata.FlexContainer;
|
||||||
private _networkShareContainerSubscription!: azdata.InputBoxComponent;
|
private _networkShareContainerSubscription!: azdata.InputBoxComponent;
|
||||||
@@ -107,7 +116,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const networkShareButton = this._view.modelBuilder.radioButton()
|
this._networkShareButton = this._view.modelBuilder.radioButton()
|
||||||
.withProps({
|
.withProps({
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
|
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
|
||||||
@@ -116,13 +125,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(networkShareButton.onDidChangeCheckedState((e) => {
|
this._disposables.push(this._networkShareButton.onDidChangeCheckedState((e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.switchNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
|
this.switchNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const blobContainerButton = this._view.modelBuilder.radioButton()
|
this._blobContainerButton = this._view.modelBuilder.radioButton()
|
||||||
.withProps({
|
.withProps({
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
label: constants.DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL,
|
label: constants.DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL,
|
||||||
@@ -131,7 +140,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(blobContainerButton.onDidChangeCheckedState((e) => {
|
this._disposables.push(this._blobContainerButton.onDidChangeCheckedState((e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.switchNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
|
this.switchNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
|
||||||
}
|
}
|
||||||
@@ -140,8 +149,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
const flexContainer = this._view.modelBuilder.flexContainer().withItems(
|
const flexContainer = this._view.modelBuilder.flexContainer().withItems(
|
||||||
[
|
[
|
||||||
selectLocationText,
|
selectLocationText,
|
||||||
networkShareButton,
|
this._networkShareButton,
|
||||||
blobContainerButton
|
this._blobContainerButton
|
||||||
]
|
]
|
||||||
).withLayout({
|
).withLayout({
|
||||||
flexFlow: 'column'
|
flexFlow: 'column'
|
||||||
@@ -472,6 +481,14 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
headerCssStyles: headerCssStyles,
|
headerCssStyles: headerCssStyles,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: WIZARD_TABLE_COLUMN_WIDTH
|
width: WIZARD_TABLE_COLUMN_WIDTH
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyles,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: WIZARD_TABLE_COLUMN_WIDTH,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).component();
|
}).component();
|
||||||
@@ -640,9 +657,20 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
|
||||||
if (this.migrationStateModel.refreshDatabaseBackupPage) {
|
if (this.migrationStateModel.refreshDatabaseBackupPage) {
|
||||||
|
const isOfflineMigration = this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE;
|
||||||
|
const lastBackupFileColumnIndex = this._blobContainerTargetDatabaseNamesTable.columns.length - 1;
|
||||||
|
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
|
||||||
|
|
||||||
|
this._networkShareButton.checked = false;
|
||||||
|
this._networkTableContainer.display = 'none';
|
||||||
|
this._networkShareContainer.updateCssStyles({ 'display': 'none' });
|
||||||
|
|
||||||
|
this._blobContainerButton.checked = false;
|
||||||
|
this._blobTableContainer.display = 'none';
|
||||||
|
this._blobContainer.updateCssStyles({ 'display': 'none' });
|
||||||
|
|
||||||
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
|
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
|
||||||
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
|
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
|
||||||
const query = 'select SUSER_NAME()';
|
const query = 'select SUSER_NAME()';
|
||||||
@@ -658,6 +686,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
this._blobContainerResourceGroupDropdowns = [];
|
this._blobContainerResourceGroupDropdowns = [];
|
||||||
this._blobContainerStorageAccountDropdowns = [];
|
this._blobContainerStorageAccountDropdowns = [];
|
||||||
this._blobContainerDropdowns = [];
|
this._blobContainerDropdowns = [];
|
||||||
|
this._blobContainerLastBackupFileDropdowns = [];
|
||||||
|
|
||||||
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
|
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
|
||||||
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
|
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
|
||||||
@@ -722,48 +751,83 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
width: WIZARD_TABLE_COLUMN_WIDTH,
|
width: WIZARD_TABLE_COLUMN_WIDTH,
|
||||||
editable: true,
|
editable: true,
|
||||||
fireOnTextChange: true,
|
fireOnTextChange: true,
|
||||||
|
required: true,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
const blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown().withProps({
|
||||||
|
ariaLabel: constants.BLOB_CONTAINER_STORAGE_ACCOUNT,
|
||||||
|
width: WIZARD_TABLE_COLUMN_WIDTH,
|
||||||
|
editable: true,
|
||||||
|
fireOnTextChange: true,
|
||||||
|
required: true,
|
||||||
|
enabled: false,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const blobContainerDropdown = this._view.modelBuilder.dropDown().withProps({
|
||||||
|
ariaLabel: constants.BLOB_CONTAINER,
|
||||||
|
width: WIZARD_TABLE_COLUMN_WIDTH,
|
||||||
|
editable: true,
|
||||||
|
fireOnTextChange: true,
|
||||||
|
required: true,
|
||||||
|
enabled: false,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const blobContainerLastBackupFileDropdown = this._view.modelBuilder.dropDown().withProps({
|
||||||
|
ariaLabel: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
|
||||||
|
width: WIZARD_TABLE_COLUMN_WIDTH,
|
||||||
|
editable: true,
|
||||||
|
fireOnTextChange: true,
|
||||||
|
required: true,
|
||||||
|
enabled: false,
|
||||||
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(blobContainerResourceDropdown.onValueChanged(async (value) => {
|
this._disposables.push(blobContainerResourceDropdown.onValueChanged(async (value) => {
|
||||||
const selectedIndex = findDropDownItemIndex(blobContainerResourceDropdown, value);
|
const selectedIndex = findDropDownItemIndex(blobContainerResourceDropdown, value);
|
||||||
if (selectedIndex > -1 && value !== constants.RESOURCE_GROUP_NOT_FOUND) {
|
if (selectedIndex > -1 && !blobResourceGroupErrorStrings.includes(value)) {
|
||||||
this.migrationStateModel._databaseBackup.blobs[index].resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex);
|
this.migrationStateModel._databaseBackup.blobs[index].resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex);
|
||||||
|
await this.loadBlobStorageDropdown(index);
|
||||||
|
blobContainerStorageAccountDropdown.updateProperties({ enabled: true });
|
||||||
|
} else {
|
||||||
|
this.disableBlobTableDropdowns(index, constants.RESOURCE_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadblobStorageDropdown(index);
|
|
||||||
}));
|
}));
|
||||||
this._blobContainerResourceGroupDropdowns.push(blobContainerResourceDropdown);
|
this._blobContainerResourceGroupDropdowns.push(blobContainerResourceDropdown);
|
||||||
|
|
||||||
const blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown()
|
|
||||||
.withProps({
|
|
||||||
ariaLabel: constants.BLOB_CONTAINER_STORAGE_ACCOUNT,
|
|
||||||
width: WIZARD_TABLE_COLUMN_WIDTH,
|
|
||||||
editable: true,
|
|
||||||
fireOnTextChange: true,
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._disposables.push(blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
|
this._disposables.push(blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
|
||||||
const selectedIndex = findDropDownItemIndex(blobContainerStorageAccountDropdown, value);
|
const selectedIndex = findDropDownItemIndex(blobContainerStorageAccountDropdown, value);
|
||||||
if (selectedIndex > -1 && value !== constants.NO_STORAGE_ACCOUNT_FOUND) {
|
if (selectedIndex > -1 && !blobStorageAccountErrorStrings.includes(value)) {
|
||||||
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = this.migrationStateModel.getStorageAccount(selectedIndex);
|
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = this.migrationStateModel.getStorageAccount(selectedIndex);
|
||||||
|
await this.loadBlobContainerDropdown(index);
|
||||||
|
blobContainerDropdown.updateProperties({ enabled: true });
|
||||||
|
} else {
|
||||||
|
this.disableBlobTableDropdowns(index, constants.STORAGE_ACCOUNT);
|
||||||
}
|
}
|
||||||
await this.loadBlobContainerDropdown(index);
|
|
||||||
}));
|
}));
|
||||||
this._blobContainerStorageAccountDropdowns.push(blobContainerStorageAccountDropdown);
|
this._blobContainerStorageAccountDropdowns.push(blobContainerStorageAccountDropdown);
|
||||||
|
|
||||||
const blobContainerDropdown = this._view.modelBuilder.dropDown()
|
this._disposables.push(blobContainerDropdown.onValueChanged(async (value) => {
|
||||||
.withProps({
|
|
||||||
ariaLabel: constants.BLOB_CONTAINER,
|
|
||||||
width: WIZARD_TABLE_COLUMN_WIDTH,
|
|
||||||
editable: true,
|
|
||||||
fireOnTextChange: true,
|
|
||||||
}).component();
|
|
||||||
this._disposables.push(blobContainerDropdown.onValueChanged(value => {
|
|
||||||
const selectedIndex = findDropDownItemIndex(blobContainerDropdown, value);
|
const selectedIndex = findDropDownItemIndex(blobContainerDropdown, value);
|
||||||
if (selectedIndex > -1 && value !== constants.NO_BLOBCONTAINERS_FOUND) {
|
if (selectedIndex > -1 && !blobContainerErrorStrings.includes(value)) {
|
||||||
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = this.migrationStateModel.getBlobContainer(selectedIndex);
|
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = this.migrationStateModel.getBlobContainer(selectedIndex);
|
||||||
|
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
await this.loadBlobLastBackupFileDropdown(index);
|
||||||
|
blobContainerLastBackupFileDropdown.updateProperties({ enabled: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.disableBlobTableDropdowns(index, constants.BLOB_CONTAINER);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._blobContainerDropdowns.push(blobContainerDropdown);
|
this._blobContainerDropdowns.push(blobContainerDropdown);
|
||||||
|
|
||||||
|
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
this._disposables.push(blobContainerLastBackupFileDropdown.onValueChanged(value => {
|
||||||
|
const selectedIndex = findDropDownItemIndex(blobContainerLastBackupFileDropdown, value);
|
||||||
|
if (selectedIndex > -1 && !blobFileErrorStrings.includes(value)) {
|
||||||
|
this.migrationStateModel._databaseBackup.blobs[index].lastBackupFile = this.migrationStateModel.getBlobLastBackupFileName(selectedIndex);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this._blobContainerLastBackupFileDropdowns.push(blobContainerLastBackupFileDropdown);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -799,13 +863,17 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
targetRow.push({
|
targetRow.push({
|
||||||
value: this._blobContainerDropdowns[index]
|
value: this._blobContainerDropdowns[index]
|
||||||
});
|
});
|
||||||
|
targetRow.push({
|
||||||
|
value: this._blobContainerLastBackupFileDropdowns[index]
|
||||||
|
});
|
||||||
data.push(targetRow);
|
data.push(targetRow);
|
||||||
});
|
});
|
||||||
this._blobContainerTargetDatabaseNamesTable.dataValues = data;
|
await this._blobContainerTargetDatabaseNamesTable.setDataValues(data);
|
||||||
|
|
||||||
|
await this.getSubscriptionValues();
|
||||||
this.migrationStateModel.refreshDatabaseBackupPage = false;
|
this.migrationStateModel.refreshDatabaseBackupPage = false;
|
||||||
}
|
}
|
||||||
await this.getSubscriptionValues();
|
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
return true;
|
return true;
|
||||||
@@ -823,22 +891,30 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NetworkContainerType.BLOB_CONTAINER:
|
case NetworkContainerType.BLOB_CONTAINER:
|
||||||
this._blobContainerResourceGroupDropdowns.forEach(v => {
|
this._blobContainerResourceGroupDropdowns.forEach((v, index) => {
|
||||||
if ((<azdata.CategoryValue>v.value).displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
|
if (this.shouldDisplayBlobDropdownError(v, [constants.RESOURCE_GROUP_NOT_FOUND])) {
|
||||||
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
|
errors.push(constants.INVALID_BLOB_RESOURCE_GROUP_ERROR(this.migrationStateModel._migrationDbs[index]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._blobContainerStorageAccountDropdowns.forEach(v => {
|
this._blobContainerStorageAccountDropdowns.forEach((v, index) => {
|
||||||
if ((<azdata.CategoryValue>v.value).displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
|
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP])) {
|
||||||
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
|
errors.push(constants.INVALID_BLOB_STORAGE_ACCOUNT_ERROR(this.migrationStateModel._migrationDbs[index]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._blobContainerDropdowns.forEach(v => {
|
this._blobContainerDropdowns.forEach((v, index) => {
|
||||||
if ((<azdata.CategoryValue>v.value).displayName === constants.NO_BLOBCONTAINERS_FOUND) {
|
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBCONTAINERS_FOUND, constants.SELECT_STORAGE_ACCOUNT])) {
|
||||||
errors.push(constants.INVALID_BLOBCONTAINER_ERROR);
|
errors.push(constants.INVALID_BLOB_CONTAINER_ERROR(this.migrationStateModel._migrationDbs[index]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
this._blobContainerLastBackupFileDropdowns.forEach((v, index) => {
|
||||||
|
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLOB_CONTAINER])) {
|
||||||
|
errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._migrationDbs[index]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
const duplicates: Map<string, number[]> = new Map();
|
const duplicates: Map<string, number[]> = new Map();
|
||||||
for (let i = 0; i < this.migrationStateModel._targetDatabaseNames.length; i++) {
|
for (let i = 0; i < this.migrationStateModel._targetDatabaseNames.length; i++) {
|
||||||
@@ -877,26 +953,27 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
try {
|
try {
|
||||||
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
|
if (pageChangeInfo.newPage > pageChangeInfo.lastPage) {
|
||||||
case NetworkContainerType.BLOB_CONTAINER:
|
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
|
||||||
for (let i = 0; i < this.migrationStateModel._databaseBackup.blobs.length; i++) {
|
case NetworkContainerType.BLOB_CONTAINER:
|
||||||
const storageAccount = this.migrationStateModel._databaseBackup.blobs[i].storageAccount;
|
for (let i = 0; i < this.migrationStateModel._databaseBackup.blobs.length; i++) {
|
||||||
this.migrationStateModel._databaseBackup.blobs[i].storageKey = (await getStorageAccountAccessKeys(
|
const storageAccount = this.migrationStateModel._databaseBackup.blobs[i].storageAccount;
|
||||||
|
this.migrationStateModel._databaseBackup.blobs[i].storageKey = (await getStorageAccountAccessKeys(
|
||||||
|
this.migrationStateModel._azureAccount,
|
||||||
|
this.migrationStateModel._databaseBackup.subscription,
|
||||||
|
storageAccount)).keyName1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetworkContainerType.NETWORK_SHARE:
|
||||||
|
const storageAccount = this.migrationStateModel._databaseBackup.networkShare.storageAccount;
|
||||||
|
this.migrationStateModel._databaseBackup.networkShare.storageKey = (await getStorageAccountAccessKeys(
|
||||||
this.migrationStateModel._azureAccount,
|
this.migrationStateModel._azureAccount,
|
||||||
this.migrationStateModel._databaseBackup.subscription,
|
this.migrationStateModel._databaseBackup.subscription,
|
||||||
storageAccount)).keyName1;
|
storageAccount)).keyName1;
|
||||||
}
|
break;
|
||||||
break;
|
}
|
||||||
case NetworkContainerType.NETWORK_SHARE:
|
|
||||||
const storageAccount = this.migrationStateModel._databaseBackup.networkShare.storageAccount;
|
|
||||||
|
|
||||||
this.migrationStateModel._databaseBackup.networkShare.storageKey = (await getStorageAccountAccessKeys(
|
|
||||||
this.migrationStateModel._azureAccount,
|
|
||||||
this.migrationStateModel._databaseBackup.subscription,
|
|
||||||
storageAccount)).keyName1;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
@@ -924,7 +1001,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
|
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
|
||||||
|
|
||||||
//Preserving the database Names between the 2 tables.
|
//Preserving the database Names between the 2 tables.
|
||||||
this.migrationStateModel._targetDatabaseNames.forEach((v, index) => {
|
this.migrationStateModel._targetDatabaseNames?.forEach((v, index) => {
|
||||||
this._networkShareTargetDatabaseNames[index].value = v;
|
this._networkShareTargetDatabaseNames[index].value = v;
|
||||||
this._blobContainerTargetDatabaseNames[index].value = v;
|
this._blobContainerTargetDatabaseNames[index].value = v;
|
||||||
});
|
});
|
||||||
@@ -961,6 +1038,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
await this._blobContainerResourceGroupDropdowns[i].validate();
|
await this._blobContainerResourceGroupDropdowns[i].validate();
|
||||||
await this._blobContainerStorageAccountDropdowns[i].validate();
|
await this._blobContainerStorageAccountDropdowns[i].validate();
|
||||||
await this._blobContainerDropdowns[i].validate();
|
await this._blobContainerDropdowns[i].validate();
|
||||||
|
|
||||||
|
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
await this._blobContainerLastBackupFileDropdowns[i]?.validate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,7 +1057,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
|
|
||||||
|
|
||||||
this.loadNetworkStorageResourceGroup();
|
this.loadNetworkStorageResourceGroup();
|
||||||
this.loadblobResourceGroup();
|
this.loadBlobResourceGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadNetworkStorageResourceGroup(): Promise<void> {
|
private async loadNetworkStorageResourceGroup(): Promise<void> {
|
||||||
@@ -1004,7 +1085,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadblobResourceGroup(): Promise<void> {
|
private async loadBlobResourceGroup(): Promise<void> {
|
||||||
this._blobContainerResourceGroupDropdowns.forEach(v => v.loading = true);
|
this._blobContainerResourceGroupDropdowns.forEach(v => v.loading = true);
|
||||||
try {
|
try {
|
||||||
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
|
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
|
||||||
@@ -1019,7 +1100,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadblobStorageDropdown(index: number): Promise<void> {
|
private async loadBlobStorageDropdown(index: number): Promise<void> {
|
||||||
this._blobContainerStorageAccountDropdowns[index].loading = true;
|
this._blobContainerStorageAccountDropdowns[index].loading = true;
|
||||||
try {
|
try {
|
||||||
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].resourceGroup);
|
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].resourceGroup);
|
||||||
@@ -1044,4 +1125,41 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async loadBlobLastBackupFileDropdown(index: number): Promise<void> {
|
||||||
|
this._blobContainerLastBackupFileDropdowns[index].loading = true;
|
||||||
|
try {
|
||||||
|
const blobLastBackupFileValues = await this.migrationStateModel.getBlobLastBackupFileNameValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].storageAccount, this.migrationStateModel._databaseBackup.blobs[index].blobContainer);
|
||||||
|
this._blobContainerLastBackupFileDropdowns[index].values = blobLastBackupFileValues;
|
||||||
|
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
this._blobContainerLastBackupFileDropdowns[index].loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldDisplayBlobDropdownError(v: azdata.DropDownComponent, errorStrings: string[]) {
|
||||||
|
return v.value === undefined || errorStrings.includes((<azdata.CategoryValue>v.value)?.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableBlobTableDropdowns(rowIndex: number, columnName: string): void {
|
||||||
|
const dropdownProps = { enabled: false, loading: false };
|
||||||
|
const createDropdownValuesWithPrereq = (displayName: string, name: string = '') => [{ displayName, name }];
|
||||||
|
|
||||||
|
if (this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE) {
|
||||||
|
this._blobContainerLastBackupFileDropdowns[rowIndex].values = createDropdownValuesWithPrereq(constants.SELECT_BLOB_CONTAINER);
|
||||||
|
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[rowIndex], 0);
|
||||||
|
this._blobContainerLastBackupFileDropdowns[rowIndex]?.updateProperties(dropdownProps);
|
||||||
|
}
|
||||||
|
if (columnName === constants.BLOB_CONTAINER) { return; }
|
||||||
|
|
||||||
|
this._blobContainerDropdowns[rowIndex].values = createDropdownValuesWithPrereq(constants.SELECT_STORAGE_ACCOUNT);
|
||||||
|
selectDropDownIndex(this._blobContainerDropdowns[rowIndex], 0);
|
||||||
|
this._blobContainerDropdowns[rowIndex].updateProperties(dropdownProps);
|
||||||
|
if (columnName === constants.STORAGE_ACCOUNT) { return; }
|
||||||
|
|
||||||
|
this._blobContainerStorageAccountDropdowns[rowIndex].values = createDropdownValuesWithPrereq(constants.SELECT_RESOURCE_GROUP);
|
||||||
|
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[rowIndex], 0);
|
||||||
|
this._blobContainerStorageAccountDropdowns[rowIndex].updateProperties(dropdownProps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
await view.initializeModel(this._form.component());
|
await view.initializeModel(this._form.component());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
|
||||||
this._subscription.value = this.migrationStateModel._targetSubscription.name;
|
this._subscription.value = this.migrationStateModel._targetSubscription.name;
|
||||||
this._location.value = await getLocationDisplayName(this.migrationStateModel._targetServerInstance.location);
|
this._location.value = await getLocationDisplayName(this.migrationStateModel._targetServerInstance.location);
|
||||||
@@ -137,7 +137,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as constants from '../constants/strings';
|
|||||||
|
|
||||||
export class MigrationModePage extends MigrationWizardPage {
|
export class MigrationModePage extends MigrationWizardPage {
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
|
private originalMigrationMode!: MigrationMode;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
||||||
@@ -34,12 +35,17 @@ export class MigrationModePage extends MigrationWizardPage {
|
|||||||
await view.initializeModel(form.component());
|
await view.initializeModel(form.component());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
this.originalMigrationMode = this.migrationStateModel._databaseBackup.migrationMode;
|
||||||
this.wizard.registerNavigationValidator((e) => {
|
this.wizard.registerNavigationValidator((e) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
|
||||||
|
this.migrationStateModel.refreshDatabaseBackupPage = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.wizard.registerNavigationValidator((e) => {
|
this.wizard.registerNavigationValidator((e) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -68,8 +74,6 @@ export class MigrationModePage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
|
||||||
|
|
||||||
this._disposables.push(onlineButton.onDidChangeCheckedState((e) => {
|
this._disposables.push(onlineButton.onDidChangeCheckedState((e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
||||||
@@ -96,9 +100,7 @@ export class MigrationModePage extends MigrationWizardPage {
|
|||||||
|
|
||||||
this._disposables.push(offlineButton.onDidChangeCheckedState((e) => {
|
this._disposables.push(offlineButton.onDidChangeCheckedState((e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
vscode.window.showInformationMessage('Feature coming soon');
|
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
|
||||||
onlineButton.checked = true;
|
|
||||||
//this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE; TODO: Enable when offline mode is supported.
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
this.wizard.message = {
|
this.wizard.message = {
|
||||||
@@ -552,7 +552,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this.wizard.nextButton.enabled = true;
|
this.wizard.nextButton.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.eventListener?.dispose();
|
this.eventListener?.dispose();
|
||||||
this.wizard.message = {
|
this.wizard.message = {
|
||||||
text: '',
|
text: '',
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ export class SqlSourceConfigurationPage extends MigrationWizardPage {
|
|||||||
await view.initializeModel(form.component());
|
await view.initializeModel(form.component());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -173,12 +173,12 @@ export class SubscriptionSelectionPage extends MigrationWizardPage {
|
|||||||
selectDropDownIndex(this.productDropDown!.component, 0);
|
selectDropDownIndex(this.productDropDown!.component, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.disposables.push(this.migrationStateModel.stateChangeEvent(async (e) => this.onStateChangeEvent(e)));
|
this.disposables.push(this.migrationStateModel.stateChangeEvent(async (e) => this.onStateChangeEvent(e)));
|
||||||
await this.populateAccountValues();
|
await this.populateAccountValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.disposables.forEach(d => { try { d.dispose(); } catch { } });
|
this.disposables.forEach(d => { try { d.dispose(); } catch { } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class SummaryPage extends MigrationWizardPage {
|
|||||||
await view.initializeModel(form.component());
|
await view.initializeModel(form.component());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
|
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
|
||||||
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({
|
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({
|
||||||
url: '',
|
url: '',
|
||||||
@@ -120,7 +120,7 @@ export class SummaryPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this._flexContainer.clearItems();
|
this._flexContainer.clearItems();
|
||||||
this.wizard.registerNavigationValidator(async (pageChangeInfo) => {
|
this.wizard.registerNavigationValidator(async (pageChangeInfo) => {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ export class WizardController {
|
|||||||
const newPage = pageChangeInfo.newPage;
|
const newPage = pageChangeInfo.newPage;
|
||||||
const lastPage = pageChangeInfo.lastPage;
|
const lastPage = pageChangeInfo.lastPage;
|
||||||
this.sendPageButtonClickEvent(pageChangeInfo).catch(e => console.log(e));
|
this.sendPageButtonClickEvent(pageChangeInfo).catch(e => console.log(e));
|
||||||
await pages[lastPage]?.onPageLeave();
|
await pages[lastPage]?.onPageLeave(pageChangeInfo);
|
||||||
await pages[newPage]?.onPageEnter();
|
await pages[newPage]?.onPageEnter(pageChangeInfo);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._wizardObject.registerNavigationValidator(async validator => {
|
this._wizardObject.registerNavigationValidator(async validator => {
|
||||||
@@ -82,7 +82,9 @@ export class WizardController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(wizardSetupPromises);
|
await Promise.all(wizardSetupPromises);
|
||||||
await pages[0].onPageEnter();
|
this.extensionContext.subscriptions.push(this._wizardObject.onPageChanged(async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => {
|
||||||
|
await pages[0].onPageEnter(pageChangeInfo);
|
||||||
|
}));
|
||||||
|
|
||||||
this.extensionContext.subscriptions.push(this._wizardObject.doneButton.onClick(async (e) => {
|
this.extensionContext.subscriptions.push(this._wizardObject.doneButton.onClick(async (e) => {
|
||||||
await stateModel.startMigration();
|
await stateModel.startMigration();
|
||||||
|
|||||||
Reference in New Issue
Block a user