Login Migration Pre Validation Changes (#25897)
* Login Migration Pre Validation Changes * Minor string changes
This commit is contained in:
Родитель
03b386a375
Коммит
ebe265ccb1
|
@ -480,8 +480,9 @@ export function LOGIN_MIGRATIONS_GET_LOGINS_ERROR_TITLE(targetType: string): str
|
|||
export function LOGIN_MIGRATIONS_GET_LOGINS_ERROR(message: string): string {
|
||||
return localize('sql.migration.wizard.target.login.error', "Error getting login information: {0}", message);
|
||||
}
|
||||
export const SELECT_LOGIN_TO_CONTINUE = localize('sql.migration.select.database.to.continue', "Please select 1 or more logins for migration");
|
||||
export const ENTER_AAD_DOMAIN_NAME = localize('sql.login.migration.enter.AAD.domain.name.to.continue', "Microsoft Entra ID Domain name is required to migrate Windows login. Please enter an AAD Domain Name or deselect windows login(s).");
|
||||
export const SELECT_LOGIN_TO_CONTINUE = localize('sql.migration.select.login.to.continue', "Select one or more logins to run validation.");
|
||||
export const SELECT_LOGIN_AND_RUN_VALIDATION_TO_CONTINUE = localize('sql.migration.select.login.run.validation.to.continue', "To perform login migration, please run validation for one or more logins.");
|
||||
export const ENTER_ENTRA_ID = localize('sql.login.migration.enter.entra.id.to.continue', "Microsoft Entra Domain is required to migrate Windows login. Please enter Microsoft Entra Domain name or deselect windows login(s)");
|
||||
export const LOGIN_MIGRATE_BUTTON_TEXT = localize('sql.migration.start.login.migration.button', "Migrate");
|
||||
export function LOGIN_MIGRATIONS_GET_CONNECTION_STRING(dataSource: string, id: string, pass: string): string {
|
||||
return localize('sql.login.migration.get.connection.string', "data source={0};initial catalog=master;user id={1};password={2};TrustServerCertificate=True;Integrated Security=false;", dataSource, id, pass);
|
||||
|
@ -509,8 +510,8 @@ export const LOGINS_NOT_FOUND = localize('sql.login.migration.logins.not.found',
|
|||
export const LOGIN_MIGRATION_STATUS_SUCCEEDED = localize('sql.login.migration.status.succeeded', "Succeeded");
|
||||
export const LOGIN_MIGRATION_STATUS_FAILED = localize('sql.login.migration.status.failed', "Failed");
|
||||
export const LOGIN_MIGRATION_STATUS_IN_PROGRESS = localize('sql.login.migration.status.in.progress', "In progress");
|
||||
export const LOGIN_MIGRATIONS_AAD_DOMAIN_NAME_INPUT_BOX_LABEL = localize('sql.login.migration.aad.domain.name.input.box.label', "Microsoft Entra ID Domain Name (only required to migrate Windows Authenication Logins)");
|
||||
export const LOGIN_MIGRATIONS_AAD_DOMAIN_NAME_INPUT_BOX_PLACEHOLDER = localize('sql.login.migration.aad.domain.name.input.box.placeholder', "Enter AAD Domain Name");
|
||||
export const LOGIN_MIGRATIONS_ENTRA_ID_INPUT_BOX_LABEL = localize('sql.login.migration.entra.id.input.box.label', "Microsoft Entra Domain Name (only required to migrate Windows Authentication Logins)");
|
||||
export const LOGIN_MIGRATIONS_ENTRA_ID_INPUT_BOX_PLACEHOLDER = localize('sql.login.migration.entra.id.input.box.placeholder', "Enter Microsoft Entra Domain name");
|
||||
export function LOGIN_MIGRATIONS_LOGIN_STATUS_DETAILS_TITLE(loginName: string): string {
|
||||
return localize('sql.login.migration.login.status.details.title', "Migration status details for {0}", loginName);
|
||||
}
|
||||
|
@ -823,6 +824,7 @@ export const TABLE_SELECTION_HASROWS_COLUMN = localize('sql.migration.table.sele
|
|||
|
||||
export const VALIDATION_DIALOG_TITLE = localize('sql.migration.validation.dialog.title', "Running validation");
|
||||
export const VALIDATION_MESSAGE_SUCCESS = localize('sql.migration.validation.success', "Validation completed successfully. Please click Next to proceed with the migration.");
|
||||
export const LOGIN_MIGRATION_VALIDATION_MESSAGE_SUCCESS = localize('login.migration.validation.success', "Validation completed successfully. Please click migrate to proceed with the migration.");
|
||||
export function VALIDATION_MESSAGE_CANCELED_ERRORS(msg: string): string {
|
||||
return localize(
|
||||
'sql.migration.validation.canceled.errors',
|
||||
|
@ -1016,6 +1018,12 @@ export const VALIDATE_IR_COLUMN_VALIDATION_STEPS = localize('sql.migration.valid
|
|||
export const VALIDATE_IR_COLUMN_STATUS = localize('sql.migration.validate.ir.column.status', "Status");
|
||||
export const VALIDATE_IR_VALIDATION_RESULT_LABEL_SHIR = localize('sql.migration.validate.ir.validation.result.label.shir', "Integration runtime connectivity");
|
||||
export const VALIDATE_IR_VALIDATION_RESULT_LABEL_STORAGE = localize('sql.migration.validate.ir.validation.result.label.storage', "Azure storage connectivity");
|
||||
export const VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_SYSADMIN = localize('sql.migration.validate.login.migration.validation.result.label.sysadmin',
|
||||
"Validating the sysadmin permission on source and target");
|
||||
export const VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_ENTRAID = localize('sql.migration.validate.login.migration.validation.result.label.entraid',
|
||||
"Validating the microsoft entra id");
|
||||
export const VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_USERMAPPING = localize('sql.migration.validate.login.migration.validation.result.label.user.mapping',
|
||||
"Validating the user mapping");
|
||||
|
||||
export function VALIDATE_IR_VALIDATION_RESULT_LABEL_SOURCE_DATABASE(databaseName: string): string {
|
||||
return localize(
|
||||
|
@ -1079,6 +1087,113 @@ export function VALIDATION_IR_BUTTON_MISSING_ERROR_MESSAGE(details: string[]): s
|
|||
missingDetails);
|
||||
}
|
||||
|
||||
// Validate Login migration validation dialog
|
||||
export const VALIDATE_LOGIN_MIGRATION_DONE_BUTTON = localize('sql.migration.validate.login.migration.done.button', "Done");
|
||||
export const VALIDATE_LOGIN_MIGRATION_HEADING = localize('sql.migration.validate.login.migration.heading', "We are validating the following:");
|
||||
export const VALIDATE_LOGIN_MIGRATION_START_VALIDATION = localize('sql.migration.validate.login.migration.start.validation', "Start validation");
|
||||
export const VALIDATE_LOGIN_MIGRATION_UNSUCCESSFUL_REVALIDATION = localize('sql.migration.validate.login.migration.unsuccessful.revalidation', "Revalidate unsuccessful steps");
|
||||
export const VALIDATE_LOGIN_MIGRATION_STOP_VALIDATION = localize('sql.migration.validate.login.migration.stop.validation', "Stop validation");
|
||||
export const VALIDATE_LOGIN_MIGRATION_COPY_RESULTS = localize('sql.migration.validate.login.migration.copy.results', "Copy validation results");
|
||||
export const VALIDATE_LOGIN_MIGRATION_RESULTS_HEADING = localize('sql.migration.validate.login.migration.results.heading', "Validation step details");
|
||||
export const VALIDATE_LOGIN_MIGRATION_VALIDATION_COMPLETED = localize('sql.migration.validate.login.migration.validation.completed', "Validation completed successfully.");
|
||||
export const VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED = localize('sql.migration.validate.login.migration.validation.camceled', "Validation check canceled");
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_VALIDATION_COMPLETED_ERRORS(msg: string): string {
|
||||
return localize(
|
||||
'sql.migration.validate.login.migration.completed.errors',
|
||||
"Validation completed with the following error(s):{0}{1}", EOL, msg);
|
||||
}
|
||||
export function VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS(state: string | undefined, errors?: string[]): string {
|
||||
const status = state ?? '';
|
||||
if (errors && errors.length > 0) {
|
||||
return localize(
|
||||
'sql.migration.validate.login.migration.status.errors',
|
||||
"Validation status: {0}{1}{2}", status, EOL, errors.join(EOL));
|
||||
} else {
|
||||
return localize(
|
||||
'sql.migration.validate.login.migration.status',
|
||||
"Validation status: {0}", status);
|
||||
}
|
||||
}
|
||||
|
||||
export const VALIDATE_LOGIN_MIGRATION_ERROR_GATEWAY_TIMEOUT = localize('sql.migration.validate.error.gatewaytimeout', "A time-out was encountered while validating a resource connection. Learn more: https://aka.ms/dms-migrations-troubleshooting.");
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS_ERROR_COUNT(state: string | undefined, errorCount: number): string {
|
||||
const status = state ?? '';
|
||||
return errorCount > 1
|
||||
? localize(
|
||||
'sql.migration.validate.login.migration.status.error.count.many',
|
||||
"{0} - {1} errors",
|
||||
status,
|
||||
errorCount)
|
||||
: localize(
|
||||
'sql.migration.validate.login.migration.status.error.count.one',
|
||||
"{0} - 1 error",
|
||||
status);
|
||||
}
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS_ERROR(state: string | undefined, errors: string[]): string {
|
||||
const status = state ?? '';
|
||||
return localize(
|
||||
'sql.migration.validate.login.migration.status.error',
|
||||
"{0}{1}{2}",
|
||||
status,
|
||||
EOL,
|
||||
errors.join(EOL));
|
||||
}
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_SYSADMIN_PERMISSION_VALIDATION_RESULT_ERROR(serverName: string, error: any,): string {
|
||||
return localize(
|
||||
'sql.migration.validate.ir.sqldb.validation.result.error',
|
||||
"Sys Admin Permission Pre Validation check error{0}server: {1}{0}Error: {2} - {3}{0}Follow the steps mentioned here https://aka.ms/loginvalidationerror and re-run the validation before performing login/s migration.",
|
||||
EOL,
|
||||
serverName,
|
||||
error.ErrorCodeString,
|
||||
error.Message);
|
||||
}
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_ENTRA_ID_VALIDATION_RESULT_ERROR(entraID: string, error: any,): string {
|
||||
return localize(
|
||||
'sql.migration.validate.ir.sqldb.validation.result.error',
|
||||
"Entra ID Pre Validation check error{0}Entra ID: {1}{0}Error: {2} - {3}{0}Follow the steps mentioned here https://aka.ms/loginvalidationerror and re-run the validation or uncheck the failed logins before performing login/s migration.",
|
||||
EOL,
|
||||
entraID,
|
||||
error.ErrorCodeString,
|
||||
error.Message);
|
||||
}
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_USER_MAPPING_VALIDATION_RESULT_ERROR(name: string, error: any,): string {
|
||||
return localize(
|
||||
'sql.migration.validate.ir.sqldb.validation.result.error',
|
||||
"User Mapping Pre Validation check error{0}Name: {1}{0}Error: {2} - {3}{0}Follow the steps mentioned here https://aka.ms/loginvalidationerror and re-run the validation or uncheck the failed logins before performing login/s migration.",
|
||||
EOL,
|
||||
name,
|
||||
error.ErrorCodeString,
|
||||
error.Message);
|
||||
}
|
||||
|
||||
export function GET_LOGIN_MIGRATION_VALIDATION_ERROR(validationFunctionName: any, name: string, error: any): any {
|
||||
switch (validationFunctionName) {
|
||||
case 'validateUserMapping':
|
||||
return VALIDATE_LOGIN_MIGRATION_USER_MAPPING_VALIDATION_RESULT_ERROR(name, error);
|
||||
case 'validateAADDomainName':
|
||||
return VALIDATE_LOGIN_MIGRATION_ENTRA_ID_VALIDATION_RESULT_ERROR(name, error)
|
||||
case 'validateSysAdminPermission':
|
||||
return VALIDATE_LOGIN_MIGRATION_SYSADMIN_PERMISSION_VALIDATION_RESULT_ERROR(name, error)
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_API_ERROR(error: Error): string {
|
||||
return localize(
|
||||
'sql.migration.validate.login.migration.validation.result.api.error',
|
||||
"Validation check error{0}Error: {1} - {2}",
|
||||
EOL,
|
||||
error.name,
|
||||
error.message);
|
||||
}
|
||||
|
||||
// common strings
|
||||
export const WARNING = localize('sql.migration.warning', "Warning");
|
||||
export const ERROR = localize('sql.migration.error', "Error");
|
||||
|
|
|
@ -0,0 +1,610 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { LoginMigrationValidationResult, MigrationStateModel, ValidateLoginMigrationValidationState } from '../../models/stateMachine';
|
||||
import { getSourceConnectionString, getTargetConnectionString } from '../../api/sqlUtils';
|
||||
import { logError, TelemetryViews } from '../../telemetry';
|
||||
import { EOL } from 'os';
|
||||
|
||||
const DialogName = 'LoginPreMigrationValidationDialog';
|
||||
|
||||
enum HttpStatusCodes {
|
||||
GatewayTimeout = "504\r\n",
|
||||
}
|
||||
|
||||
enum HttpStatusExceptionCodes {
|
||||
ConnectionTimeoutError = "ConnectionTimeoutError",
|
||||
}
|
||||
|
||||
enum ValidationResultIndex {
|
||||
message = 0,
|
||||
icon = 1,
|
||||
status = 2,
|
||||
errors = 3,
|
||||
state = 4,
|
||||
}
|
||||
|
||||
export const ValidationStatusLookup: constants.LookupTable<string | undefined> = {
|
||||
[ValidateLoginMigrationValidationState.Canceled]: constants.VALIDATION_STATE_CANCELED,
|
||||
[ValidateLoginMigrationValidationState.Failed]: constants.VALIDATION_STATE_FAILED,
|
||||
[ValidateLoginMigrationValidationState.Pending]: constants.VALIDATION_STATE_PENDING,
|
||||
[ValidateLoginMigrationValidationState.Running]: constants.VALIDATION_STATE_RUNNING,
|
||||
[ValidateLoginMigrationValidationState.Succeeded]: constants.VALIDATION_STATE_SUCCEEDED,
|
||||
default: undefined
|
||||
};
|
||||
|
||||
export class LoginPreMigrationValidationDialog {
|
||||
private _canceled: boolean = true;
|
||||
private _dialog: azdata.window.Dialog | undefined;
|
||||
private _isOpen: boolean = false;
|
||||
private _model!: MigrationStateModel;
|
||||
private _resultsTable!: azdata.TableComponent;
|
||||
private _startLoader!: azdata.LoadingComponent;
|
||||
private _startButton!: azdata.ButtonComponent;
|
||||
private _revalidationButton!: azdata.ButtonComponent;
|
||||
private _cancelButton!: azdata.ButtonComponent;
|
||||
private _copyButton!: azdata.ButtonComponent;
|
||||
private _validationResult: any[][] = [];
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
private _valdiationErrors: string[] = [];
|
||||
private _onClosed: () => void;
|
||||
|
||||
constructor(
|
||||
model: MigrationStateModel,
|
||||
onClosed: () => void) {
|
||||
this._model = model;
|
||||
this._onClosed = onClosed;
|
||||
}
|
||||
|
||||
public async openDialog(dialogTitle: string, results?: LoginMigrationValidationResult[]): Promise<void> {
|
||||
if (!this._isOpen) {
|
||||
this._isOpen = true;
|
||||
this._dialog = azdata.window.createModelViewDialog(
|
||||
dialogTitle,
|
||||
DialogName,
|
||||
600);
|
||||
|
||||
const promise = this._initializeDialog(this._dialog);
|
||||
azdata.window.openDialog(this._dialog);
|
||||
await promise;
|
||||
|
||||
return this._runValidation(results);
|
||||
}
|
||||
}
|
||||
|
||||
private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.registerContent(async (view) => {
|
||||
try {
|
||||
dialog.okButton.label = constants.VALIDATE_LOGIN_MIGRATION_DONE_BUTTON;
|
||||
dialog.okButton.position = 'left';
|
||||
dialog.okButton.enabled = false;
|
||||
dialog.cancelButton.position = 'left';
|
||||
|
||||
this._disposables.push(
|
||||
dialog.cancelButton.onClick(
|
||||
e => {
|
||||
this._canceled = true;
|
||||
this._saveResults();
|
||||
this._onClosed();
|
||||
}));
|
||||
|
||||
this._disposables.push(
|
||||
dialog.okButton.onClick(
|
||||
e => this._onClosed()));
|
||||
|
||||
const headingText = view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.VALIDATE_LOGIN_MIGRATION_HEADING,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': '400',
|
||||
'margin-bottom': '10px',
|
||||
},
|
||||
})
|
||||
.component();
|
||||
this._startLoader = view.modelBuilder.loadingComponent()
|
||||
.withProps({
|
||||
loading: false,
|
||||
CSSStyles: { 'margin': '5px 0 0 10px' }
|
||||
})
|
||||
.component();
|
||||
const headingContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
})
|
||||
.withItems([headingText, this._startLoader], { flex: '0 0 auto' })
|
||||
.component();
|
||||
|
||||
this._resultsTable = await this._createResultsTable(view);
|
||||
|
||||
this._startButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.restartDataCollection,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 100,
|
||||
label: constants.VALIDATE_LOGIN_MIGRATION_START_VALIDATION,
|
||||
}).component();
|
||||
|
||||
this._cancelButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.stop,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 100,
|
||||
label: constants.VALIDATE_LOGIN_MIGRATION_STOP_VALIDATION,
|
||||
enabled: false,
|
||||
}).component();
|
||||
|
||||
this._revalidationButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.redo,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 170,
|
||||
label: constants.VALIDATE_LOGIN_MIGRATION_UNSUCCESSFUL_REVALIDATION,
|
||||
enabled: false,
|
||||
}).component();
|
||||
|
||||
this._copyButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.copy,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 140,
|
||||
label: constants.VALIDATE_LOGIN_MIGRATION_COPY_RESULTS,
|
||||
enabled: false,
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
this._startButton.onDidClick(
|
||||
async (e) => await this._runValidation()));
|
||||
|
||||
this._disposables.push(
|
||||
this._revalidationButton.onDidClick(
|
||||
async (e) => await this._runUnsuccessfulRevalidation()));
|
||||
this._disposables.push(
|
||||
this._cancelButton.onDidClick(
|
||||
e => {
|
||||
this._cancelButton.enabled = false;
|
||||
this._canceled = true;
|
||||
}));
|
||||
|
||||
this._disposables.push(
|
||||
this._copyButton.onDidClick(
|
||||
async (e) => this._copyValidationResults()));
|
||||
|
||||
const toolbar = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([
|
||||
{ component: this._startButton },
|
||||
{ component: this._cancelButton },
|
||||
{ component: this._revalidationButton },
|
||||
{ component: this._copyButton }])
|
||||
.component();
|
||||
|
||||
const resultsHeading = view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.VALIDATE_LOGIN_MIGRATION_RESULTS_HEADING,
|
||||
CSSStyles: {
|
||||
'font-size': '16px',
|
||||
'font-weight': '600',
|
||||
'margin-bottom': '10px'
|
||||
},
|
||||
})
|
||||
.component();
|
||||
const resultsText = view.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
inputType: 'text',
|
||||
height: 200,
|
||||
multiline: true,
|
||||
CSSStyles: { 'overflow': 'none auto' }
|
||||
})
|
||||
.component();
|
||||
|
||||
this._disposables.push(
|
||||
this._resultsTable.onRowSelected(
|
||||
async (e) => await this._updateResultsInfoBox(resultsText)));
|
||||
|
||||
const flex = view.modelBuilder.flexContainer()
|
||||
.withItems([
|
||||
headingContainer,
|
||||
toolbar,
|
||||
this._resultsTable,
|
||||
resultsHeading,
|
||||
resultsText],
|
||||
{ flex: '0 0 auto' })
|
||||
.withProps({ CSSStyles: { 'margin': '0 0 0 15px' } })
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
height: '100%',
|
||||
width: 565,
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
view.onClosed(e =>
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } })));
|
||||
|
||||
await view.initializeModel(flex);
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _copyValidationResults(): any {
|
||||
const errorsText = this._valdiationErrors.join(EOL);
|
||||
const msg = errorsText.length === 0
|
||||
? constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_COMPLETED
|
||||
: constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_COMPLETED_ERRORS(errorsText);
|
||||
return vscode.env.clipboard.writeText(msg);
|
||||
}
|
||||
|
||||
private async _runUnsuccessfulRevalidation(): Promise<any> {
|
||||
try {
|
||||
this._startLoader.loading = true;
|
||||
this._startButton.enabled = false;
|
||||
this._revalidationButton.enabled = false;
|
||||
this._cancelButton.enabled = true;
|
||||
this._copyButton.enabled = false;
|
||||
this._dialog!.okButton.enabled = false;
|
||||
this._dialog!.cancelButton.enabled = true;
|
||||
if (!this._model.isLoginMigrationTargetValidated) {
|
||||
await this._revalidate();
|
||||
}
|
||||
} finally {
|
||||
this._startLoader.loading = false;
|
||||
this._startButton.enabled = true;
|
||||
this._revalidationButton.enabled = !this._model.isLoginMigrationTargetValidated;
|
||||
this._cancelButton.enabled = false;
|
||||
this._copyButton.enabled = true;
|
||||
this._dialog!.okButton.enabled = this._model.isLoginMigrationTargetValidated;
|
||||
this._dialog!.cancelButton.enabled = !this._model.isLoginMigrationTargetValidated;
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateResultsInfoBox(text: azdata.InputBoxComponent): Promise<void> {
|
||||
const selectedRows: number[] = this._resultsTable.selectedRows ?? [];
|
||||
const statusMessages: string[] = [];
|
||||
if (selectedRows.length > 0) {
|
||||
for (let i = 0; i < selectedRows.length; i++) {
|
||||
const row = selectedRows[i];
|
||||
const results: any[] = this._validationResult[row];
|
||||
const status = results[ValidationResultIndex.status];
|
||||
const errors = results[ValidationResultIndex.errors];
|
||||
statusMessages.push(
|
||||
constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS(ValidationStatusLookup[status], errors));
|
||||
}
|
||||
}
|
||||
|
||||
const msg = statusMessages.length > 0
|
||||
? statusMessages.join(EOL)
|
||||
: '';
|
||||
text.value = msg;
|
||||
}
|
||||
|
||||
private async _createResultsTable(view: azdata.ModelView): Promise<azdata.TableComponent> {
|
||||
return view.modelBuilder.table()
|
||||
.withProps({
|
||||
columns: [
|
||||
{
|
||||
value: 'test',
|
||||
name: "Validation steps",
|
||||
type: azdata.ColumnType.text,
|
||||
width: 380,
|
||||
headerCssClass: 'no-borders',
|
||||
cssClass: 'no-borders align-with-header',
|
||||
},
|
||||
{
|
||||
value: 'image',
|
||||
name: '',
|
||||
type: azdata.ColumnType.icon,
|
||||
width: 20,
|
||||
headerCssClass: 'no-borders display-none',
|
||||
cssClass: 'no-borders align-with-header',
|
||||
},
|
||||
{
|
||||
value: 'message',
|
||||
name: "Status",
|
||||
type: azdata.ColumnType.text,
|
||||
width: 150,
|
||||
headerCssClass: 'no-borders',
|
||||
cssClass: 'no-borders align-with-header',
|
||||
},
|
||||
],
|
||||
data: [],
|
||||
width: 580,
|
||||
height: 300,
|
||||
CSSStyles: {
|
||||
'margin-top': '10px',
|
||||
'margin-bottom': '10px',
|
||||
},
|
||||
})
|
||||
.component();
|
||||
}
|
||||
|
||||
private _saveResults(): void {
|
||||
const results = this._validationResults();
|
||||
this._model._validateLoginMigration = results;
|
||||
}
|
||||
|
||||
private _validationResults(): LoginMigrationValidationResult[] {
|
||||
return this._validationResult.map(result => {
|
||||
const state = result[ValidationResultIndex.state];
|
||||
const finalState = this._canceled
|
||||
? (state === ValidateLoginMigrationValidationState.Running || state === ValidateLoginMigrationValidationState.Pending)
|
||||
? ValidateLoginMigrationValidationState.Canceled
|
||||
: state
|
||||
: state;
|
||||
const errors = result[ValidationResultIndex.errors] ?? [];
|
||||
return {
|
||||
errors: errors,
|
||||
state: finalState,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async _runValidation(results?: LoginMigrationValidationResult[]): Promise<void> {
|
||||
try {
|
||||
this._startLoader.loading = true;
|
||||
this._startButton.enabled = false;
|
||||
this._revalidationButton.enabled = false;
|
||||
this._cancelButton.enabled = true;
|
||||
this._copyButton.enabled = false;
|
||||
this._dialog!.okButton.enabled = false;
|
||||
this._dialog!.cancelButton.enabled = true;
|
||||
await this._validate();
|
||||
} finally {
|
||||
this._startLoader.loading = false;
|
||||
this._startButton.enabled = true;
|
||||
this._revalidationButton.enabled = !this._model.isLoginMigrationTargetValidated;
|
||||
this._cancelButton.enabled = false;
|
||||
this._copyButton.enabled = true;
|
||||
this._dialog!.okButton.enabled = this._model.isLoginMigrationTargetValidated;
|
||||
this._dialog!.cancelButton.enabled = !this._model.isLoginMigrationTargetValidated;
|
||||
}
|
||||
}
|
||||
|
||||
private async _validate(): Promise<void> {
|
||||
this._canceled = false;
|
||||
await this._initializeResults();
|
||||
await this._validateLoginMigration();
|
||||
this._saveResults();
|
||||
}
|
||||
|
||||
private async _revalidate(): Promise<void> {
|
||||
await this._initLoginMigrationResultsForRevalidation();
|
||||
await this._validateLoginMigration(true);
|
||||
this._saveResults();
|
||||
}
|
||||
|
||||
private _formatError(error: Error): Error {
|
||||
if (error?.message?.startsWith(HttpStatusCodes.GatewayTimeout)) {
|
||||
return {
|
||||
name: HttpStatusExceptionCodes.ConnectionTimeoutError,
|
||||
message: constants.VALIDATE_LOGIN_MIGRATION_ERROR_GATEWAY_TIMEOUT,
|
||||
};
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
private async _validateLoginMigration(skipSuccessfulSteps: boolean = false): Promise<void> {
|
||||
let testNumber: number = 0;
|
||||
|
||||
const validate = async (
|
||||
validationFunction: any,
|
||||
loginList: string[],
|
||||
aadDomainName: string,
|
||||
): Promise<boolean> => {
|
||||
|
||||
try {
|
||||
await this._updateValidateLoginMigrationResults(testNumber, ValidateLoginMigrationValidationState.Running);
|
||||
const sourceConnectionString: string = await getSourceConnectionString();
|
||||
const targetConnectionString: string = await getTargetConnectionString(
|
||||
this._model.targetServerName,
|
||||
this._model._targetServerInstance.id,
|
||||
this._model._targetUserName,
|
||||
this._model._targetPassword,
|
||||
this._model._targetPort,
|
||||
// for login migration, connect to target Azure SQL with true/true
|
||||
// to-do: take as input from the user, should be true/false for DB/MI but true/true for VM
|
||||
true /* encryptConnection */,
|
||||
true /* trustServerCertificate */);
|
||||
const validationFunctionName = validationFunction.name.replace('bound ', '');
|
||||
|
||||
var validationResult = await validationFunction(
|
||||
sourceConnectionString,
|
||||
targetConnectionString,
|
||||
loginList,
|
||||
aadDomainName);
|
||||
|
||||
if (validationResult !== undefined) {
|
||||
if (Object.keys(validationResult.exceptionMap).length > 0) {
|
||||
var errors: any[] = []
|
||||
Object.keys(validationResult.exceptionMap).forEach(name => {
|
||||
const error = validationResult?.exceptionMap[name][0]
|
||||
errors.push(constants.GET_LOGIN_MIGRATION_VALIDATION_ERROR(
|
||||
validationFunctionName,
|
||||
name,
|
||||
error))
|
||||
});
|
||||
await this._updateValidateLoginMigrationResults(testNumber, ValidateLoginMigrationValidationState.Failed, errors);
|
||||
} else {
|
||||
await this._updateValidateLoginMigrationResults(testNumber, ValidateLoginMigrationValidationState.Succeeded);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const err = this._formatError(error);
|
||||
logError(TelemetryViews.LoginMigrationPreValdationDialog, err.message, error);
|
||||
await this._updateValidateLoginMigrationResults(
|
||||
testNumber,
|
||||
ValidateLoginMigrationValidationState.Failed,
|
||||
[constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_API_ERROR(err)]);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// validate sys admin permissions
|
||||
if (!skipSuccessfulSteps || this._validationResult[testNumber][ValidationResultIndex.state] !== ValidateLoginMigrationValidationState.Succeeded) {
|
||||
var loginList: string[] = [];
|
||||
var aadDomainName = "";
|
||||
if (!await validate(this._model.migrationService.validateSysAdminPermission.bind(this._model.migrationService), loginList, aadDomainName)) {
|
||||
this._canceled = true;
|
||||
await this._updateValidateLoginMigrationResults(testNumber + 1, ValidateLoginMigrationValidationState.Canceled, [constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
testNumber++;
|
||||
if (this._canceled) {
|
||||
await this._updateValidateLoginMigrationResults(testNumber, ValidateLoginMigrationValidationState.Canceled, [constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED]);
|
||||
}
|
||||
// validate Entra ID
|
||||
else if (!skipSuccessfulSteps || this._validationResult[testNumber][ValidationResultIndex.state] !== ValidateLoginMigrationValidationState.Succeeded) {
|
||||
var loginList: string[] = this._model._loginMigrationModel.loginsForMigration.map(row => row.loginName);
|
||||
var aadDomainName = this._model._aadDomainName;
|
||||
if (!await validate(this._model.migrationService.validateAADDomainName.bind(this._model.migrationService), loginList, aadDomainName)) {
|
||||
this._canceled = true;
|
||||
await this._updateValidateLoginMigrationResults(testNumber + 1, ValidateLoginMigrationValidationState.Canceled, [constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
testNumber++;
|
||||
if (this._canceled) {
|
||||
await this._updateValidateLoginMigrationResults(testNumber, ValidateLoginMigrationValidationState.Canceled, [constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED]);
|
||||
}
|
||||
// validate user mapping
|
||||
else if (!skipSuccessfulSteps || this._validationResult[testNumber][ValidationResultIndex.state] !== ValidateLoginMigrationValidationState.Succeeded) {
|
||||
var loginList: string[] = this._model._loginMigrationModel.loginsForMigration.map(row => row.loginName);
|
||||
var aadDomainName = this._model._aadDomainName;
|
||||
if (!await validate(this._model.migrationService.validateUserMapping.bind(this._model.migrationService), loginList, aadDomainName)) {
|
||||
this._canceled = true;
|
||||
await this._updateValidateLoginMigrationResults(testNumber + 1, ValidateLoginMigrationValidationState.Canceled, [constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_CANCELED]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateValidateLoginMigrationResults(row: number, state: ValidateLoginMigrationValidationState, errors: string[] = [], updateTable: boolean = true): Promise<void> {
|
||||
if (state === ValidateLoginMigrationValidationState.Canceled) {
|
||||
for (let cancelRow = row; cancelRow < this._validationResult.length; cancelRow++) {
|
||||
await this._updateResults(cancelRow, state, errors);
|
||||
}
|
||||
} else {
|
||||
await this._updateResults(row, state, errors);
|
||||
}
|
||||
|
||||
if (updateTable) {
|
||||
const data = this._validationResult.map(row => [
|
||||
row[ValidationResultIndex.message],
|
||||
row[ValidationResultIndex.icon],
|
||||
row[ValidationResultIndex.status]]);
|
||||
await this._resultsTable.updateProperty('data', data);
|
||||
}
|
||||
|
||||
this._valdiationErrors.push(...errors);
|
||||
}
|
||||
|
||||
private async _updateResults(row: number, state: ValidateLoginMigrationValidationState, errors: string[] = []): Promise<void> {
|
||||
const result = this._validationResult[row];
|
||||
const status = ValidationStatusLookup[state];
|
||||
const statusMsg = state === ValidateLoginMigrationValidationState.Failed && errors.length > 0
|
||||
? constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS_ERROR_COUNT(status, errors.length)
|
||||
: status;
|
||||
|
||||
const statusMessage = errors.length > 0
|
||||
? constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_STATUS_ERROR(status, errors)
|
||||
: statusMsg;
|
||||
|
||||
this._validationResult[row] = [
|
||||
result[ValidationResultIndex.message],
|
||||
<azdata.IconColumnCellValue>{
|
||||
icon: this._getValidationStateImage(state),
|
||||
title: statusMessage,
|
||||
},
|
||||
statusMsg,
|
||||
errors,
|
||||
state];
|
||||
}
|
||||
|
||||
private _getValidationStateImage(state: ValidateLoginMigrationValidationState): azdata.IconPath {
|
||||
switch (state) {
|
||||
case ValidateLoginMigrationValidationState.Canceled:
|
||||
return IconPathHelper.cancel;
|
||||
case ValidateLoginMigrationValidationState.Failed:
|
||||
return IconPathHelper.error;
|
||||
case ValidateLoginMigrationValidationState.Running:
|
||||
return IconPathHelper.inProgressMigration;
|
||||
case ValidateLoginMigrationValidationState.Succeeded:
|
||||
return IconPathHelper.completedMigration;
|
||||
case ValidateLoginMigrationValidationState.Pending:
|
||||
default:
|
||||
return IconPathHelper.notStartedMigration;
|
||||
}
|
||||
}
|
||||
|
||||
private async _initializeResults(results?: LoginMigrationValidationResult[]): Promise<void> {
|
||||
this._valdiationErrors = [];
|
||||
this._validationResult = [];
|
||||
|
||||
this._addValidationResult(constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_SYSADMIN);
|
||||
this._addValidationResult(constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_ENTRAID);
|
||||
this._addValidationResult(constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_RESULT_LABEL_USERMAPPING);
|
||||
|
||||
if (results && results.length > 0) {
|
||||
for (let row = 0; row < results.length; row++) {
|
||||
await this._updateValidateLoginMigrationResults(
|
||||
row,
|
||||
results[row].state,
|
||||
results[row].errors,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this._validationResult.map(row => [
|
||||
row[ValidationResultIndex.message],
|
||||
row[ValidationResultIndex.icon],
|
||||
"Pending"]);
|
||||
|
||||
await this._resultsTable.updateProperty('data', data);
|
||||
}
|
||||
|
||||
private async _initLoginMigrationResultsForRevalidation(): Promise<void> {
|
||||
this._valdiationErrors = [];
|
||||
this._canceled = false;
|
||||
let testNumber: number = 0;
|
||||
|
||||
this._validationResult.forEach(async element => {
|
||||
if (element[ValidationResultIndex.state] !== ValidateLoginMigrationValidationState.Succeeded) {
|
||||
await this._updateValidateLoginMigrationResults(testNumber++, ValidateLoginMigrationValidationState.Pending);
|
||||
}
|
||||
else {
|
||||
testNumber++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _addValidationResult(message: string): void {
|
||||
this._validationResult.push([
|
||||
message,
|
||||
<azdata.IconColumnCellValue>{
|
||||
icon: IconPathHelper.notStartedMigration,
|
||||
title: ValidationStatusLookup[ValidateLoginMigrationValidationState.Pending],
|
||||
},
|
||||
ValidationStatusLookup[ValidateLoginMigrationValidationState.Pending],
|
||||
[],
|
||||
ValidateLoginMigrationValidationState.Pending]);
|
||||
}
|
||||
}
|
|
@ -29,11 +29,24 @@ export enum ValidateIrState {
|
|||
Canceled = 'Canceled',
|
||||
}
|
||||
|
||||
export enum ValidateLoginMigrationValidationState {
|
||||
Pending = 'Pending',
|
||||
Running = 'Running',
|
||||
Succeeded = 'Succeeded',
|
||||
Failed = 'Failed',
|
||||
Canceled = 'Canceled',
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
errors: string[];
|
||||
state: ValidateIrState;
|
||||
}
|
||||
|
||||
export interface LoginMigrationValidationResult {
|
||||
errors: string[];
|
||||
state: ValidateLoginMigrationValidationState;
|
||||
}
|
||||
|
||||
export enum State {
|
||||
INIT,
|
||||
COLLECTING_SOURCE_INFO,
|
||||
|
@ -238,6 +251,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||
public _validateIrSqlDb: ValidationResult[] = [];
|
||||
public _validateIrSqlMi: ValidationResult[] = [];
|
||||
public _validateIrSqlVm: ValidationResult[] = [];
|
||||
public _validateLoginMigration: LoginMigrationValidationResult[] = [];
|
||||
|
||||
public _skuRecommendationResults!: SkuRecommendation;
|
||||
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
|
||||
|
@ -358,6 +372,14 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||
r.state === ValidateIrState.Succeeded)
|
||||
}
|
||||
|
||||
public get isLoginMigrationTargetValidated(): boolean {
|
||||
const results = this._validateLoginMigration ?? [];
|
||||
return results.length > 1
|
||||
&& results.every(r =>
|
||||
r.errors.length === 0 &&
|
||||
r.state === ValidateLoginMigrationValidationState.Succeeded)
|
||||
}
|
||||
|
||||
public get migrationTargetServerName(): string {
|
||||
switch (this._targetType) {
|
||||
case MigrationTargetType.SQLMI:
|
||||
|
|
|
@ -43,6 +43,7 @@ export enum TelemetryViews {
|
|||
LoginMigrationTargetSelectionPage = 'LoginMigrationTargetSelectionPage',
|
||||
LoginMigrationSelectorPage = 'LoginMigrationSelectorPage',
|
||||
LoginMigrationStatusPage = 'LoginMigrationStatusPage',
|
||||
LoginMigrationPreValdationDialog = 'LoginMigrationPreValdationDialog',
|
||||
TdeConfigurationDialog = 'TdeConfigurationDialog',
|
||||
TdeMigrationDialog = 'TdeMigrationDialog',
|
||||
ValidIrDialog = 'validIrDialog',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import { LoginMigrationValidationResult, MigrationStateModel, StateChangeEvent, ValidateLoginMigrationValidationState } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import { debounce, getLoginStatusImage, getLoginStatusMessage, getSourceLogins } from '../api/utils';
|
||||
import * as styles from '../constants/styles';
|
||||
|
@ -17,6 +17,10 @@ import { getTelemetryProps, logError, sendSqlMigrationActionEvent, TelemetryActi
|
|||
import { CollectingSourceLoginsFailed, CollectingTargetLoginsFailed } from '../models/loginMigrationModel';
|
||||
import { WizardController } from './wizardController';
|
||||
import { Tab } from 'azdata';
|
||||
import { LoginPreMigrationValidationDialog } from '../dialog/loginMigration/loginPreMigrationValidationDialog';
|
||||
import { EOL } from 'os';
|
||||
|
||||
const VALIDATE_LOGIN_MIGRATION_CUSTOM_BUTTON_INDEX = 0;
|
||||
|
||||
export class LoginSelectorPage extends MigrationWizardPage {
|
||||
private _view!: azdata.ModelView;
|
||||
|
@ -57,6 +61,10 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
}).component();
|
||||
flex.addItem(await this.createRootContainer(view), { flex: '1 1 auto' });
|
||||
|
||||
this._disposables.push(
|
||||
this.wizard.customButtons[VALIDATE_LOGIN_MIGRATION_CUSTOM_BUTTON_INDEX].onClick(
|
||||
async e => await this._validateLoginPreMigration()));
|
||||
|
||||
this._disposables.push(this._view.onClosed(e => {
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
|
@ -71,8 +79,11 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
constants.WIZARD_CANCEL_REASON_NEED_TO_REVIEW_LOGIN_SELECTION
|
||||
]);
|
||||
|
||||
this.wizard.customButtons[VALIDATE_LOGIN_MIGRATION_CUSTOM_BUTTON_INDEX].hidden = false;
|
||||
|
||||
this._isCurrentPage = true;
|
||||
this.updateNextButton();
|
||||
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
this.wizard.message = {
|
||||
text: '',
|
||||
|
@ -83,9 +94,9 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (this.selectedLogins().length === 0) {
|
||||
if (this.selectedLogins().length === 0 || !this.migrationStateModel.isLoginMigrationTargetValidated) {
|
||||
this.wizard.message = {
|
||||
text: constants.SELECT_LOGIN_TO_CONTINUE,
|
||||
text: constants.SELECT_LOGIN_AND_RUN_VALIDATION_TO_CONTINUE,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return false;
|
||||
|
@ -93,7 +104,7 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
|
||||
if (this.migrationStateModel._loginMigrationModel.selectedWindowsLogins && !this.migrationStateModel._aadDomainName) {
|
||||
this.wizard.message = {
|
||||
text: constants.ENTER_AAD_DOMAIN_NAME,
|
||||
text: constants.ENTER_ENTRA_ID,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return false;
|
||||
|
@ -110,6 +121,8 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
}
|
||||
|
||||
public async onPageLeave(): Promise<void> {
|
||||
this.wizard.customButtons[VALIDATE_LOGIN_MIGRATION_CUSTOM_BUTTON_INDEX].hidden = true;
|
||||
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
return true;
|
||||
});
|
||||
|
@ -151,7 +164,7 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
// target user name
|
||||
const aadDomainNameLabel = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.LOGIN_MIGRATIONS_AAD_DOMAIN_NAME_INPUT_BOX_LABEL,
|
||||
value: constants.LOGIN_MIGRATIONS_ENTRA_ID_INPUT_BOX_LABEL,
|
||||
requiredIndicator: false,
|
||||
CSSStyles: { ...styles.LABEL_CSS }
|
||||
}).component();
|
||||
|
@ -160,7 +173,7 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
.withProps({
|
||||
width: '300px',
|
||||
inputType: 'text',
|
||||
placeHolder: constants.LOGIN_MIGRATIONS_AAD_DOMAIN_NAME_INPUT_BOX_PLACEHOLDER,
|
||||
placeHolder: constants.LOGIN_MIGRATIONS_ENTRA_ID_INPUT_BOX_PLACEHOLDER,
|
||||
required: false,
|
||||
}).component();
|
||||
|
||||
|
@ -680,6 +693,56 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
await this._loginSelectorTable.updateProperty("height", selectedWindowsLogins ? 600 : 650);
|
||||
}
|
||||
|
||||
public updateValidationResultUI(initializing?: boolean): void {
|
||||
const succeeded = this.migrationStateModel.isLoginMigrationTargetValidated;
|
||||
if (succeeded) {
|
||||
this.wizard.message = {
|
||||
level: azdata.window.MessageLevel.Information,
|
||||
text: constants.LOGIN_MIGRATION_VALIDATION_MESSAGE_SUCCESS,
|
||||
};
|
||||
} else {
|
||||
const results = this.migrationStateModel._validateLoginMigration;
|
||||
const hasResults = results.length > 0;
|
||||
if (initializing && !hasResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canceled = results.some(result => result.state === ValidateLoginMigrationValidationState.Canceled);
|
||||
const errors: string[] = results.flatMap(result => result.errors) ?? [];
|
||||
const errorsMessage: string = errors.join(EOL);
|
||||
const hasErrors = errors.length > 0;
|
||||
const msg = hasResults
|
||||
? hasErrors
|
||||
? canceled
|
||||
? constants.VALIDATION_MESSAGE_CANCELED_ERRORS(errorsMessage)
|
||||
: constants.VALIDATE_LOGIN_MIGRATION_VALIDATION_COMPLETED_ERRORS(errorsMessage)
|
||||
: constants.VALIDATION_MESSAGE_CANCELED
|
||||
: constants.VALIDATION_MESSAGE_NOT_RUN;
|
||||
|
||||
this.wizard.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: msg,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async _validateLoginPreMigration(): Promise<void> {
|
||||
if (this.migrationStateModel?._loginMigrationModel.loginsForMigration?.length <= 0) {
|
||||
this.wizard.message = {
|
||||
text: constants.SELECT_LOGIN_TO_CONTINUE,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = new LoginPreMigrationValidationDialog(
|
||||
this.migrationStateModel,
|
||||
() => this.updateValidationResultUI());
|
||||
let results: LoginMigrationValidationResult[] = [];
|
||||
results = this.migrationStateModel._validateLoginMigration;
|
||||
await dialog.openDialog(constants.VALIDATION_DIALOG_TITLE, results);
|
||||
}
|
||||
|
||||
private async updateValuesOnSelection() {
|
||||
const selectedLogins = this.selectedLogins() || [];
|
||||
await this._loginCount.updateProperties({
|
||||
|
@ -688,7 +751,6 @@ export class LoginSelectorPage extends MigrationWizardPage {
|
|||
this._loginSelectorTable.data?.length || 0)
|
||||
});
|
||||
|
||||
this.migrationStateModel._loginMigrationModel.loginsForMigration = selectedLogins;
|
||||
this.migrationStateModel._loginMigrationModel.loginsForMigration = selectedLogins;
|
||||
await this.refreshAADInputBox();
|
||||
this.updateNextButton();
|
||||
|
|
|
@ -218,11 +218,17 @@ export class WizardController {
|
|||
this._wizardObject.nextButton.secondary = false;
|
||||
this._wizardObject.cancelButton.hidden = true;
|
||||
|
||||
const validateButton = azdata.window.createButton(
|
||||
loc.RUN_VALIDATION,
|
||||
'left');
|
||||
validateButton.secondary = false;
|
||||
validateButton.hidden = true;
|
||||
|
||||
const customCancelButton = azdata.window.createButton(
|
||||
loc.CANCEL,
|
||||
'right');
|
||||
customCancelButton.secondary = true;
|
||||
this._wizardObject.customButtons = [customCancelButton];
|
||||
this._wizardObject.customButtons = [validateButton, customCancelButton];
|
||||
|
||||
|
||||
const targetSelectionPage = new LoginMigrationTargetSelectionPage(this._wizardObject, stateModel, this);
|
||||
|
|
Загрузка…
Ссылка в новой задаче