mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
sql-migration: update migration status page latest design (#16099)
* add migrations status context menu and commands * add migration status images * remove test code, add account validation * fix command registration to occure once * fix typo
This commit is contained in:
@@ -44,8 +44,70 @@
|
|||||||
"command": "sqlmigration.openNotebooks",
|
"command": "sqlmigration.openNotebooks",
|
||||||
"title": "%migration-notebook-command-title%",
|
"title": "%migration-notebook-command-title%",
|
||||||
"category": "%migration-command-category%"
|
"category": "%migration-command-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.cutover",
|
||||||
|
"title": "%complete-cutover-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.database",
|
||||||
|
"title": "%database-details-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.target",
|
||||||
|
"title": "%view-target-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.service",
|
||||||
|
"title": "%view-service-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.copy.migration",
|
||||||
|
"title": "%copy-migration-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.cancel.migration",
|
||||||
|
"title": "%cancel-migration-menu%",
|
||||||
|
"category": "%migration-context-menu-category%"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"menu": {
|
||||||
|
"commandPalette": [
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.sendfeedback",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.cutover",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.database",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.target",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.view.service",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.copy.migration",
|
||||||
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlmigration.cancel.migration",
|
||||||
|
"when": "false"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"dashboard.tabs": [
|
"dashboard.tabs": [
|
||||||
{
|
{
|
||||||
"id": "migration-dashboard",
|
"id": "migration-dashboard",
|
||||||
|
|||||||
@@ -6,5 +6,12 @@
|
|||||||
"migration-dashboard-tasks": "Migration Tasks",
|
"migration-dashboard-tasks": "Migration Tasks",
|
||||||
"migration-command-category": "Azure SQL Migration",
|
"migration-command-category": "Azure SQL Migration",
|
||||||
"start-migration-command": "Migrate to Azure SQL",
|
"start-migration-command": "Migrate to Azure SQL",
|
||||||
"send-feedback-command": "Feedback"
|
"send-feedback-command": "Feedback",
|
||||||
|
"migration-context-menu-category": "Migration Context Menu",
|
||||||
|
"complete-cutover-menu": "Complete cutover",
|
||||||
|
"database-details-menu": "Database details",
|
||||||
|
"view-target-menu": "Azure SQL Target details",
|
||||||
|
"view-service-menu": "Dataase Migration Service details",
|
||||||
|
"copy-migration-menu": "Copy migration details",
|
||||||
|
"cancel-migration-menu": "Cancel migration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ export const AZURE_TENANT = localize('sql.migration.azure.tenant', "Azure AD ten
|
|||||||
export function ACCOUNT_STALE_ERROR(account: AzureAccount) {
|
export function ACCOUNT_STALE_ERROR(account: AzureAccount) {
|
||||||
return localize('azure.accounts.accountStaleError', "The access token for selected account '{0}' is no longer valid. Please click the 'Link Account' button and refresh the account or select a different account.", `${account.displayInfo.displayName} (${account.displayInfo.userId})`);
|
return localize('azure.accounts.accountStaleError', "The access token for selected account '{0}' is no longer valid. Please click the 'Link Account' button and refresh the account or select a different account.", `${account.displayInfo.displayName} (${account.displayInfo.userId})`);
|
||||||
}
|
}
|
||||||
|
export function ACCOUNT_ACCESS_ERROR(account: AzureAccount, error: Error) {
|
||||||
|
return localize('azure.accounts.accountAccessError', "An error occurred while accessing the selected account '{0}'. Please click the 'Link Account' button and refresh the account or select a different account. Error '{1}'", `${account.displayInfo.displayName} (${account.displayInfo.userId})`, error.message);
|
||||||
|
}
|
||||||
|
|
||||||
// database backup page
|
// database backup page
|
||||||
export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database Backup");
|
export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database Backup");
|
||||||
@@ -330,7 +333,7 @@ export const SOURCE_VERSION = localize('sql.migration.source.version', "Source v
|
|||||||
export const TARGET_DATABASE_NAME = localize('sql.migration.target.database.name', "Target database name");
|
export const TARGET_DATABASE_NAME = localize('sql.migration.target.database.name', "Target database name");
|
||||||
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
||||||
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
||||||
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration status");
|
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration Status");
|
||||||
export const MIGRATION_STATUS_FILTER = localize('sql.migration.migration.status.filter', "Migration status filter");
|
export const MIGRATION_STATUS_FILTER = localize('sql.migration.migration.status.filter', "Migration status filter");
|
||||||
export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files");
|
export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files");
|
||||||
export const LAST_APPLIED_LSN = localize('sql.migration.last.applied.lsn', "Last applied LSN");
|
export const LAST_APPLIED_LSN = localize('sql.migration.last.applied.lsn', "Last applied LSN");
|
||||||
@@ -372,6 +375,9 @@ export const CONFIRM_CUTOVER_CHECKBOX = localize('sql.migration.confirm.checkbox
|
|||||||
export function CUTOVER_IN_PROGRESS(dbName: string): string {
|
export function CUTOVER_IN_PROGRESS(dbName: string): string {
|
||||||
return localize('sql.migration.cutover.in.progress', "Cutover in progress for database '{0}'", dbName);
|
return localize('sql.migration.cutover.in.progress', "Cutover in progress for database '{0}'", dbName);
|
||||||
}
|
}
|
||||||
|
export const MIGRATION_CANNOT_CANCEL = localize('sql.migration.cannot.cancel', 'Migration is not in progress and cannot be cancelled.');
|
||||||
|
export const MIGRATION_CANNOT_CUTOVER = localize('sql.migration.cannot.cutover', 'Migration is not in progress and cannot be cutover.');
|
||||||
|
|
||||||
//Migration status dialog
|
//Migration status dialog
|
||||||
export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations");
|
export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations");
|
||||||
export const ONLINE = localize('sql.migration.online', "Online");
|
export const ONLINE = localize('sql.migration.online', "Online");
|
||||||
@@ -386,24 +392,47 @@ export const TARGET_AZURE_SQL_INSTANCE_NAME = localize('sql.migration.target.azu
|
|||||||
export const MIGRATION_MODE = localize('sql.migration.cutover.type', "Migration Mode");
|
export const MIGRATION_MODE = localize('sql.migration.cutover.type', "Migration Mode");
|
||||||
export const START_TIME = localize('sql.migration.start.time', "Start Time");
|
export const START_TIME = localize('sql.migration.start.time', "Start Time");
|
||||||
export const FINISH_TIME = localize('sql.migration.finish.time', "Finish Time");
|
export const FINISH_TIME = localize('sql.migration.finish.time', "Finish Time");
|
||||||
export function STATUS_WARNING_COUNT(status: string, count: number): string {
|
|
||||||
if (status === 'InProgress' || status === 'Creating' || status === 'Completing' || status === 'Creating') {
|
export function STATUS_VALUE(status: string, count: number): string {
|
||||||
|
if (count > 0) {
|
||||||
|
return localize('sql.migration.status.error.count.some', "{0} (", StatusLookup[status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localize('sql.migration.status.error.count.none', "{0}", StatusLookup[status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LookupTable<T> {
|
||||||
|
[key: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusLookup: LookupTable<string | undefined> = {
|
||||||
|
['InProgress']: localize('sql.migration.status.inprogress', 'In progress'),
|
||||||
|
['Succeeded']: localize('sql.migration.status.succeeded', 'Succeeded'),
|
||||||
|
['Creating']: localize('sql.migration.status.creating', 'Creating'),
|
||||||
|
['Completing']: localize('sql.migration.status.completing', 'Completing'),
|
||||||
|
['Cancelling']: localize('sql.migration.status.cancelling', 'Cancelling'),
|
||||||
|
['Failed']: localize('sql.migration.status.failed', 'Failed'),
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function STATUS_WARNING_COUNT(status: string, count: number): string | undefined {
|
||||||
|
if (status === 'InProgress' || status === 'Creating' || status === 'Completing') {
|
||||||
switch (count) {
|
switch (count) {
|
||||||
case 0:
|
case 0:
|
||||||
return localize('sql.migration.status.warning.count.none', "{0}", status);
|
return undefined;
|
||||||
case 1:
|
case 1:
|
||||||
return localize('sql.migration.status.warning.count.single', "{0} ({1} Warning)", status, count);
|
return localize('sql.migration.status.warning.count.single', "{0} Warning)", count);
|
||||||
default:
|
default:
|
||||||
return localize('sql.migration.status.warning.count.multiple', "{0} ({1} Warnings)", status, count);
|
return localize('sql.migration.status.warning.count.multiple', "{0} Warnings)", count);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (count) {
|
switch (count) {
|
||||||
case 0:
|
case 0:
|
||||||
return localize('sql.migration.status.error.count.none', "{0}", status);
|
return undefined;
|
||||||
case 1:
|
case 1:
|
||||||
return localize('sql.migration.status.error.count.single', "{0} ({1} Error)", status, count);
|
return localize('sql.migration.status.error.count.single', "{0} Error)", count);
|
||||||
default:
|
default:
|
||||||
return localize('sql.migration.status.error.count.multiple', "{0} ({1} Errors)", status, count);
|
return localize('sql.migration.status.error.count.multiple', "{0} Errors)", count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,6 +503,8 @@ export const AUTHENTICATION_TYPE = localize('sql.migration.authentication.type',
|
|||||||
export const SQL_LOGIN = localize('sql.migration.sql.login', "SQL Login");
|
export const SQL_LOGIN = localize('sql.migration.sql.login', "SQL Login");
|
||||||
export const WINDOWS_AUTHENTICATION = localize('sql.migration.windows.auth', "Windows Authentication");
|
export const WINDOWS_AUTHENTICATION = localize('sql.migration.windows.auth', "Windows Authentication");
|
||||||
|
|
||||||
|
export const REFRESH_BUTTON_LABEL = localize('sql.migration.status.refresh.label', 'Refresh');
|
||||||
|
|
||||||
//AutoRefresh
|
//AutoRefresh
|
||||||
export function AUTO_REFRESH_BUTTON_TEXT(interval: SupportedAutoRefreshIntervals): string {
|
export function AUTO_REFRESH_BUTTON_TEXT(interval: SupportedAutoRefreshIntervals): string {
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
|
|||||||
@@ -39,13 +39,11 @@ export class MigrationCutoverDialog {
|
|||||||
private _lastAppliedLSN!: azdata.TextComponent;
|
private _lastAppliedLSN!: azdata.TextComponent;
|
||||||
private _lastAppliedBackupFile!: azdata.TextComponent;
|
private _lastAppliedBackupFile!: azdata.TextComponent;
|
||||||
private _lastAppliedBackupTakenOn!: azdata.TextComponent;
|
private _lastAppliedBackupTakenOn!: azdata.TextComponent;
|
||||||
|
|
||||||
private _fileCount!: azdata.TextComponent;
|
private _fileCount!: azdata.TextComponent;
|
||||||
|
|
||||||
private fileTable!: azdata.TableComponent;
|
private fileTable!: azdata.TableComponent;
|
||||||
private _autoRefreshHandle!: any;
|
private _autoRefreshHandle!: any;
|
||||||
readonly _infoFieldWidth: string = '250px';
|
|
||||||
|
|
||||||
|
readonly _infoFieldWidth: string = '250px';
|
||||||
|
|
||||||
constructor(migration: MigrationContext) {
|
constructor(migration: MigrationContext) {
|
||||||
this._model = new MigrationCutoverDialogModel(migration);
|
this._model = new MigrationCutoverDialogModel(migration);
|
||||||
@@ -55,6 +53,7 @@ export class MigrationCutoverDialog {
|
|||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
let tab = azdata.window.createTab('');
|
let tab = azdata.window.createTab('');
|
||||||
tab.registerContent(async (view: azdata.ModelView) => {
|
tab.registerContent(async (view: azdata.ModelView) => {
|
||||||
|
try {
|
||||||
this._view = view;
|
this._view = view;
|
||||||
const sourceDatabase = this.createInfoField(loc.SOURCE_DATABASE, '');
|
const sourceDatabase = this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||||
const sourceDetails = this.createInfoField(loc.SOURCE_SERVER, '');
|
const sourceDetails = this.createInfoField(loc.SOURCE_SERVER, '');
|
||||||
@@ -116,7 +115,6 @@ export class MigrationCutoverDialog {
|
|||||||
const fullBackupFileOn = this.createInfoField(loc.FULL_BACKUP_FILES, '');
|
const fullBackupFileOn = this.createInfoField(loc.FULL_BACKUP_FILES, '');
|
||||||
const backupLocation = this.createInfoField(loc.BACKUP_LOCATION, '');
|
const backupLocation = this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||||
|
|
||||||
|
|
||||||
this._migrationStatus = migrationStatus.text;
|
this._migrationStatus = migrationStatus.text;
|
||||||
this._fullBackupFile = fullBackupFileOn.text;
|
this._fullBackupFile = fullBackupFileOn.text;
|
||||||
this._backupLocation = backupLocation.text;
|
this._backupLocation = backupLocation.text;
|
||||||
@@ -145,7 +143,6 @@ export class MigrationCutoverDialog {
|
|||||||
const lastAppliedBackup = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
const lastAppliedBackup = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||||
const lastAppliedBackupOn = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '');
|
const lastAppliedBackupOn = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '');
|
||||||
|
|
||||||
|
|
||||||
this._lastAppliedLSN = lastSSN.text;
|
this._lastAppliedLSN = lastSSN.text;
|
||||||
this._lastAppliedBackupFile = lastAppliedBackup.text;
|
this._lastAppliedBackupFile = lastAppliedBackup.text;
|
||||||
this._lastAppliedBackupTakenOn = lastAppliedBackupOn.text;
|
this._lastAppliedBackupTakenOn = lastAppliedBackupOn.text;
|
||||||
@@ -263,36 +260,26 @@ export class MigrationCutoverDialog {
|
|||||||
|
|
||||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||||
[
|
[
|
||||||
{
|
{ component: this.migrationContainerHeader() },
|
||||||
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._view.modelBuilder.separator().withProps({ width: 1000 }).component()
|
{ component: this._fileCount },
|
||||||
},
|
{ component: this.fileTable }
|
||||||
{
|
|
||||||
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();
|
||||||
this._view.onClosed(e => {
|
this._view.onClosed(e => {
|
||||||
clearInterval(this._autoRefreshHandle);
|
clearInterval(this._autoRefreshHandle);
|
||||||
});
|
});
|
||||||
|
|
||||||
return view.initializeModel(form).then((value) => {
|
return view.initializeModel(form).then((value) => {
|
||||||
this.refreshStatus();
|
this.refreshStatus();
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this._dialogObject.content = [tab];
|
this._dialogObject.content = [tab];
|
||||||
|
|
||||||
@@ -305,7 +292,6 @@ export class MigrationCutoverDialog {
|
|||||||
azdata.window.openDialog(this._dialogObject);
|
azdata.window.openDialog(this._dialogObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private migrationContainerHeader(): azdata.FlexContainer {
|
private migrationContainerHeader(): azdata.FlexContainer {
|
||||||
const sqlDatbaseLogo = this._view.modelBuilder.image().withProps({
|
const sqlDatbaseLogo = this._view.modelBuilder.image().withProps({
|
||||||
iconPath: IconPathHelper.sqlDatabaseLogo,
|
iconPath: IconPathHelper.sqlDatabaseLogo,
|
||||||
@@ -404,7 +390,7 @@ export class MigrationCutoverDialog {
|
|||||||
this._cancelButton.onDidClick((e) => {
|
this._cancelButton.onDidClick((e) => {
|
||||||
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
|
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
|
||||||
if (v === loc.YES) {
|
if (v === loc.YES) {
|
||||||
await this.cancelMigration();
|
await this._model.cancelMigration();
|
||||||
await this.refreshStatus();
|
await this.refreshStatus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -427,9 +413,8 @@ export class MigrationCutoverDialog {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._refreshButton.onDidClick((e) => {
|
this._refreshButton.onDidClick(
|
||||||
this.refreshStatus();
|
async (e) => await this.refreshStatus());
|
||||||
});
|
|
||||||
|
|
||||||
headerActions.addItem(this._refreshButton, {
|
headerActions.addItem(this._refreshButton, {
|
||||||
flex: '0',
|
flex: '0',
|
||||||
@@ -664,11 +649,6 @@ export class MigrationCutoverDialog {
|
|||||||
text: textComponent
|
text: textComponent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cancelMigration(): Promise<void> {
|
|
||||||
await this._model.cancelMigration();
|
|
||||||
await this.refreshStatus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActiveBackupFileSchema {
|
interface ActiveBackupFileSchema {
|
||||||
|
|||||||
@@ -12,9 +12,15 @@ import { AdsMigrationStatus, MigrationStatusDialogModel } from './migrationStatu
|
|||||||
import * as loc from '../../constants/strings';
|
import * as loc from '../../constants/strings';
|
||||||
import { convertTimeDifferenceToDuration, filterMigrations, SupportedAutoRefreshIntervals } from '../../api/utils';
|
import { convertTimeDifferenceToDuration, filterMigrations, SupportedAutoRefreshIntervals } from '../../api/utils';
|
||||||
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
||||||
|
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
|
||||||
|
import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel';
|
||||||
|
|
||||||
const refreshFrequency: SupportedAutoRefreshIntervals = 180000;
|
const refreshFrequency: SupportedAutoRefreshIntervals = 180000;
|
||||||
|
|
||||||
|
const statusImageSize: number = 14;
|
||||||
|
const imageCellStyles: azdata.CssStyles = { 'margin': '3px 3px 0 0', 'padding': '0' };
|
||||||
|
const statusCellStyles: azdata.CssStyles = { 'margin': '0', 'padding': '0' };
|
||||||
|
|
||||||
export class MigrationStatusDialog {
|
export class MigrationStatusDialog {
|
||||||
private _model: MigrationStatusDialogModel;
|
private _model: MigrationStatusDialogModel;
|
||||||
private _dialogObject!: azdata.window.Dialog;
|
private _dialogObject!: azdata.window.Dialog;
|
||||||
@@ -52,6 +58,7 @@ export class MigrationStatusDialog {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.registerCommands();
|
||||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -83,6 +90,9 @@ export class MigrationStatusDialog {
|
|||||||
azdata.window.openDialog(this._dialogObject);
|
azdata.window.openDialog(this._dialogObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private canCancelMigration = (status: string | undefined) => status && status in ['InProgress', 'Creating', 'Completing', 'Creating'];
|
||||||
|
private canCutoverMigration = (status: string | undefined) => status === 'InProgress';
|
||||||
|
|
||||||
private createSearchAndRefreshContainer(): azdata.FlexContainer {
|
private createSearchAndRefreshContainer(): azdata.FlexContainer {
|
||||||
this._searchBox = this._view.modelBuilder.inputBox().withProps({
|
this._searchBox = this._view.modelBuilder.inputBox().withProps({
|
||||||
stopEnterPropagation: true,
|
stopEnterPropagation: true,
|
||||||
@@ -102,7 +112,7 @@ export class MigrationStatusDialog {
|
|||||||
iconHeight: '16px',
|
iconHeight: '16px',
|
||||||
iconWidth: '20px',
|
iconWidth: '20px',
|
||||||
height: '30px',
|
height: '30px',
|
||||||
label: 'Refresh',
|
label: loc.REFRESH_BUTTON_LABEL,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._refresh.onDidClick((e) => {
|
this._refresh.onDidClick((e) => {
|
||||||
@@ -152,122 +162,291 @@ export class MigrationStatusDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
||||||
let classVariable = this;
|
const classVariable = this;
|
||||||
clearInterval(this._autoRefreshHandle);
|
clearInterval(this._autoRefreshHandle);
|
||||||
if (interval !== -1) {
|
if (interval !== -1) {
|
||||||
this._autoRefreshHandle = setInterval(function () { classVariable.refreshTable(); }, interval);
|
this._autoRefreshHandle = setInterval(function () { classVariable.refreshTable(); }, interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private populateMigrationTable(): void {
|
private registerCommands(): void {
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.cutover',
|
||||||
|
async (migrationId: string) => {
|
||||||
try {
|
try {
|
||||||
const migrations = filterMigrations(this._model._migrations, (<azdata.CategoryValue>this._statusDropdown.value).name, this._searchBox.value!);
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
if (this.canCutoverMigration(migration?.migrationContext.properties.migrationStatus)) {
|
||||||
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
||||||
|
await cutoverDialogModel.fetchStatus();
|
||||||
|
const dialog = new ConfirmCutoverDialog(cutoverDialogModel);
|
||||||
|
await dialog.initialize();
|
||||||
|
} else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CUTOVER);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const data: azdata.DeclarativeTableCellValue[][] = [];
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.view.database',
|
||||||
|
async (migrationId: string) => {
|
||||||
|
try {
|
||||||
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
const dialog = new MigrationCutoverDialog(migration!);
|
||||||
|
await dialog.initialize();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.view.target',
|
||||||
|
async (migrationId: string) => {
|
||||||
|
try {
|
||||||
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
const url = 'https://portal.azure.com/#resource/' + migration!.targetManagedInstance.id;
|
||||||
|
await vscode.env.openExternal(vscode.Uri.parse(url));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.view.service',
|
||||||
|
async (migrationId: string) => {
|
||||||
|
try {
|
||||||
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
const dialog = new SqlMigrationServiceDetailsDialog(migration!);
|
||||||
|
await dialog.initialize();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.copy.migration',
|
||||||
|
async (migrationId: string) => {
|
||||||
|
try {
|
||||||
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
||||||
|
await cutoverDialogModel.fetchStatus();
|
||||||
|
if (cutoverDialogModel.migrationOpStatus) {
|
||||||
|
await vscode.env.clipboard.writeText(JSON.stringify({
|
||||||
|
'async-operation-details': cutoverDialogModel.migrationOpStatus,
|
||||||
|
'details': cutoverDialogModel.migrationStatus
|
||||||
|
}, undefined, 2));
|
||||||
|
} else {
|
||||||
|
await vscode.env.clipboard.writeText(JSON.stringify(cutoverDialogModel.migrationStatus, undefined, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
await vscode.window.showInformationMessage(loc.DETAILS_COPIED);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
'sqlmigration.cancel.migration',
|
||||||
|
async (migrationId: string) => {
|
||||||
|
try {
|
||||||
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
||||||
|
if (this.canCancelMigration(migration?.migrationContext.properties.migrationStatus)) {
|
||||||
|
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
|
||||||
|
if (v === loc.YES) {
|
||||||
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
||||||
|
await cutoverDialogModel.fetchStatus();
|
||||||
|
await cutoverDialogModel.cancelMigration();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CANCEL);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async populateMigrationTable(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const migrations = filterMigrations(
|
||||||
|
this._model._migrations,
|
||||||
|
(<azdata.CategoryValue>this._statusDropdown.value).name,
|
||||||
|
this._searchBox.value!);
|
||||||
|
|
||||||
migrations.sort((m1, m2) => {
|
migrations.sort((m1, m2) => {
|
||||||
return new Date(m1.migrationContext.properties.startedOn) > new Date(m2.migrationContext.properties.startedOn) ? -1 : 1;
|
return new Date(m1.migrationContext.properties.startedOn) > new Date(m2.migrationContext.properties.startedOn) ? -1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
migrations.forEach((migration, index) => {
|
const data: azdata.DeclarativeTableCellValue[][] = migrations.map((migration, index) => {
|
||||||
const migrationRow: azdata.DeclarativeTableCellValue[] = [];
|
return [
|
||||||
|
{ value: this._getDatabaserHyperLink(migration) },
|
||||||
const databaseHyperLink = this._view.modelBuilder.hyperlink().withProps({
|
{ value: this._getMigrationStatus(migration) },
|
||||||
label: migration.migrationContext.properties.sourceDatabaseName,
|
{ value: loc.ONLINE },
|
||||||
url: ''
|
{ value: this._getMigrationTargetType(migration) },
|
||||||
}).component();
|
{ value: migration.targetManagedInstance.name },
|
||||||
databaseHyperLink.onDidClick(async (e) => {
|
{ value: migration.controller.name },
|
||||||
await (new MigrationCutoverDialog(migration)).initialize();
|
{
|
||||||
});
|
value: this._getMigrationDuration(
|
||||||
migrationRow.push({
|
migration.migrationContext.properties.startedOn,
|
||||||
value: databaseHyperLink,
|
migration.migrationContext.properties.endedOn)
|
||||||
});
|
},
|
||||||
|
{ value: this._getMigrationTime(migration.migrationContext.properties.startedOn) },
|
||||||
migrationRow.push({
|
{ value: this._getMigrationTime(migration.migrationContext.properties.endedOn) },
|
||||||
value: (migration.targetManagedInstance.type === 'microsoft.sql/managedinstances') ? loc.SQL_MANAGED_INSTANCE : loc.SQL_VIRTUAL_MACHINE
|
{
|
||||||
});
|
value: {
|
||||||
|
commands: [
|
||||||
const sqlMigrationName = this._view.modelBuilder.hyperlink().withProps({
|
'sqlmigration.cutover',
|
||||||
label: migration.targetManagedInstance.name,
|
'sqlmigration.view.database',
|
||||||
url: ''
|
'sqlmigration.view.target',
|
||||||
}).component();
|
'sqlmigration.view.service',
|
||||||
sqlMigrationName.onDidClick((e) => {
|
'sqlmigration.copy.migration',
|
||||||
vscode.window.showInformationMessage(loc.COMING_SOON);
|
'sqlmigration.cancel.migration',
|
||||||
});
|
],
|
||||||
|
context: migration.migrationContext.id
|
||||||
migrationRow.push({
|
},
|
||||||
value: sqlMigrationName
|
|
||||||
});
|
|
||||||
|
|
||||||
const dms = this._view.modelBuilder.hyperlink().withProps({
|
|
||||||
label: migration.controller.name,
|
|
||||||
url: ''
|
|
||||||
}).component();
|
|
||||||
dms.onDidClick((e) => {
|
|
||||||
(new SqlMigrationServiceDetailsDialog(migration)).initialize();
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationRow.push({
|
|
||||||
value: dms
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationRow.push({
|
|
||||||
value: loc.ONLINE
|
|
||||||
});
|
|
||||||
|
|
||||||
let migrationStatus = migration.migrationContext.properties.migrationStatus ? migration.migrationContext.properties.migrationStatus : migration.migrationContext.properties.provisioningState;
|
|
||||||
|
|
||||||
let warningCount = 0;
|
|
||||||
|
|
||||||
if (migration.asyncOperationResult?.error?.message) {
|
|
||||||
warningCount++;
|
|
||||||
}
|
}
|
||||||
if (migration.migrationContext.properties.migrationFailureError?.message) {
|
];
|
||||||
warningCount++;
|
|
||||||
}
|
|
||||||
if (migration.migrationContext.properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
|
||||||
warningCount += migration.migrationContext.properties.migrationStatusDetails?.fileUploadBlockingErrors.length;
|
|
||||||
}
|
|
||||||
if (migration.migrationContext.properties.migrationStatusDetails?.restoreBlockingReason) {
|
|
||||||
warningCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationRow.push({
|
|
||||||
value: loc.STATUS_WARNING_COUNT(migrationStatus, warningCount)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let duration;
|
await this._statusTable.setDataValues(data);
|
||||||
if (migration.migrationContext.properties.endedOn) {
|
|
||||||
duration = convertTimeDifferenceToDuration(new Date(migration.migrationContext.properties.startedOn), new Date(migration.migrationContext.properties.endedOn));
|
|
||||||
} else {
|
|
||||||
duration = convertTimeDifferenceToDuration(new Date(migration.migrationContext.properties.startedOn), new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationRow.push({
|
|
||||||
value: (migration.migrationContext.properties.startedOn) ? duration : '---'
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationRow.push({
|
|
||||||
value: (migration.migrationContext.properties.startedOn) ? new Date(migration.migrationContext.properties.startedOn).toLocaleString() : '---'
|
|
||||||
});
|
|
||||||
migrationRow.push({
|
|
||||||
value: (migration.migrationContext.properties.endedOn) ? new Date(migration.migrationContext.properties.endedOn).toLocaleString() : '---'
|
|
||||||
});
|
|
||||||
|
|
||||||
data.push(migrationRow);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._statusTable.dataValues = data;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getDatabaserHyperLink(migration: MigrationContext): azdata.FlexContainer {
|
||||||
|
const imageControl = this._view.modelBuilder.image()
|
||||||
|
.withProps({
|
||||||
|
iconPath: IconPathHelper.sqlDatabaseLogo,
|
||||||
|
iconHeight: statusImageSize,
|
||||||
|
iconWidth: statusImageSize,
|
||||||
|
height: statusImageSize,
|
||||||
|
width: statusImageSize,
|
||||||
|
CSSStyles: imageCellStyles
|
||||||
|
})
|
||||||
|
.component();
|
||||||
|
|
||||||
|
const databaseHyperLink = this._view.modelBuilder
|
||||||
|
.hyperlink()
|
||||||
|
.withProps({
|
||||||
|
label: migration.migrationContext.properties.sourceDatabaseName,
|
||||||
|
url: '',
|
||||||
|
CSSStyles: statusCellStyles
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
databaseHyperLink.onDidClick(
|
||||||
|
async (e) => await (new MigrationCutoverDialog(migration)).initialize());
|
||||||
|
|
||||||
|
return this._view.modelBuilder
|
||||||
|
.flexContainer()
|
||||||
|
.withItems([imageControl, databaseHyperLink])
|
||||||
|
.withProps({ CSSStyles: statusCellStyles, display: 'inline-flex' })
|
||||||
|
.component();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMigrationTime(migrationTime: string): string {
|
||||||
|
return migrationTime
|
||||||
|
? new Date(migrationTime).toLocaleString()
|
||||||
|
: '---';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMigrationDuration(startDate: string, endDate: string): string {
|
||||||
|
if (startDate) {
|
||||||
|
if (endDate) {
|
||||||
|
return convertTimeDifferenceToDuration(
|
||||||
|
new Date(startDate),
|
||||||
|
new Date(endDate));
|
||||||
|
} else {
|
||||||
|
return convertTimeDifferenceToDuration(
|
||||||
|
new Date(startDate),
|
||||||
|
new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '---';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMigrationTargetType(migration: MigrationContext): string {
|
||||||
|
return migration.targetManagedInstance.type === 'microsoft.sql/managedinstances'
|
||||||
|
? loc.SQL_MANAGED_INSTANCE
|
||||||
|
: loc.SQL_VIRTUAL_MACHINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMigrationStatus(migration: MigrationContext): azdata.FlexContainer {
|
||||||
|
const properties = migration.migrationContext.properties;
|
||||||
|
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
||||||
|
let warningCount = 0;
|
||||||
|
if (migration.asyncOperationResult?.error?.message) {
|
||||||
|
warningCount++;
|
||||||
|
}
|
||||||
|
if (properties.migrationFailureError?.message) {
|
||||||
|
warningCount++;
|
||||||
|
}
|
||||||
|
if (properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
||||||
|
warningCount += properties.migrationStatusDetails?.fileUploadBlockingErrors.length;
|
||||||
|
}
|
||||||
|
if (properties.migrationStatusDetails?.restoreBlockingReason) {
|
||||||
|
warningCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getStatusControl(migrationStatus, warningCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getStatusControl(status: string, count: number): azdata.FlexContainer {
|
||||||
|
const control = this._view.modelBuilder
|
||||||
|
.flexContainer()
|
||||||
|
.withItems([
|
||||||
|
// migration status icon
|
||||||
|
this._view.modelBuilder.image()
|
||||||
|
.withProps({
|
||||||
|
iconPath: this._statusImageMap(status),
|
||||||
|
iconHeight: statusImageSize,
|
||||||
|
iconWidth: statusImageSize,
|
||||||
|
height: statusImageSize,
|
||||||
|
width: statusImageSize,
|
||||||
|
CSSStyles: imageCellStyles
|
||||||
|
})
|
||||||
|
.component(),
|
||||||
|
// migration status text
|
||||||
|
this._view.modelBuilder.text().withProps({
|
||||||
|
value: loc.STATUS_VALUE(status, count),
|
||||||
|
height: statusImageSize,
|
||||||
|
CSSStyles: statusCellStyles,
|
||||||
|
}).component()
|
||||||
|
])
|
||||||
|
.withProps({ CSSStyles: statusCellStyles, display: 'inline-flex' })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
control.addItems([
|
||||||
|
// migration warning / error image
|
||||||
|
this._view.modelBuilder.image().withProps({
|
||||||
|
iconPath: this._statusInfoMap(status),
|
||||||
|
iconHeight: statusImageSize,
|
||||||
|
iconWidth: statusImageSize,
|
||||||
|
height: statusImageSize,
|
||||||
|
width: statusImageSize,
|
||||||
|
CSSStyles: imageCellStyles
|
||||||
|
}).component(),
|
||||||
|
// migration warning / error counts
|
||||||
|
this._view.modelBuilder.text().withProps({
|
||||||
|
value: loc.STATUS_WARNING_COUNT(status, count),
|
||||||
|
height: statusImageSize,
|
||||||
|
CSSStyles: statusCellStyles,
|
||||||
|
}).component()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshTable(): Promise<void> {
|
private async refreshTable(): Promise<void> {
|
||||||
this._refreshLoader.loading = true;
|
this._refreshLoader.loading = true;
|
||||||
const currentConnection = await azdata.connection.getCurrentConnection();
|
const currentConnection = await azdata.connection.getCurrentConnection();
|
||||||
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
|
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
|
||||||
this.populateMigrationTable();
|
await this.populateMigrationTable();
|
||||||
this._refreshLoader.loading = false;
|
this._refreshLoader.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,25 +477,9 @@ export class MigrationStatusDialog {
|
|||||||
headerCssStyles: headerCssStyles
|
headerCssStyles: headerCssStyles
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: loc.AZURE_SQL_TARGET,
|
displayName: loc.MIGRATION_STATUS,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '140px',
|
|
||||||
isReadOnly: true,
|
|
||||||
rowCssStyles: rowCssStyle,
|
|
||||||
headerCssStyles: headerCssStyles
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
width: '130px',
|
width: '170px',
|
||||||
isReadOnly: true,
|
|
||||||
rowCssStyles: rowCssStyle,
|
|
||||||
headerCssStyles: headerCssStyles
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.DATABASE_MIGRATION_SERVICE,
|
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
|
||||||
width: '150px',
|
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyles
|
headerCssStyles: headerCssStyles
|
||||||
@@ -324,13 +487,29 @@ export class MigrationStatusDialog {
|
|||||||
{
|
{
|
||||||
displayName: loc.MIGRATION_MODE,
|
displayName: loc.MIGRATION_MODE,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '100px',
|
width: '90px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyles
|
headerCssStyles: headerCssStyles
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: loc.MIGRATION_STATUS,
|
displayName: loc.AZURE_SQL_TARGET,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
width: '130px',
|
||||||
|
isReadOnly: true,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyles
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
width: '130px',
|
||||||
|
isReadOnly: true,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyles
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.DATABASE_MIGRATION_SERVICE,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '150px',
|
width: '150px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
@@ -348,7 +527,7 @@ export class MigrationStatusDialog {
|
|||||||
{
|
{
|
||||||
displayName: loc.START_TIME,
|
displayName: loc.START_TIME,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '120px',
|
width: '140px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyles
|
headerCssStyles: headerCssStyles
|
||||||
@@ -356,13 +535,50 @@ export class MigrationStatusDialog {
|
|||||||
{
|
{
|
||||||
displayName: loc.FINISH_TIME,
|
displayName: loc.FINISH_TIME,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '120px',
|
width: '140px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyles
|
headerCssStyles: headerCssStyles
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: '',
|
||||||
|
valueType: azdata.DeclarativeDataType.menu,
|
||||||
|
width: '20px',
|
||||||
|
isReadOnly: true,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyles,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).component();
|
}).component();
|
||||||
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 'Cancelling':
|
||||||
|
return IconPathHelper.cancel;
|
||||||
|
case 'Failed':
|
||||||
|
default:
|
||||||
|
return IconPathHelper.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _statusInfoMap(status: string): azdata.IconPath {
|
||||||
|
switch (status) {
|
||||||
|
case 'InProgress':
|
||||||
|
case 'Creating':
|
||||||
|
case 'Completing':
|
||||||
|
return IconPathHelper.warning;
|
||||||
|
default:
|
||||||
|
return IconPathHelper.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
|||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||||
import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
|
import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
|
||||||
|
import { getSubscriptions } from '../api/azure';
|
||||||
|
|
||||||
export class AccountsSelectionPage extends MigrationWizardPage {
|
export class AccountsSelectionPage extends MigrationWizardPage {
|
||||||
private _azureAccountsDropdown!: azdata.DropDownComponent;
|
private _azureAccountsDropdown!: azdata.DropDownComponent;
|
||||||
@@ -198,14 +199,21 @@ export class AccountsSelectionPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(): Promise<void> {
|
public async onPageEnter(): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator(pageChangeInfo => {
|
this.wizard.registerNavigationValidator(async pageChangeInfo => {
|
||||||
if (this.migrationStateModel._azureAccount?.isStale === true) {
|
try {
|
||||||
this.wizard.message = {
|
if (!this.migrationStateModel._azureAccount?.isStale) {
|
||||||
text: constants.ACCOUNT_STALE_ERROR(this.migrationStateModel._azureAccount)
|
const subscriptions = await getSubscriptions(this.migrationStateModel._azureAccount);
|
||||||
};
|
if (subscriptions?.length > 0) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wizard.message = { text: constants.ACCOUNT_STALE_ERROR(this.migrationStateModel._azureAccount) };
|
||||||
|
} catch (error) {
|
||||||
|
this.wizard.message = { text: constants.ACCOUNT_ACCESS_ERROR(this.migrationStateModel._azureAccount, error) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user