mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 17:22:59 -05:00
Adding migration status and cutover to extension (#14482)
This commit is contained in:
@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azurecore from 'azurecore';
|
||||
import { azureResource } from 'azureResource';
|
||||
import * as loc from '../constants/strings';
|
||||
|
||||
async function getAzureCoreAPI(): Promise<azurecore.IExtension> {
|
||||
const api = (await vscode.extensions.getExtension(azurecore.extension.name)?.activate()) as azurecore.IExtension;
|
||||
@@ -82,7 +83,7 @@ export async function getBlobContainers(account: azdata.Account, subscription: S
|
||||
return blobContainers!;
|
||||
}
|
||||
|
||||
export async function getMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<MigrationController> {
|
||||
export async function getMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<SqlMigrationController> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://${regionName}.management.azure.com`;
|
||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}?api-version=2020-09-01-preview`;
|
||||
@@ -93,7 +94,7 @@ export async function getMigrationController(account: azdata.Account, subscripti
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise<MigrationController[]> {
|
||||
export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise<SqlMigrationController[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://${regionName}.management.azure.com`;
|
||||
const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/Controllers?api-version=2020-09-01-preview`;
|
||||
@@ -101,10 +102,11 @@ export async function getMigrationControllers(account: azdata.Account, subscript
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
}
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function createMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<MigrationController> {
|
||||
export async function createMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<SqlMigrationController> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://${regionName}.management.azure.com`;
|
||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}?api-version=2020-09-01-preview`;
|
||||
@@ -157,10 +159,10 @@ export async function getMigrationControllerMonitoringData(account: azdata.Accou
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, managedInstance: string, migrationControllerName: string, requestBody: StartDatabaseMigrationRequest): Promise<StartDatabaseMigrationResponse> {
|
||||
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, managedInstance: string, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest): Promise<StartDatabaseMigrationResponse> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://${regionName}.management.azure.com`;
|
||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/managedInstances/${managedInstance}/providers/Microsoft.DataMigration/databaseMigrations/${migrationControllerName}?api-version=2020-09-01-preview`;
|
||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/managedInstances/${managedInstance}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=2020-09-01-preview`;
|
||||
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host);
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
@@ -171,7 +173,18 @@ export async function startDatabaseMigration(account: azdata.Account, subscripti
|
||||
};
|
||||
}
|
||||
|
||||
export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<any> {
|
||||
export async function getDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, migrationId: string): Promise<DatabaseMigration> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://${regionName}.management.azure.com`;
|
||||
const path = `${migrationId}?api-version=2020-09-01-preview`;
|
||||
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
}
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<DatabaseMigration> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://eastus2euap.management.azure.com`;
|
||||
const path = `${migration.id}?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`;
|
||||
@@ -179,9 +192,29 @@ export async function getMigrationStatus(account: azdata.Account, subscription:
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
}
|
||||
return {
|
||||
result: response.response.data
|
||||
};
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function listMigrationsByController(account: azdata.Account, subscription: Subscription, controller: SqlMigrationController): Promise<DatabaseMigration[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://eastus2euap.management.azure.com`;
|
||||
const path = `${controller.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`;
|
||||
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
}
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function startMigrationCutover(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration): Promise<any> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const host = `https://eastus2euap.management.azure.com`;
|
||||
const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cutover?api-version=2020-09-01-preview`;
|
||||
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host);
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.toString());
|
||||
}
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,13 +223,13 @@ export async function getMigrationStatus(account: azdata.Account, subscription:
|
||||
export function getMigrationControllerRegions(): azdata.CategoryValue[] {
|
||||
return [
|
||||
{
|
||||
displayName: 'East US EUAP',
|
||||
displayName: loc.EASTUS2EUAP,
|
||||
name: 'eastus2euap'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription;
|
||||
type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription | SqlMigrationController;
|
||||
function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void {
|
||||
if (!resourceArray) {
|
||||
return;
|
||||
@@ -222,7 +255,7 @@ export interface MigrationControllerProperties {
|
||||
isProvisioned?: boolean;
|
||||
}
|
||||
|
||||
export interface MigrationController {
|
||||
export interface SqlMigrationController {
|
||||
properties: MigrationControllerProperties;
|
||||
location: string;
|
||||
id: string;
|
||||
@@ -285,19 +318,105 @@ export interface StartDatabaseMigrationRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DatabaseMigration {
|
||||
properties: {
|
||||
name: string,
|
||||
provisioningState: string,
|
||||
sourceDatabaseName: string,
|
||||
migrationOperationId: string,
|
||||
},
|
||||
id: string,
|
||||
name: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface StartDatabaseMigrationResponse {
|
||||
status: number,
|
||||
databaseMigration: DatabaseMigration
|
||||
}
|
||||
|
||||
export interface DatabaseMigration {
|
||||
properties: DatabaseMigrationProperties;
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
export interface DatabaseMigrationProperties {
|
||||
scope: string;
|
||||
provisioningState: string;
|
||||
migrationStatus: string;
|
||||
migrationStatusDetails?: MigrationStatusDetails;
|
||||
sourceSqlConnection: SqlConnectionInfo;
|
||||
sourceDatabaseName: string;
|
||||
targetDatabaseCollation: string;
|
||||
migrationController: string;
|
||||
migrationOperationId: string;
|
||||
backupConfiguration: BackupConfiguration;
|
||||
autoCutoverConfiguration: AutoCutoverConfiguration;
|
||||
migrationFailureError: ErrorInfo;
|
||||
}
|
||||
export interface MigrationStatusDetails {
|
||||
migrationState: string;
|
||||
startedOn: string;
|
||||
endedOn: string;
|
||||
fullBackupSetInfo: BackupSetInfo;
|
||||
lastRestoredBackupSetInfo: BackupSetInfo;
|
||||
activeBackupSets: BackupSetInfo[];
|
||||
blobContainerName: string;
|
||||
isFullBackupRestored: boolean;
|
||||
restoreBlockingReason: string;
|
||||
fileUploadBlockingErrors: string[];
|
||||
currentRestoringFileName: string;
|
||||
lastRestoredFilename: string;
|
||||
}
|
||||
|
||||
export interface SqlConnectionInfo {
|
||||
dataSource: string;
|
||||
authentication: string;
|
||||
username: string;
|
||||
password: string;
|
||||
encryptConnection: string;
|
||||
trustServerCertificate: string;
|
||||
}
|
||||
|
||||
export interface BackupConfiguration {
|
||||
sourceLocation: SourceLocation;
|
||||
targetLocation: TargetLocation;
|
||||
}
|
||||
|
||||
export interface AutoCutoverConfiguration {
|
||||
lastBackupName: string;
|
||||
}
|
||||
|
||||
export interface ErrorInfo {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface BackupSetInfo {
|
||||
backupSetId: string;
|
||||
firstLSN: string;
|
||||
lastLSN: string;
|
||||
backupType: string;
|
||||
listOfBackupFiles: BackupFileInfo[];
|
||||
backupStartDate: string;
|
||||
backupFinishDate: string;
|
||||
isBackupRestored: boolean;
|
||||
backupSize: number;
|
||||
compressedBackupSize: number;
|
||||
}
|
||||
|
||||
export interface SourceLocation {
|
||||
fileShare: DatabaseMigrationFileShare;
|
||||
azureBlob: DatabaseMigrationAzureBlob;
|
||||
}
|
||||
|
||||
export interface TargetLocation {
|
||||
storageAccountResourceId: string;
|
||||
accountKey: string;
|
||||
}
|
||||
|
||||
export interface BackupFileInfo {
|
||||
fileName: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface DatabaseMigrationFileShare {
|
||||
path: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface DatabaseMigrationAzureBlob {
|
||||
storageAccountResourceId: string;
|
||||
accountKey: string;
|
||||
blobContainerName: string;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ export interface IconPath {
|
||||
export class IconPathHelper {
|
||||
public static copy: IconPath;
|
||||
public static refresh: IconPath;
|
||||
public static sqlMiImportHelpThumbnail: IconPath;
|
||||
public static sqlVmImportHelpThumbnail: IconPath;
|
||||
public static migrationDashboardHeaderBackground: IconPath;
|
||||
public static cutover: IconPath;
|
||||
public static sqlMigrationLogo: IconPath;
|
||||
public static sqlMiVideoThumbnail: IconPath;
|
||||
public static sqlVmVideoThumbnail: IconPath;
|
||||
public static migrationDashboardHeaderBackground: IconPath;
|
||||
public static inProgressMigration: IconPath;
|
||||
public static completedMigration: IconPath;
|
||||
public static notStartedMigration: IconPath;
|
||||
|
||||
public static setExtensionContext(context: vscode.ExtensionContext) {
|
||||
IconPathHelper.copy = {
|
||||
@@ -27,13 +31,13 @@ export class IconPathHelper {
|
||||
light: context.asAbsolutePath('images/refresh.svg'),
|
||||
dark: context.asAbsolutePath('images/refresh.svg')
|
||||
};
|
||||
IconPathHelper.sqlMiImportHelpThumbnail = {
|
||||
light: context.asAbsolutePath('images/sqlMiImportHelpThumbnail.svg'),
|
||||
dark: context.asAbsolutePath('images/sqlMiImportHelpThumbnail.svg')
|
||||
IconPathHelper.sqlMiVideoThumbnail = {
|
||||
light: context.asAbsolutePath('images/sqlMiVideoThumbnail.svg'),
|
||||
dark: context.asAbsolutePath('images/sqlMiVideoThumbnail.svg')
|
||||
};
|
||||
IconPathHelper.sqlVmImportHelpThumbnail = {
|
||||
light: context.asAbsolutePath('images/sqlVmImportHelpThumbnail.svg'),
|
||||
dark: context.asAbsolutePath('images/sqlVmImportHelpThumbnail.svg')
|
||||
IconPathHelper.sqlVmVideoThumbnail = {
|
||||
light: context.asAbsolutePath('images/sqlVmVideoThumbnail.svg'),
|
||||
dark: context.asAbsolutePath('images/sqlVmVideoThumbnail.svg')
|
||||
};
|
||||
IconPathHelper.migrationDashboardHeaderBackground = {
|
||||
light: context.asAbsolutePath('images/background.svg'),
|
||||
@@ -43,5 +47,21 @@ export class IconPathHelper {
|
||||
light: context.asAbsolutePath('images/migration.svg'),
|
||||
dark: context.asAbsolutePath('images/migration.svg')
|
||||
};
|
||||
IconPathHelper.inProgressMigration = {
|
||||
light: context.asAbsolutePath('images/inProgress.svg'),
|
||||
dark: context.asAbsolutePath('images/inProgress.svg')
|
||||
};
|
||||
IconPathHelper.completedMigration = {
|
||||
light: context.asAbsolutePath('images/succeeded.svg'),
|
||||
dark: context.asAbsolutePath('images/succeeded.svg')
|
||||
};
|
||||
IconPathHelper.notStartedMigration = {
|
||||
light: context.asAbsolutePath('images/notStarted.svg'),
|
||||
dark: context.asAbsolutePath('images/notStarted.svg')
|
||||
};
|
||||
IconPathHelper.cutover = {
|
||||
light: context.asAbsolutePath('images/cutover.svg'),
|
||||
dark: context.asAbsolutePath('images/cutover.svg')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as loc from '../models/strings';
|
||||
import * as loc from './strings';
|
||||
|
||||
export class NotebookPathHelper {
|
||||
private static context: vscode.ExtensionContext;
|
||||
|
||||
@@ -192,4 +192,52 @@ export const PRE_REQ_TITLE = localize('sql.migration.pre.req.title', "Things you
|
||||
export const PRE_REQ_1 = localize('sql.migration.pre.req.1', "Azure account details");
|
||||
export const PRE_REQ_2 = localize('sql.migration.pre.req.2', "Azure SQL Managed Instance or SQL Server on Azure Virtual Machine");
|
||||
export const PRE_REQ_3 = localize('sql.migration.pre.req.3', "Backup location details");
|
||||
export const MIGRATION_IN_PROGRESS = localize('sql.migration.migration.in.progress', "Migration in progress");
|
||||
export const LOG_SHIPPING_IN_PROGRESS = localize('sql.migration.log.shipping.in.progress', "Log shipping in progress");
|
||||
export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed', "Migration completed");
|
||||
export const SUCCESSFULLY_MIGRATED_TO_AZURE_SQL = localize('sql.migration.successfully.migrated.to.azure.sql', "Successfully migrated to Azure SQL");
|
||||
export const MIGRATION_NOT_STARTED = localize('sql.migration.migration.not.started', "Migration not started");
|
||||
export const CHOOSE_TO_MIGRATE_TO_AZURE_SQL = localize('sql.migration.choose.to.migrate.to.azure.sql', "Choose to migrate to Azure SQL");
|
||||
|
||||
|
||||
// Azure APIs
|
||||
export const EASTUS2EUAP = localize('sql.migration.eastus2euap', 'East US 2 EUAP');
|
||||
|
||||
|
||||
//Migration cutover dialog
|
||||
export const MIGRATION_CUTOVER = localize('sql.migration.cutover', "Migration cutover");
|
||||
export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server");
|
||||
export const SOURCE_VERSION = localize('sql.migration.source.version', "Source version");
|
||||
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
||||
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
||||
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration status");
|
||||
export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files(s)");
|
||||
export const LAST_APPLIED_LSN = localize('sql.migration.last.applied.lsn', "Last applied LSN");
|
||||
export const LAST_APPLIED_BACKUP_FILES = localize('sql.migration.last.applied.backup.files', "Last applied backup file(s)");
|
||||
export const LAST_APPLIED_BACKUP_FILES_TAKEN_ON = localize('sql.migration.last.applied.files.taken.on', "Last applied backup file(s) taken on");
|
||||
export const ACTIVE_BACKUP_FILES = localize('sql.migration.active.backup.files', "Active Backup file(s)");
|
||||
export const STATUS = localize('sql.migration.status', "Status");
|
||||
export const BACKUP_START_TIME = localize('sql.migration.backup.start.time', "Backup start time");
|
||||
export const FIRST_LSN = localize('sql.migration.first.lsn', "First LSN");
|
||||
export const LAST_LSN = localize('sql.migration.last.LSN', "Last LSN");
|
||||
export const CANNOT_START_CUTOVER_ERROR = localize('sql.migration.cannot.start.cutover.error', "Cannot start the cutover process until all the migrations are done. Click refresh to fetch the latest file status");
|
||||
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Database Managed Instance");
|
||||
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE = localize('sql.migration.azure.sql.database.virtual.machine', "Azure SQL Database Virtual Machine");
|
||||
|
||||
export function ACTIVE_BACKUP_FILES_ITEMS(fileCount: number) {
|
||||
if (fileCount === 1) {
|
||||
return localize('sql.migration.active.backup.files.items', "Active Backup files (1 item)");
|
||||
} else {
|
||||
return localize('sql.migration.active.backup.files.multiple.items', "Active Backup files ({0} items)", fileCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Migration status dialog
|
||||
export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations");
|
||||
export const ONLINE = localize('sql.migration.online', "Online");
|
||||
export const DATABASE = localize('sql.migration.database', "Database");
|
||||
export const TARGET_AZURE_SQL_INSTANCE_NAME = localize('sql.migration.target.azure.sql.instance.name', "Target Azure SQL Instance Name");
|
||||
export const CUTOVER_TYPE = localize('sql.migration.cutover.type', "Cutover type");
|
||||
export const START_TIME = localize('sql.migration.start.time', "Start Time");
|
||||
export const FINISH_TIME = localize('sql.migration.finish.time', "Finish Time");
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||
import * as loc from '../models/strings';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { MigrationContext, MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||
import * as loc from '../constants/strings';
|
||||
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { getDatabaseMigration } from '../api/azure';
|
||||
import { MigrationStatusDialog } from '../dialog/migrationStatus/migrationStatusDialog';
|
||||
import { MigrationCategory } from '../dialog/migrationStatus/migrationStatusDialogModel';
|
||||
|
||||
interface IActionMetadata {
|
||||
title?: string,
|
||||
@@ -22,6 +25,7 @@ const maxWidth = 800;
|
||||
export class DashboardWidget {
|
||||
|
||||
private _migrationStatusCardsContainer!: azdata.FlexContainer;
|
||||
private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent;
|
||||
private _view!: azdata.ModelView;
|
||||
|
||||
constructor() {
|
||||
@@ -30,47 +34,36 @@ export class DashboardWidget {
|
||||
public register(): void {
|
||||
azdata.ui.registerModelViewProvider('migration.dashboard', async (view) => {
|
||||
this._view = view;
|
||||
|
||||
const container = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
|
||||
const header = this.createHeader(view);
|
||||
|
||||
const tasksContainer = await this.createTasks(view);
|
||||
|
||||
container.addItem(header, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(<string>IconPathHelper.migrationDashboardHeaderBackground.light)})`,
|
||||
'width': '1100px',
|
||||
'height': '300px',
|
||||
'width': '870px',
|
||||
'height': '260px',
|
||||
'background-size': '100%',
|
||||
}
|
||||
});
|
||||
|
||||
const tasksContainer = await this.createTasks(view);
|
||||
header.addItem(tasksContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '150px',
|
||||
}
|
||||
});
|
||||
|
||||
header.addItem(await this.createFooter(view), {
|
||||
container.addItem(await this.createFooter(view), {
|
||||
CSSStyles: {
|
||||
'margin-top': '20px'
|
||||
}
|
||||
});
|
||||
|
||||
const mainContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}).component();
|
||||
mainContainer.addItem(container, {
|
||||
CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' }
|
||||
});
|
||||
await view.initializeModel(mainContainer);
|
||||
await view.initializeModel(container);
|
||||
|
||||
this.refreshMigrations();
|
||||
});
|
||||
@@ -85,12 +78,14 @@ export class DashboardWidget {
|
||||
value: loc.DASHBOARD_TITLE,
|
||||
CSSStyles: {
|
||||
'font-size': '36px',
|
||||
'margin-bottom': '5px',
|
||||
}
|
||||
}).component();
|
||||
const descComponent = view.modelBuilder.text().withProps({
|
||||
value: loc.DASHBOARD_DESCRIPTION,
|
||||
CSSStyles: {
|
||||
'font-size': '12px',
|
||||
'margin-top': '10px',
|
||||
}
|
||||
}).component();
|
||||
header.addItems([titleComponent, descComponent], {
|
||||
@@ -99,7 +94,6 @@ export class DashboardWidget {
|
||||
'padding-left': '20px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
@@ -135,7 +129,9 @@ export class DashboardWidget {
|
||||
const preRequisiteListElement = view.modelBuilder.text().withProps({
|
||||
value: points,
|
||||
CSSStyles: {
|
||||
'padding-left': '15px'
|
||||
'padding-left': '15px',
|
||||
'margin-bottom': '5px',
|
||||
'margin-top': '10px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
@@ -160,7 +156,6 @@ export class DashboardWidget {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
tasksContainer.addItem(migrateButton, {
|
||||
CSSStyles: {
|
||||
'margin-top': '20px',
|
||||
@@ -199,19 +194,172 @@ export class DashboardWidget {
|
||||
}
|
||||
|
||||
private async refreshMigrations(): Promise<void> {
|
||||
this._migrationStatusCardLoadingContainer.loading = true;
|
||||
this._migrationStatusCardsContainer.clearItems();
|
||||
const currentConnection = (await azdata.connection.getCurrentConnection());
|
||||
const getMigrations = MigrationLocalStorage.getMigrations(currentConnection);
|
||||
getMigrations.forEach((migration) => {
|
||||
const button = this._view.modelBuilder.button().withProps({
|
||||
label: `Migration to ${migration.targetManagedInstance.name} using controller ${migration.migrationContext.name}`
|
||||
}).component();
|
||||
button.onDidClick(async (e) => {
|
||||
try {
|
||||
const migrationStatus = await this.getMigrations();
|
||||
|
||||
const inProgressMigrations = migrationStatus.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'InProgress' || status === 'Creating' || status === 'Completing';
|
||||
});
|
||||
const inProgressMigrationButton = this.createStatusCard(
|
||||
IconPathHelper.inProgressMigration,
|
||||
loc.MIGRATION_IN_PROGRESS,
|
||||
loc.LOG_SHIPPING_IN_PROGRESS,
|
||||
inProgressMigrations.length
|
||||
);
|
||||
inProgressMigrationButton.onDidClick((e) => {
|
||||
const dialog = new MigrationStatusDialog(migrationStatus, MigrationCategory.ONGOING);
|
||||
dialog.initialize();
|
||||
});
|
||||
this._migrationStatusCardsContainer.addItem(inProgressMigrationButton);
|
||||
|
||||
const successfulMigration = migrationStatus.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'Succeeded';
|
||||
});
|
||||
const successfulMigrationButton = this.createStatusCard(
|
||||
IconPathHelper.completedMigration,
|
||||
loc.MIGRATION_COMPLETED,
|
||||
loc.SUCCESSFULLY_MIGRATED_TO_AZURE_SQL,
|
||||
successfulMigration.length
|
||||
);
|
||||
successfulMigrationButton.onDidClick((e) => {
|
||||
const dialog = new MigrationStatusDialog(migrationStatus, MigrationCategory.SUCCEEDED);
|
||||
dialog.initialize();
|
||||
});
|
||||
this._migrationStatusCardsContainer.addItem(
|
||||
button
|
||||
successfulMigrationButton
|
||||
);
|
||||
|
||||
const currentConnection = (await azdata.connection.getCurrentConnection());
|
||||
const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection);
|
||||
const migrationDatabases = new Set(
|
||||
localMigrations.filter((value) => {
|
||||
|
||||
}).map((value) => {
|
||||
return value.migrationContext.properties.sourceDatabaseName;
|
||||
}));
|
||||
const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId);
|
||||
const notStartedMigrationCard = this.createStatusCard(
|
||||
IconPathHelper.notStartedMigration,
|
||||
loc.MIGRATION_NOT_STARTED,
|
||||
loc.CHOOSE_TO_MIGRATE_TO_AZURE_SQL,
|
||||
serverDatabases.length - migrationDatabases.size
|
||||
);
|
||||
notStartedMigrationCard.onDidClick((e) => {
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
});
|
||||
this._migrationStatusCardsContainer.addItem(
|
||||
notStartedMigrationCard
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
this._migrationStatusCardLoadingContainer.loading = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async getMigrations(): Promise<MigrationContext[]> {
|
||||
const currentConnection = (await azdata.connection.getCurrentConnection());
|
||||
const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection);
|
||||
for (let i = 0; i < localMigrations.length; i++) {
|
||||
const localMigration = localMigrations[i];
|
||||
localMigration.migrationContext = await getDatabaseMigration(
|
||||
localMigration.azureAccount,
|
||||
localMigration.subscription,
|
||||
localMigration.targetManagedInstance.location,
|
||||
localMigration.migrationContext.id
|
||||
);
|
||||
localMigration.sourceConnectionProfile = currentConnection;
|
||||
}
|
||||
return localMigrations;
|
||||
}
|
||||
|
||||
private createStatusCard(
|
||||
cardIconPath: IconPath,
|
||||
cardTitle: string,
|
||||
cardDescription: string,
|
||||
count: number
|
||||
): azdata.DivContainer {
|
||||
|
||||
const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({
|
||||
CSSStyles: {
|
||||
'font-weight': 'bold',
|
||||
'height': '20px',
|
||||
'margin-top': '6px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '300px',
|
||||
'font-size': '14px'
|
||||
}
|
||||
}).component();
|
||||
const cardDescriptionText = this._view.modelBuilder.text().withProps({ value: cardDescription }).withProps({
|
||||
CSSStyles: {
|
||||
'height': '18px',
|
||||
'margin-top': '0px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '300px'
|
||||
}
|
||||
}).component();
|
||||
const cardCount = this._view.modelBuilder.text().withProps({
|
||||
value: count.toString(),
|
||||
CSSStyles: {
|
||||
'font-size': '28px',
|
||||
'line-height': '36px',
|
||||
'margin-top': '4px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
|
||||
cardTitleText,
|
||||
cardDescriptionText
|
||||
]).withLayout({
|
||||
flexFlow: 'column'
|
||||
}).withProps({
|
||||
CSSStyles: {
|
||||
'width': '300px',
|
||||
'height': '50px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const flex = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'width': '400px',
|
||||
'height': '50px',
|
||||
'margin-top': '10px',
|
||||
'box-shadow': '0 1px 2px 0 rgba(0,0,0,0.2)'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const img = this._view.modelBuilder.image().withProps({
|
||||
iconPath: cardIconPath!.light,
|
||||
iconHeight: 16,
|
||||
iconWidth: 16,
|
||||
width: 64,
|
||||
height: 50
|
||||
}).component();
|
||||
|
||||
flex.addItem(img, {
|
||||
flex: '0'
|
||||
});
|
||||
flex.addItem(flexContainer, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '300px'
|
||||
}
|
||||
});
|
||||
flex.addItem(cardCount, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
const compositeButton = this._view.modelBuilder.divContainer().withItems([flex]).withProps({
|
||||
ariaRole: 'button',
|
||||
ariaLabel: 'show status',
|
||||
clickable: true
|
||||
}).component();
|
||||
return compositeButton;
|
||||
}
|
||||
|
||||
private async createFooter(view: azdata.ModelView): Promise<azdata.Component> {
|
||||
@@ -258,14 +406,22 @@ export class DashboardWidget {
|
||||
|
||||
const viewAllButton = view.modelBuilder.hyperlink().withProps({
|
||||
label: loc.VIEW_ALL,
|
||||
url: ''
|
||||
url: '',
|
||||
CSSStyles: {
|
||||
'font-size': '13px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
viewAllButton.onDidClick(async (e) => {
|
||||
new MigrationStatusDialog(await this.getMigrations(), MigrationCategory.ALL).initialize();
|
||||
});
|
||||
|
||||
const refreshButton = view.modelBuilder.hyperlink().withProps({
|
||||
label: loc.REFRESH,
|
||||
url: '',
|
||||
CSSStyles: {
|
||||
'text-align': 'right'
|
||||
'text-align': 'right',
|
||||
'font-size': '13px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
@@ -305,6 +461,7 @@ export class DashboardWidget {
|
||||
|
||||
|
||||
this._migrationStatusCardsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this._migrationStatusCardLoadingContainer = view.modelBuilder.loadingComponent().withItem(this._migrationStatusCardsContainer).component();
|
||||
|
||||
statusContainer.addItem(
|
||||
header, {
|
||||
@@ -318,7 +475,7 @@ export class DashboardWidget {
|
||||
}
|
||||
);
|
||||
|
||||
statusContainer.addItem(this._migrationStatusCardsContainer, {
|
||||
statusContainer.addItem(this._migrationStatusCardLoadingContainer, {
|
||||
CSSStyles: {
|
||||
'margin-top': '30px'
|
||||
}
|
||||
@@ -374,12 +531,12 @@ export class DashboardWidget {
|
||||
|
||||
const videosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: IconPathHelper.sqlMiImportHelpThumbnail,
|
||||
iconPath: IconPathHelper.sqlMiVideoThumbnail,
|
||||
description: loc.HELP_VIDEO1_TITLE,
|
||||
link: 'https://www.youtube.com/watch?v=sE99cSoFOHs' //TODO: Fix Video link
|
||||
},
|
||||
{
|
||||
iconPath: IconPathHelper.sqlVmImportHelpThumbnail,
|
||||
iconPath: IconPathHelper.sqlVmVideoThumbnail,
|
||||
description: loc.HELP_VIDEO2_TITLE,
|
||||
link: 'https://www.youtube.com/watch?v=R4GCBoxADyQ' //TODO: Fix video link
|
||||
}
|
||||
@@ -447,13 +604,10 @@ export class DashboardWidget {
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
}).component();
|
||||
|
||||
links.forEach(link => {
|
||||
const videoContainer = this.createVideoLink(view, link);
|
||||
|
||||
videosContainer.addItem(videoContainer);
|
||||
});
|
||||
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as azdata from 'azdata';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
||||
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
|
||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||
|
||||
export type Issues = {
|
||||
description: string,
|
||||
@@ -30,7 +31,7 @@ export class AssessmentResultsDialog {
|
||||
private _tree: SqlDatabaseTree;
|
||||
|
||||
|
||||
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string) {
|
||||
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private skuRecommendationPage: SKURecommendationPage) {
|
||||
this._model = model;
|
||||
let assessmentData = this.parseData(this._model);
|
||||
this._tree = new SqlDatabaseTree(this._model, assessmentData);
|
||||
@@ -126,6 +127,7 @@ export class AssessmentResultsDialog {
|
||||
|
||||
protected async execute() {
|
||||
this.model._migrationDbs = this._tree.selectedDbs();
|
||||
this.skuRecommendationPage.refreshDatabaseCount(this._model._migrationDbs.length);
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../../api/azure';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../models/strings';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as os from 'os';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage';
|
||||
@@ -130,7 +130,6 @@ export class CreateMigrationControllerDialog {
|
||||
this._dialogObject.okButton.enabled = false;
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
this._dialogObject.cancelButton.onClick((e) => {
|
||||
this.migrationStateModel._migrationController = undefined!;
|
||||
});
|
||||
this._dialogObject.okButton.onClick((e) => {
|
||||
this.irPage.populateMigrationController();
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
export class MigrationCutoverDialog {
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
private _model: MigrationCutoverDialogModel;
|
||||
|
||||
private _databaseTitleName!: azdata.TextComponent;
|
||||
private _databaseCutoverButton!: azdata.ButtonComponent;
|
||||
private _refresh!: azdata.ButtonComponent;
|
||||
|
||||
private _serverName!: azdata.TextComponent;
|
||||
private _serverVersion!: azdata.TextComponent;
|
||||
private _targetServer!: azdata.TextComponent;
|
||||
private _targetVersion!: azdata.TextComponent;
|
||||
private _migrationStatus!: azdata.TextComponent;
|
||||
private _fullBackupFile!: azdata.TextComponent;
|
||||
private _lastAppliedLSN!: azdata.TextComponent;
|
||||
private _lastAppliedBackupFile!: azdata.TextComponent;
|
||||
private _lastAppliedBackupTakenOn!: azdata.TextComponent;
|
||||
|
||||
private _fileCount!: azdata.TextComponent;
|
||||
|
||||
private fileTable!: azdata.TableComponent;
|
||||
|
||||
private _startCutover!: boolean;
|
||||
|
||||
constructor(migration: MigrationContext) {
|
||||
this._model = new MigrationCutoverDialogModel(migration);
|
||||
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_CUTOVER, 'MigrationCutoverDialog', 1000);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
let tab = azdata.window.createTab('');
|
||||
tab.registerContent(async (view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
const sourceDetails = this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
|
||||
this._serverName = sourceDetails.text;
|
||||
this._serverVersion = sourceVersion.text;
|
||||
|
||||
const flexServer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexServer.addItem(sourceDetails.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
flexServer.addItem(sourceVersion.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
|
||||
const targetServer = this.createInfoField(loc.TARGET_SERVER, '');
|
||||
const targetVersion = this.createInfoField(loc.TARGET_VERSION, '');
|
||||
|
||||
this._targetServer = targetServer.text;
|
||||
this._targetVersion = targetVersion.text;
|
||||
|
||||
const flexTarget = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexTarget.addItem(targetServer.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexTarget.addItem(targetVersion.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
|
||||
const migrationStatus = this.createInfoField(loc.MIGRATION_STATUS, '');
|
||||
const fullBackupFileOn = this.createInfoField(loc.FULL_BACKUP_FILES, '');
|
||||
|
||||
|
||||
this._migrationStatus = migrationStatus.text;
|
||||
this._fullBackupFile = fullBackupFileOn.text;
|
||||
|
||||
const flexStatus = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexStatus.addItem(migrationStatus.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
flexStatus.addItem(fullBackupFileOn.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
|
||||
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': '230px'
|
||||
}
|
||||
});
|
||||
flexFile.addItem(lastAppliedBackup.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexFile.addItem(lastAppliedBackupOn.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
const flexInfo = view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'width': '700px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
flexInfo.addItem(flexServer, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexTarget, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexStatus, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexFile, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '200px'
|
||||
}
|
||||
});
|
||||
|
||||
this._fileCount = view.modelBuilder.text().withProps({
|
||||
width: '500px',
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
}).component();
|
||||
|
||||
this.fileTable = view.modelBuilder.table().withProps({
|
||||
columns: [
|
||||
{
|
||||
value: loc.ACTIVE_BACKUP_FILES,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.TYPE,
|
||||
width: 100,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.STATUS,
|
||||
width: 100,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.BACKUP_START_TIME,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}, {
|
||||
value: loc.FIRST_LSN,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}, {
|
||||
value: loc.LAST_LSN,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
width: '800px',
|
||||
height: '600px',
|
||||
}).component();
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: await this.migrationContainerHeader()
|
||||
},
|
||||
{
|
||||
component: flexInfo
|
||||
},
|
||||
{
|
||||
component: this._fileCount
|
||||
},
|
||||
{
|
||||
component: this.fileTable
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
this.refreshStatus();
|
||||
}
|
||||
|
||||
|
||||
private migrationContainerHeader(): azdata.FlexContainer {
|
||||
const header = this._view.modelBuilder.flexContainer().withLayout({
|
||||
}).component();
|
||||
|
||||
this._databaseTitleName = this._view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
'font-size': 'large',
|
||||
'width': '400px'
|
||||
},
|
||||
value: this._model._migration.migrationContext.name
|
||||
}).component();
|
||||
|
||||
header.addItem(this._databaseTitleName, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '500px'
|
||||
}
|
||||
});
|
||||
|
||||
this._databaseCutoverButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.cutover,
|
||||
iconHeight: '14px',
|
||||
iconWidth: '12px',
|
||||
label: 'Start Cutover',
|
||||
height: '55px',
|
||||
width: '100px',
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this._databaseCutoverButton.onDidClick(async (e) => {
|
||||
if (this._startCutover) {
|
||||
await this._model.startCutover();
|
||||
this.refreshStatus();
|
||||
} else {
|
||||
this._dialogObject.message = {
|
||||
text: loc.CANNOT_START_CUTOVER_ERROR,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
header.addItem(this._databaseCutoverButton, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '100px'
|
||||
}
|
||||
});
|
||||
|
||||
this._refresh = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.refresh,
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
label: 'Refresh',
|
||||
height: '55px',
|
||||
width: '100px'
|
||||
}).component();
|
||||
|
||||
this._refresh.onDidClick((e) => {
|
||||
this.refreshStatus();
|
||||
});
|
||||
|
||||
header.addItem(this._refresh, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '100px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
private async refreshStatus(): Promise<void> {
|
||||
try {
|
||||
await this._model.fetchStatus();
|
||||
const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId);
|
||||
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
|
||||
const sqlServerVersion = sqlServerInfo.serverVersion;
|
||||
const sqlServerEdition = sqlServerInfo.serverEdition;
|
||||
const targetServerName = this._model._migration.targetManagedInstance.name;
|
||||
let targetServerVersion;
|
||||
if (this._model.migrationStatus.id.includes('managedInstances')) {
|
||||
targetServerVersion = loc.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
|
||||
} else {
|
||||
targetServerVersion = loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
|
||||
}
|
||||
|
||||
const migrationStatusTextValue = this._model.migrationStatus.properties.migrationStatus;
|
||||
|
||||
let fullBackupFileName: string;
|
||||
let lastAppliedSSN: string;
|
||||
let lastAppliedBackupFileTakenOn: string;
|
||||
|
||||
|
||||
const tableData: ActiveBackupFileSchema[] = [];
|
||||
|
||||
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach((activeBackupSet) => {
|
||||
tableData.push(
|
||||
{
|
||||
fileName: activeBackupSet.listOfBackupFiles[0].fileName,
|
||||
type: activeBackupSet.backupType,
|
||||
status: activeBackupSet.listOfBackupFiles[0].status,
|
||||
backupStartTime: activeBackupSet.backupStartDate,
|
||||
firstLSN: activeBackupSet.firstLSN,
|
||||
lastLSN: activeBackupSet.lastLSN
|
||||
}
|
||||
);
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName.substr(activeBackupSet.listOfBackupFiles[0].fileName.lastIndexOf('.') + 1) === 'bak') {
|
||||
fullBackupFileName = activeBackupSet.listOfBackupFiles[0].fileName;
|
||||
}
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
this._serverName.value = sqlServerName;
|
||||
this._serverVersion.value = `${sqlServerVersion}
|
||||
${sqlServerEdition}`;
|
||||
|
||||
this._targetServer.value = targetServerName;
|
||||
this._targetVersion.value = targetServerVersion;
|
||||
|
||||
this._migrationStatus.value = migrationStatusTextValue;
|
||||
this._fullBackupFile.value = fullBackupFileName!;
|
||||
|
||||
this._lastAppliedLSN.value = lastAppliedSSN!;
|
||||
this._lastAppliedBackupFile.value = this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename;
|
||||
this._lastAppliedBackupTakenOn.value = new Date(lastAppliedBackupFileTakenOn!).toLocaleString();
|
||||
|
||||
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
||||
|
||||
this.fileTable.data = tableData.map((row) => {
|
||||
return [
|
||||
row.fileName,
|
||||
row.type,
|
||||
row.status,
|
||||
new Date(row.backupStartTime).toLocaleString(),
|
||||
row.firstLSN,
|
||||
row.lastLSN
|
||||
];
|
||||
});
|
||||
if (this._model.migrationStatus.properties.migrationStatusDetails?.isFullBackupRestored) {
|
||||
this._startCutover = true;
|
||||
}
|
||||
|
||||
if (migrationStatusTextValue === 'InProgress') {
|
||||
this._databaseCutoverButton.enabled = true;
|
||||
} else {
|
||||
this._databaseCutoverButton.enabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private createInfoField(label: string, value: string): {
|
||||
flexContainer: azdata.FlexContainer,
|
||||
text: azdata.TextComponent
|
||||
} {
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
const labelComponent = this._view.modelBuilder.text().withProps({
|
||||
value: label,
|
||||
CSSStyles: {
|
||||
'font-weight': 'bold',
|
||||
'margin-bottom': '0'
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(labelComponent);
|
||||
|
||||
const textComponent = this._view.modelBuilder.text().withProps({
|
||||
value: value,
|
||||
CSSStyles: {
|
||||
'margin-top': '5px',
|
||||
'margin-bottom': '0'
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(textComponent);
|
||||
return {
|
||||
flexContainer: flexContainer,
|
||||
text: textComponent
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface ActiveBackupFileSchema {
|
||||
fileName: string,
|
||||
type: string,
|
||||
status: string,
|
||||
backupStartTime: string,
|
||||
firstLSN: string,
|
||||
lastLSN: string
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getMigrationStatus, DatabaseMigration, startMigrationCutover } from '../../api/azure';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
|
||||
|
||||
export class MigrationCutoverDialogModel {
|
||||
|
||||
public migrationStatus!: DatabaseMigration;
|
||||
|
||||
constructor(public _migration: MigrationContext) {
|
||||
}
|
||||
|
||||
public async fetchStatus(): Promise<void> {
|
||||
this.migrationStatus = (await getMigrationStatus(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this._migration.migrationContext
|
||||
));
|
||||
}
|
||||
|
||||
public async startCutover(): Promise<DatabaseMigration | undefined> {
|
||||
try {
|
||||
if (this.migrationStatus) {
|
||||
return await startMigrationCutover(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this.migrationStatus
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return undefined!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
||||
import { MigrationCategory, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
export class MigrationStatusDialog {
|
||||
private _model: MigrationStatusDialogModel;
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
private _searchBox!: azdata.InputBoxComponent;
|
||||
private _refresh!: azdata.ButtonComponent;
|
||||
private _statusDropdown!: azdata.DropDownComponent;
|
||||
private _statusTable!: azdata.DeclarativeTableComponent;
|
||||
|
||||
constructor(migrations: MigrationContext[], private _filter: MigrationCategory) {
|
||||
this._model = new MigrationStatusDialogModel(migrations);
|
||||
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
|
||||
}
|
||||
|
||||
initialize() {
|
||||
let tab = azdata.window.createTab('');
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
|
||||
this._statusDropdown = this._view.modelBuilder.dropDown().withProps({
|
||||
values: this._model.statusDropdownValues,
|
||||
width: '220px'
|
||||
}).component();
|
||||
|
||||
this._statusDropdown.onValueChanged((value) => {
|
||||
this.populateMigrationTable();
|
||||
});
|
||||
|
||||
this._statusDropdown.value = this._statusDropdown.values![this._filter];
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.createSearchAndRefreshContainer()
|
||||
},
|
||||
{
|
||||
component: this._statusDropdown
|
||||
},
|
||||
{
|
||||
component: this.createStatusTable()
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
}
|
||||
|
||||
private createSearchAndRefreshContainer(): azdata.FlexContainer {
|
||||
this._searchBox = this._view.modelBuilder.inputBox().withProps({
|
||||
placeHolder: loc.SEARCH_FOR_MIGRATIONS,
|
||||
width: '360px'
|
||||
}).component();
|
||||
|
||||
this._searchBox.onTextChanged((value) => {
|
||||
this.populateMigrationTable();
|
||||
});
|
||||
|
||||
this._refresh = this._view.modelBuilder.button().withProps({
|
||||
iconPath: {
|
||||
light: IconPathHelper.refresh.light,
|
||||
dark: IconPathHelper.refresh.dark
|
||||
},
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
height: '30px',
|
||||
label: 'Refresh',
|
||||
}).component();
|
||||
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().component();
|
||||
|
||||
flexContainer.addItem(this._searchBox, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
flexContainer.addItem(this._refresh, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'margin-left': '20px'
|
||||
}
|
||||
});
|
||||
|
||||
return flexContainer;
|
||||
}
|
||||
|
||||
private populateMigrationTable(): void {
|
||||
|
||||
try {
|
||||
const migrations = this._model.filterMigration(
|
||||
this._searchBox.value!,
|
||||
(<azdata.CategoryValue>this._statusDropdown.value).name
|
||||
);
|
||||
|
||||
const data: azdata.DeclarativeTableCellValue[][] = [];
|
||||
|
||||
migrations.forEach((migration) => {
|
||||
const migrationRow: azdata.DeclarativeTableCellValue[] = [];
|
||||
|
||||
const databaseHyperLink = this._view.modelBuilder.hyperlink().withProps({
|
||||
label: migration.migrationContext.name,
|
||||
url: ''
|
||||
}).component();
|
||||
databaseHyperLink.onDidClick(async (e) => {
|
||||
await (new MigrationCutoverDialog(migration)).initialize();
|
||||
});
|
||||
migrationRow.push({
|
||||
value: databaseHyperLink,
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: migration.migrationContext.properties.migrationStatus
|
||||
});
|
||||
|
||||
const sqlMigrationIcon = this._view.modelBuilder.image().withProps({
|
||||
iconPath: IconPathHelper.sqlMigrationLogo,
|
||||
iconWidth: '16px',
|
||||
iconHeight: '16px',
|
||||
width: '32px',
|
||||
height: '20px'
|
||||
}).component();
|
||||
const sqlMigrationName = this._view.modelBuilder.hyperlink().withProps({
|
||||
label: migration.migrationContext.name,
|
||||
url: ''
|
||||
}).component();
|
||||
sqlMigrationName.onDidClick((e) => {
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
});
|
||||
|
||||
const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'justify-content': 'center'
|
||||
}
|
||||
}).component();
|
||||
sqlMigrationContainer.addItem(sqlMigrationIcon, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '32px'
|
||||
}
|
||||
});
|
||||
sqlMigrationContainer.addItem(sqlMigrationName,
|
||||
{
|
||||
CSSStyles: {
|
||||
'width': 'auto'
|
||||
}
|
||||
});
|
||||
migrationRow.push({
|
||||
value: sqlMigrationContainer
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: loc.ONLINE
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: '---'
|
||||
});
|
||||
migrationRow.push({
|
||||
value: '---'
|
||||
});
|
||||
|
||||
data.push(migrationRow);
|
||||
});
|
||||
|
||||
this._statusTable.dataValues = data;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private createStatusTable(): azdata.DeclarativeTableComponent {
|
||||
this._statusTable = this._view.modelBuilder.declarativeTable().withProps({
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.DATABASE,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '100px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.MIGRATION_STATUS,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '300px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.CUTOVER_TYPE,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.START_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.FINISH_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
}
|
||||
]
|
||||
}).component();
|
||||
return this._statusTable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
|
||||
export class MigrationStatusDialogModel {
|
||||
|
||||
public statusDropdownValues: azdata.CategoryValue[] = [
|
||||
{
|
||||
displayName: 'Status: All',
|
||||
name: 'All',
|
||||
}, {
|
||||
displayName: 'Status: Ongoing',
|
||||
name: 'Ongoing',
|
||||
}, {
|
||||
displayName: 'Status: Succeeded',
|
||||
name: 'Succeeded',
|
||||
}
|
||||
];
|
||||
|
||||
constructor(public _migrations: MigrationContext[]) {
|
||||
}
|
||||
|
||||
public filterMigration(databaseName: string, category: string): MigrationContext[] {
|
||||
let filteredMigration: MigrationContext[] = [];
|
||||
if (category === 'All') {
|
||||
filteredMigration = this._migrations;
|
||||
} else if (category === 'Ongoing') {
|
||||
filteredMigration = this._migrations.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'InProgress' || status === 'Creating' || status === 'Completing';
|
||||
});
|
||||
} else if (category === 'Succeeded') {
|
||||
filteredMigration = this._migrations.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'Succeeded';
|
||||
});
|
||||
}
|
||||
if (databaseName) {
|
||||
filteredMigration = filteredMigration.filter((value) => {
|
||||
return value.migrationContext.name.toLowerCase().includes(databaseName.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
return filteredMigration;
|
||||
}
|
||||
}
|
||||
|
||||
export enum MigrationCategory {
|
||||
ALL,
|
||||
ONGOING,
|
||||
SUCCEEDED
|
||||
}
|
||||
@@ -6,9 +6,8 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { WizardController } from './wizard/wizardController';
|
||||
import { AssessmentResultsDialog } from './dialog/assessmentResults/assessmentResultsDialog';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as loc from './models/strings';
|
||||
import * as loc from './constants/strings';
|
||||
import { MigrationNotebookInfo, NotebookPathHelper } from './constants/notebookPathHelper';
|
||||
import { IconPathHelper } from './constants/iconPathHelper';
|
||||
import { DashboardWidget } from './dashboard/sqlServerDashboard';
|
||||
@@ -42,12 +41,6 @@ class SQLMigration {
|
||||
const wizardController = new WizardController(this.context);
|
||||
await wizardController.openWizard(connectionId);
|
||||
}),
|
||||
|
||||
vscode.commands.registerCommand('sqlmigration.testDialog', async () => {
|
||||
let dialog = new AssessmentResultsDialog('ownerUri', undefined!, 'Assessment Dialog');
|
||||
await dialog.openDialog();
|
||||
}),
|
||||
|
||||
vscode.commands.registerCommand('sqlmigration.openNotebooks', async () => {
|
||||
const input = vscode.window.createQuickPick<MigrationNotebookInfo>();
|
||||
input.placeholder = loc.NOTEBOOK_QUICK_PICK_PLACEHOLDER;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { DatabaseMigration, MigrationController, SqlManagedInstance } from '../api/azure';
|
||||
import { DatabaseMigration, SqlMigrationController, SqlManagedInstance } from '../api/azure';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export class MigrationLocalStorage {
|
||||
MigrationLocalStorage.context = context;
|
||||
}
|
||||
|
||||
public static getMigrations(connectionProfile: azdata.connection.ConnectionProfile): MigrationContext[] {
|
||||
public static getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile): MigrationContext[] {
|
||||
|
||||
let dataBaseMigrations: MigrationContext[] = [];
|
||||
try {
|
||||
@@ -41,7 +41,7 @@ export class MigrationLocalStorage {
|
||||
targetMI: SqlManagedInstance,
|
||||
azureAccount: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
controller: MigrationController): void {
|
||||
controller: SqlMigrationController): void {
|
||||
try {
|
||||
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
|
||||
migrationMementos.push({
|
||||
@@ -69,5 +69,5 @@ export interface MigrationContext {
|
||||
targetManagedInstance: SqlManagedInstance,
|
||||
azureAccount: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
controller: MigrationController
|
||||
controller: SqlMigrationController
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import * as azdata from 'azdata';
|
||||
import { azureResource } from 'azureResource';
|
||||
import * as vscode from 'vscode';
|
||||
import * as mssql from '../../../mssql';
|
||||
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getMigrationControllers, getSubscriptions, MigrationController, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount } from '../api/azure';
|
||||
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getMigrationControllers, getSubscriptions, SqlMigrationController, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount } from '../api/azure';
|
||||
import { SKURecommendations } from './externalContract';
|
||||
import * as constants from '../models/strings';
|
||||
import * as constants from '../constants/strings';
|
||||
import { MigrationLocalStorage } from './migrationLocalStorage';
|
||||
|
||||
export enum State {
|
||||
@@ -85,13 +85,13 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
public _targetManagedInstance!: SqlManagedInstance;
|
||||
|
||||
public _databaseBackup!: DatabaseBackupModel;
|
||||
public _migrationDbs!: string[];
|
||||
public _migrationDbs: string[] = [];
|
||||
public _storageAccounts!: StorageAccount[];
|
||||
public _fileShares!: azureResource.FileShare[];
|
||||
public _blobContainers!: azureResource.BlobContainer[];
|
||||
|
||||
public _migrationController!: MigrationController;
|
||||
public _migrationControllers!: MigrationController[];
|
||||
public _migrationController!: SqlMigrationController;
|
||||
public _migrationControllers!: SqlMigrationController[];
|
||||
public _nodeNames!: string[];
|
||||
|
||||
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
|
||||
@@ -403,7 +403,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
return migrationControllerValues;
|
||||
}
|
||||
|
||||
public getMigrationController(index: number): MigrationController {
|
||||
public getMigrationController(index: number): SqlMigrationController {
|
||||
return this._migrationControllers[index];
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
const requestBody: StartDatabaseMigrationRequest = {
|
||||
location: this._migrationController?.properties.location!,
|
||||
properties: {
|
||||
SourceDatabaseName: currentConnection?.databaseName!,
|
||||
SourceDatabaseName: '',
|
||||
MigrationController: this._migrationController?.id!,
|
||||
BackupConfiguration: {
|
||||
TargetLocation: {
|
||||
@@ -445,26 +445,36 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
const response = await startDatabaseMigration(
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
this._targetManagedInstance.resourceGroup!,
|
||||
this._migrationController?.properties.location!,
|
||||
this._targetManagedInstance.name,
|
||||
this._migrationController?.name!,
|
||||
requestBody
|
||||
);
|
||||
this._migrationDbs.forEach(async (db) => {
|
||||
|
||||
if (response.status === 201) {
|
||||
MigrationLocalStorage.saveMigration(
|
||||
currentConnection!,
|
||||
response.databaseMigration,
|
||||
this._targetManagedInstance,
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
this._migrationController
|
||||
);
|
||||
}
|
||||
requestBody.properties.SourceDatabaseName = db;
|
||||
try {
|
||||
const response = await startDatabaseMigration(
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
this._targetManagedInstance.resourceGroup!,
|
||||
this._migrationController?.properties.location!,
|
||||
this._targetManagedInstance.name,
|
||||
currentConnection?.databaseName!,
|
||||
requestBody
|
||||
);
|
||||
|
||||
if (response.status === 201) {
|
||||
MigrationLocalStorage.saveMigration(
|
||||
currentConnection!,
|
||||
response.databaseMigration,
|
||||
this._targetManagedInstance,
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
this._migrationController
|
||||
);
|
||||
vscode.window.showInformationMessage(`Starting migration for database ${db} to ${this._targetManagedInstance.name}`);
|
||||
}
|
||||
} catch (e) {
|
||||
vscode.window.showInformationMessage(e);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
vscode.window.showInformationMessage(constants.MIGRATION_STARTED);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../models/strings';
|
||||
import * as constants from '../constants/strings';
|
||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
|
||||
export class AccountsSelectionPage extends MigrationWizardPage {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { EOL } from 'os';
|
||||
import { getStorageAccountAccessKeys } from '../api/azure';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationCutover, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../models/strings';
|
||||
|
||||
import * as constants from '../constants/strings';
|
||||
import * as vscode from 'vscode';
|
||||
export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
|
||||
private _networkShareContainer!: azdata.FlexContainer;
|
||||
@@ -85,7 +85,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
|
||||
blobContainerButton.onDidChangeCheckedState((e) => {
|
||||
if (e) {
|
||||
this.toggleNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
networkShareButton.checked = true;
|
||||
//this.toggleNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -97,7 +99,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
|
||||
fileShareButton.onDidChangeCheckedState((e) => {
|
||||
if (e) {
|
||||
this.toggleNetworkContainerFields(NetworkContainerType.FILE_SHARE);
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
networkShareButton.checked = true;
|
||||
//this.toggleNetworkContainerFields(NetworkContainerType.FILE_SHARE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -414,7 +418,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
|
||||
offlineButton.onDidChangeCheckedState((e) => {
|
||||
if (e) {
|
||||
this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE;
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
onlineButton.checked = true;
|
||||
//this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -489,7 +495,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
|
||||
public async onPageLeave(): Promise<void> {
|
||||
this.migrationStateModel._databaseBackup.storageKey = (await getStorageAccountAccessKeys(this.migrationStateModel._azureAccount, this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount)).keyName1;
|
||||
console.log(this.migrationStateModel._databaseBackup);
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import { CreateMigrationControllerDialog } from '../dialog/createMigrationDialog/createMigrationControllerDialog';
|
||||
import * as constants from '../models/strings';
|
||||
import * as constants from '../constants/strings';
|
||||
import { createInformationRow, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
import { getMigrationController, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../api/azure';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
@@ -147,7 +147,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
||||
return flexContainer;
|
||||
}
|
||||
|
||||
public async populateMigrationController(controllerStatus?: string): Promise<void> {
|
||||
public async populateMigrationController(): Promise<void> {
|
||||
this.migrationControllerDropdown.loading = true;
|
||||
try {
|
||||
this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetManagedInstance);
|
||||
|
||||
@@ -8,11 +8,10 @@ import * as path from 'path';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import { Product, ProductLookupTable } from '../models/product';
|
||||
import { Disposable } from 'vscode';
|
||||
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
|
||||
import { getAvailableManagedInstanceProducts, getSubscriptions, SqlManagedInstance, Subscription } from '../api/azure';
|
||||
import * as constants from '../models/strings';
|
||||
import { azureResource } from 'azureResource';
|
||||
import * as constants from '../constants/strings';
|
||||
import * as vscode from 'vscode';
|
||||
import { EOL } from 'os';
|
||||
|
||||
// import { SqlMigrationService } from '../../../../extensions/mssql/src/sqlMigration/sqlMigrationService';
|
||||
|
||||
@@ -32,11 +31,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
private _azureSubscriptionText: azdata.FormComponent<azdata.TextComponent> | undefined;
|
||||
private _managedInstanceSubscriptionDropdown!: azdata.DropDownComponent;
|
||||
private _managedInstanceDropdown!: azdata.DropDownComponent;
|
||||
private _subscriptionDropdownValues: azdata.CategoryValue[] = [];
|
||||
private _subscriptionMap: Map<string, Subscription> = new Map();
|
||||
private _view: azdata.ModelView | undefined;
|
||||
private _rbg!: azdata.RadioCardGroupComponent;
|
||||
|
||||
private async initialState(view: azdata.ModelView) {
|
||||
this._view = view;
|
||||
this._igComponent = this.createStatusComponent(view); // The first component giving basic information
|
||||
this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
|
||||
this._chooseTargetComponent = this.createChooseTargetComponent(view);
|
||||
@@ -47,12 +46,24 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
}).component();
|
||||
this._managedInstanceSubscriptionDropdown = view.modelBuilder.dropDown().component();
|
||||
this._managedInstanceSubscriptionDropdown.onValueChanged((e) => {
|
||||
this.populateManagedInstanceDropdown();
|
||||
if (e.selected) {
|
||||
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index);
|
||||
this.migrationStateModel._targetManagedInstance = undefined!;
|
||||
this.migrationStateModel._migrationController = undefined!;
|
||||
this.populateManagedInstanceDropdown();
|
||||
}
|
||||
});
|
||||
const managedInstanceDropdownLabel = view.modelBuilder.text().withProps({
|
||||
value: constants.MANAGED_INSTANCE
|
||||
}).component();
|
||||
|
||||
this._managedInstanceDropdown = view.modelBuilder.dropDown().component();
|
||||
this._managedInstanceDropdown.onValueChanged((e) => {
|
||||
if (e.selected) {
|
||||
this.migrationStateModel._migrationControllers = undefined!;
|
||||
this.migrationStateModel._targetManagedInstance = this.migrationStateModel.getManagedInstance(e.index);
|
||||
}
|
||||
});
|
||||
|
||||
const targetContainer = view.modelBuilder.flexContainer().withItems(
|
||||
[
|
||||
@@ -137,18 +148,23 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
private constructTargets(): void {
|
||||
const products: Product[] = Object.values(ProductLookupTable);
|
||||
|
||||
const rbg = this._view!.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||
this._rbg = this._view!.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||
cards: [],
|
||||
cardWidth: '600px',
|
||||
cardHeight: '60px',
|
||||
orientation: azdata.Orientation.Vertical,
|
||||
iconHeight: '30px',
|
||||
iconWidth: '30px'
|
||||
});
|
||||
}).component();
|
||||
|
||||
products.forEach((product) => {
|
||||
const imagePath = path.resolve(this.migrationStateModel.getExtensionPath(), 'media', product.icon ?? 'ads.svg');
|
||||
|
||||
let dbCount = 0;
|
||||
if (product.type === 'AzureSQLVM') {
|
||||
dbCount = 0;
|
||||
} else {
|
||||
dbCount = this.migrationStateModel._migrationDbs.length;
|
||||
}
|
||||
const descriptions: azdata.RadioCardDescription[] = [
|
||||
{
|
||||
textValue: product.name,
|
||||
@@ -164,7 +180,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
}
|
||||
},
|
||||
{
|
||||
textValue: '9 databases will be migrated',
|
||||
textValue: `${dbCount} databases will be migrated`,
|
||||
linkDisplayValue: 'View/Change',
|
||||
displayLinkCodicon: true,
|
||||
linkCodiconStyles: {
|
||||
@@ -174,22 +190,31 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
}
|
||||
];
|
||||
|
||||
rbg.component().cards.push({
|
||||
id: product.name,
|
||||
this._rbg.cards.push({
|
||||
id: product.type,
|
||||
icon: imagePath,
|
||||
descriptions
|
||||
});
|
||||
});
|
||||
|
||||
rbg.component().onLinkClick(async (value) => {
|
||||
this._rbg.onLinkClick(async (value) => {
|
||||
|
||||
//check which card is being selected, and open correct dialog based on link
|
||||
console.log(value);
|
||||
let dialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, 'Assessment Dialog');
|
||||
let dialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, 'Assessment Dialog', this);
|
||||
await dialog.openDialog();
|
||||
});
|
||||
|
||||
this._chooseTargetComponent?.component.addItem(rbg.component());
|
||||
this._rbg.onSelectionChanged((value) => {
|
||||
if (value.cardId === 'AzureSQLVM') {
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
this._rbg.selectedCardId = 'AzureSQLMI';
|
||||
}
|
||||
});
|
||||
|
||||
this._rbg.selectedCardId = 'AzureSQLMI';
|
||||
|
||||
this._chooseTargetComponent?.component.addItem(this._rbg);
|
||||
}
|
||||
|
||||
private createAzureSubscriptionText(view: azdata.ModelView): azdata.FormComponent<azdata.TextComponent> {
|
||||
@@ -205,85 +230,78 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
}
|
||||
|
||||
private async populateSubscriptionDropdown(): Promise<void> {
|
||||
this._managedInstanceSubscriptionDropdown.loading = true;
|
||||
this._managedInstanceDropdown.loading = true;
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
try {
|
||||
subscriptions = await getSubscriptions(this.migrationStateModel._azureAccount);
|
||||
subscriptions.forEach((subscription) => {
|
||||
this._subscriptionMap.set(subscription.id, subscription);
|
||||
this._subscriptionDropdownValues.push({
|
||||
name: subscription.id,
|
||||
displayName: subscription.name + ' - ' + subscription.id,
|
||||
});
|
||||
});
|
||||
|
||||
if (!this._subscriptionDropdownValues || this._subscriptionDropdownValues.length === 0) {
|
||||
this._subscriptionDropdownValues = [
|
||||
{
|
||||
displayName: constants.NO_SUBSCRIPTIONS_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
if (!this.migrationStateModel._targetSubscription) {
|
||||
this._managedInstanceSubscriptionDropdown.loading = true;
|
||||
this._managedInstanceDropdown.loading = true;
|
||||
try {
|
||||
this._managedInstanceSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
this._managedInstanceSubscriptionDropdown.loading = false;
|
||||
}
|
||||
|
||||
this._managedInstanceSubscriptionDropdown.values = this._subscriptionDropdownValues;
|
||||
} catch (error) {
|
||||
this.setEmptyDropdownPlaceHolder(this._managedInstanceSubscriptionDropdown, constants.NO_SUBSCRIPTIONS_FOUND);
|
||||
this._managedInstanceDropdown.loading = false;
|
||||
}
|
||||
this.populateManagedInstanceDropdown();
|
||||
this._managedInstanceSubscriptionDropdown.loading = false;
|
||||
}
|
||||
|
||||
private async populateManagedInstanceDropdown(): Promise<void> {
|
||||
this._managedInstanceDropdown.loading = true;
|
||||
let mis: SqlManagedInstance[] = [];
|
||||
let miValues: azdata.CategoryValue[] = [];
|
||||
try {
|
||||
const subscriptionId = (<azdata.CategoryValue>this._managedInstanceSubscriptionDropdown.value).name;
|
||||
|
||||
mis = await getAvailableManagedInstanceProducts(this.migrationStateModel._azureAccount, this._subscriptionMap.get(subscriptionId)!);
|
||||
mis.forEach((mi) => {
|
||||
miValues.push({
|
||||
name: mi.name,
|
||||
displayName: mi.name
|
||||
});
|
||||
});
|
||||
|
||||
if (!miValues || miValues.length === 0) {
|
||||
miValues = [
|
||||
{
|
||||
displayName: constants.NO_MANAGED_INSTANCE_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
if (!this.migrationStateModel._targetManagedInstance) {
|
||||
this._managedInstanceDropdown.loading = true;
|
||||
try {
|
||||
this._managedInstanceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
this._managedInstanceDropdown.loading = false;
|
||||
}
|
||||
|
||||
this._managedInstanceDropdown.values = miValues;
|
||||
} catch (error) {
|
||||
this.setEmptyDropdownPlaceHolder(this._managedInstanceDropdown, constants.NO_MANAGED_INSTANCE_FOUND);
|
||||
}
|
||||
|
||||
this._managedInstanceDropdown.loading = false;
|
||||
}
|
||||
|
||||
private setEmptyDropdownPlaceHolder(dropDown: azdata.DropDownComponent, placeholder: string): void {
|
||||
dropDown.values = [{
|
||||
displayName: placeholder,
|
||||
name: ''
|
||||
}];
|
||||
}
|
||||
|
||||
private eventListener: Disposable | undefined;
|
||||
private eventListener: vscode.Disposable | undefined;
|
||||
public async onPageEnter(): Promise<void> {
|
||||
this.eventListener = this.migrationStateModel.stateChangeEvent(async (e) => this.onStateChangeEvent(e));
|
||||
this.populateSubscriptionDropdown();
|
||||
this.constructDetails();
|
||||
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
const errors: string[] = [];
|
||||
this.wizard.message = {
|
||||
text: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||
return true;
|
||||
}
|
||||
if (this.migrationStateModel._migrationDbs.length === 0) {
|
||||
errors.push('Please select databases to migrate');
|
||||
|
||||
}
|
||||
if ((<azdata.CategoryValue>this._managedInstanceSubscriptionDropdown.value).displayName === constants.NO_SUBSCRIPTIONS_FOUND) {
|
||||
errors.push(constants.INVALID_SUBSCRIPTION_ERROR);
|
||||
}
|
||||
if ((<azdata.CategoryValue>this._managedInstanceDropdown.value).displayName === constants.NO_MANAGED_INSTANCE_FOUND) {
|
||||
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
this.wizard.message = {
|
||||
text: errors.join(EOL),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public async onPageLeave(): Promise<void> {
|
||||
this.eventListener?.dispose();
|
||||
this.wizard.message = {
|
||||
text: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||
@@ -292,4 +310,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
public refreshDatabaseCount(count: number): void {
|
||||
this.wizard.message = {
|
||||
text: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
const textValue: string = `${count} databases will be migrated`;
|
||||
this._rbg.cards[0].descriptions[1].textValue = textValue;
|
||||
this._rbg.updateProperties({
|
||||
cards: this._rbg.cards
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { SOURCE_CONFIGURATION_PAGE_TITLE, COLLECTING_SOURCE_CONFIGURATIONS, COLLECTING_SOURCE_CONFIGURATIONS_INFO, COLLECTING_SOURCE_CONFIGURATIONS_ERROR } from '../models/strings';
|
||||
import { SOURCE_CONFIGURATION_PAGE_TITLE, COLLECTING_SOURCE_CONFIGURATIONS, COLLECTING_SOURCE_CONFIGURATIONS_INFO, COLLECTING_SOURCE_CONFIGURATIONS_ERROR } from '../constants/strings';
|
||||
import { MigrationStateModel, StateChangeEvent, State } from '../models/stateMachine';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import { SUBSCRIPTION_SELECTION_PAGE_TITLE, SUBSCRIPTION_SELECTION_AZURE_ACCOUNT_TITLE, SUBSCRIPTION_SELECTION_AZURE_PRODUCT_TITLE, SUBSCRIPTION_SELECTION_AZURE_SUBSCRIPTION_TITLE } from '../models/strings';
|
||||
import { SUBSCRIPTION_SELECTION_PAGE_TITLE, SUBSCRIPTION_SELECTION_AZURE_ACCOUNT_TITLE, SUBSCRIPTION_SELECTION_AZURE_PRODUCT_TITLE, SUBSCRIPTION_SELECTION_AZURE_SUBSCRIPTION_TITLE } from '../constants/strings';
|
||||
import { Disposable } from 'vscode';
|
||||
import { getSubscriptions, Subscription, getAvailableManagedInstanceProducts, AzureProduct, getAvailableSqlServers } from '../api/azure';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../models/strings';
|
||||
import * as constants from '../constants/strings';
|
||||
import { createHeadingTextComponent, createInformationRow } from './wizardController';
|
||||
|
||||
export class SummaryPage extends MigrationWizardPage {
|
||||
@@ -42,7 +42,7 @@ export class SummaryPage extends MigrationWizardPage {
|
||||
createInformationRow(this._view, constants.TYPE, constants.SUMMARY_MI_TYPE),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_MI_TYPE, this.migrationStateModel._targetManagedInstance.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, '1'),
|
||||
createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, this.migrationStateModel._migrationDbs.length.toString()),
|
||||
createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE),
|
||||
this.createNetworkContainerRows(),
|
||||
createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../models/strings';
|
||||
import * as constants from '../constants/strings';
|
||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
|
||||
export class TempTargetSelectionPage extends MigrationWizardPage {
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as mssql from '../../../mssql';
|
||||
import { MigrationStateModel } from '../models/stateMachine';
|
||||
import { WIZARD_TITLE } from '../models/strings';
|
||||
import * as loc from '../constants/strings';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { SKURecommendationPage } from './skuRecommendationPage';
|
||||
// import { SubscriptionSelectionPage } from './subscriptionSelectionPage';
|
||||
@@ -31,7 +31,7 @@ export class WizardController {
|
||||
}
|
||||
|
||||
private async createWizard(stateModel: MigrationStateModel): Promise<void> {
|
||||
const wizard = azdata.window.createWizard(WIZARD_TITLE, 'wide');
|
||||
const wizard = azdata.window.createWizard(loc.WIZARD_TITLE, 'wide');
|
||||
wizard.generateScriptButton.enabled = false;
|
||||
wizard.generateScriptButton.hidden = true;
|
||||
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
|
||||
|
||||
Reference in New Issue
Block a user