mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 02:48:30 -05:00
Junierch/mi tde (#21573)
* WIP * wip tde wizard * wip for merge w master * wip * wip share * tde wizard * PR reviews updates * PR review updates * PR updates * PR review updates * PR reviews updates * Bump STS to 4.4.0.22 * PR reviews updates * fix localize build issue * fix build issue with localize * remove unused function * Windows only flag. Bug Bash fixes * Use azdata with latest STS changes * revert azdata, other PR comments * sts and extesion version upgraded. logins back
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as styles from '../../constants/styles';
|
||||
import * as utils from '../../api/utils';
|
||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||
|
||||
export class TdeConfigurationDialog {
|
||||
|
||||
private dialog: azdata.window.Dialog | undefined;
|
||||
private _isOpen: boolean = false;
|
||||
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
private _adsMethodConfirmationContainer!: azdata.FlexContainer;
|
||||
private _adsConfirmationCheckBox!: azdata.CheckBoxComponent;
|
||||
private _manualMethodWarningContainer!: azdata.FlexContainer;
|
||||
private _networkPathText!: azdata.InputBoxComponent;
|
||||
private _onClosed: () => void;
|
||||
|
||||
constructor(public skuRecommendationPage: SKURecommendationPage, public wizard: azdata.window.Wizard, public migrationStateModel: MigrationStateModel,
|
||||
onClosed: () => void) {
|
||||
this._onClosed = onClosed;
|
||||
}
|
||||
|
||||
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.registerContent(async (view) => {
|
||||
try {
|
||||
const flex = this.createContainer(view);
|
||||
|
||||
this._disposables.push(view.onClosed(e => {
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
}));
|
||||
|
||||
await view.initializeModel(flex);
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||
const container = _view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'margin': '8px 16px',
|
||||
'flex-direction': 'column',
|
||||
}
|
||||
}).component();
|
||||
const encryptedDescriptionText = constants.TDE_WIZARD_DATABASES_SELECTED(
|
||||
this.migrationStateModel.tdeMigrationConfig.getTdeEnabledDatabasesCount(),
|
||||
this.migrationStateModel._databasesForMigration.length);
|
||||
|
||||
const encrypted_description1 = _view.modelBuilder.text().withProps({
|
||||
value: encryptedDescriptionText,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
}
|
||||
}).component();
|
||||
const encrypted_description2 = _view.modelBuilder.text().withProps({
|
||||
value: constants.TDE_WIZARD_DESCRIPTION,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'margin-top': '8px',
|
||||
'text-align': 'justify'
|
||||
},
|
||||
links: [{
|
||||
text: constants.LEARN_MORE,
|
||||
url: 'https://learn.microsoft.com/sql/relational-databases/security/encryption/transparent-data-encryption',
|
||||
accessibilityInformation: {
|
||||
label: constants.LEARN_MORE
|
||||
}
|
||||
}]
|
||||
}).component();
|
||||
const selectDataSourceRadioButtons = this.createMethodsContainer(_view);
|
||||
container.addItems([
|
||||
encrypted_description1,
|
||||
encrypted_description2,
|
||||
selectDataSourceRadioButtons,
|
||||
]);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
private createMethodsContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||
const chooseMethodText = _view.modelBuilder.text().withProps({
|
||||
value: constants.TDE_WIZARD_MIGRATION_CAPTION,
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin-top': '16px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
const buttonGroup = 'dataSourceContainer';
|
||||
const radioButtonContainer = _view.modelBuilder.flexContainer().withProps({
|
||||
ariaLabel: constants.AZURE_RECOMMENDATION_CHOOSE_METHOD,
|
||||
ariaRole: 'radiogroup',
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const adsMethodContainer = _view.modelBuilder.flexContainer()
|
||||
.withProps(
|
||||
{
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column',
|
||||
'display': 'inline'
|
||||
}
|
||||
})
|
||||
.component();
|
||||
|
||||
const adsMethodButton = _view.modelBuilder.radioButton()
|
||||
.withProps({
|
||||
name: buttonGroup,
|
||||
label: constants.TDE_WIZARD_MIGRATION_OPTION_ADS,
|
||||
checked: this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAds(),
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS
|
||||
},
|
||||
}).component();
|
||||
this._disposables.push(
|
||||
adsMethodButton.onDidChangeCheckedState(async checked => {
|
||||
if (checked) {
|
||||
this.migrationStateModel.tdeMigrationConfig.setTdeMigrationMethod(true);
|
||||
await this.updateUI();
|
||||
}
|
||||
}));
|
||||
|
||||
this._adsMethodConfirmationContainer = this.createAdsConfirmationContainer(_view);
|
||||
|
||||
adsMethodContainer.addItems([
|
||||
adsMethodButton,
|
||||
this._adsMethodConfirmationContainer]);
|
||||
|
||||
const manualMethodContainer = _view.modelBuilder.flexContainer()
|
||||
.withProps(
|
||||
{
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column',
|
||||
'display': 'inline'
|
||||
}
|
||||
})
|
||||
.component();
|
||||
|
||||
const manualMethodButton = _view.modelBuilder.radioButton()
|
||||
.withProps({
|
||||
name: buttonGroup,
|
||||
label: constants.TDE_WIZARD_MIGRATION_OPTION_MANUAL,
|
||||
checked: this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodManual(),
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
manualMethodButton.onDidChangeCheckedState(async checked => {
|
||||
if (checked) {
|
||||
this.migrationStateModel.tdeMigrationConfig.setTdeMigrationMethod(false);
|
||||
await this.updateUI();
|
||||
}
|
||||
}));
|
||||
|
||||
this._manualMethodWarningContainer = this.createManualWarningContainer(_view);
|
||||
|
||||
manualMethodContainer.addItems([
|
||||
manualMethodButton,
|
||||
this._manualMethodWarningContainer
|
||||
]);
|
||||
|
||||
radioButtonContainer.addItems([
|
||||
adsMethodContainer,
|
||||
manualMethodContainer]);
|
||||
|
||||
const container = _view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withItems([
|
||||
chooseMethodText,
|
||||
radioButtonContainer
|
||||
])
|
||||
.component();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private createAdsConfirmationContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||
const container = _view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const adsMethodInfoMessage = _view.modelBuilder.infoBox()
|
||||
.withProps({
|
||||
text: constants.TDE_WIZARD_ADS_CERTS_INFO,
|
||||
style: 'information',
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'margin': '4px 14px 0px 14px',
|
||||
'text-align': 'justify'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const networkPathLabel = _view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.TDE_WIZARD_CERTS_NETWORK_SHARE_LABEL,
|
||||
description: constants.TDE_WIZARD_CERTS_NETWORK_SHARE_INFO,
|
||||
requiredIndicator: true,
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin': '4px 0 14px 45px'
|
||||
}
|
||||
}).component();
|
||||
this._networkPathText = _view.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: '',
|
||||
width: '300px',
|
||||
placeHolder: constants.TDE_WIZARD_CERTS_NETWORK_SHARE_PLACEHOLDER,
|
||||
required: true,
|
||||
CSSStyles: { ...styles.BODY_CSS, 'margin-top': '-1em', 'margin-left': '45px' }
|
||||
}).component();
|
||||
|
||||
this._adsConfirmationCheckBox = _view.modelBuilder.checkBox()
|
||||
.withProps({
|
||||
label: constants.TDE_WIZARD_MIGRATION_OPTION_ADS_CONFIRM,
|
||||
checked: this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAdsConfirmed(),
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'margin': '10px 0 14px 15px'
|
||||
}
|
||||
}).component();
|
||||
this._disposables.push(
|
||||
this._adsConfirmationCheckBox.onChanged(async checked => {
|
||||
this.migrationStateModel.tdeMigrationConfig.setAdsConfirmation(
|
||||
checked,
|
||||
this._networkPathText.value ?? '');
|
||||
await this.updateUI();
|
||||
}));
|
||||
|
||||
container.addItems([
|
||||
adsMethodInfoMessage,
|
||||
networkPathLabel,
|
||||
this._networkPathText,
|
||||
this._adsConfirmationCheckBox]);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private createManualWarningContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||
const container = _view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const manualMethodConfirmationDialog = _view.modelBuilder.infoBox()
|
||||
.withProps({
|
||||
text: constants.TDE_WIZARD_MIGRATION_OPTION_MANUAL_WARNING,
|
||||
style: 'warning',
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'margin': '4px 14px 0px 14px'
|
||||
},
|
||||
links: [{
|
||||
text: constants.LEARN_MORE,
|
||||
url: 'https://learn.microsoft.com/azure/azure-sql/managed-instance/tde-certificate-migrate',
|
||||
accessibilityInformation: {
|
||||
label: constants.LEARN_MORE
|
||||
}
|
||||
}]
|
||||
}).component();
|
||||
|
||||
container.addItems([
|
||||
manualMethodConfirmationDialog]);
|
||||
return container;
|
||||
}
|
||||
|
||||
private async updateUI(): Promise<void> {
|
||||
const useAds = this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAds();
|
||||
|
||||
this._networkPathText.value = this.migrationStateModel.tdeMigrationConfig._networkPath;
|
||||
this._adsConfirmationCheckBox.checked = this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAdsConfirmed();
|
||||
await utils.updateControlDisplay(this._adsMethodConfirmationContainer, useAds);
|
||||
|
||||
const useManual = this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodManual();
|
||||
await utils.updateControlDisplay(this._manualMethodWarningContainer, useManual);
|
||||
|
||||
this.dialog!.okButton.enabled = this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodSet();
|
||||
|
||||
this._networkPathText.required = useAds;
|
||||
}
|
||||
|
||||
public async openDialog(dialogName?: string,) {
|
||||
if (!this._isOpen) {
|
||||
|
||||
this.migrationStateModel.tdeMigrationConfig.configurationShown();
|
||||
|
||||
this._isOpen = true;
|
||||
this.dialog = azdata.window.createModelViewDialog(
|
||||
constants.TDE_WIZARD_TITLE,
|
||||
'TdeConfigurationDialog',
|
||||
'narrow');
|
||||
|
||||
this.dialog.okButton.label = constants.APPLY;
|
||||
this._disposables.push(
|
||||
this.dialog.okButton.onClick(
|
||||
(eventArgs) => {
|
||||
this._isOpen = false;
|
||||
this.migrationStateModel.tdeMigrationConfig.setConfigurationCompleted();
|
||||
|
||||
if (this.migrationStateModel.tdeMigrationConfig.shouldAdsMigrateCertificates()) {
|
||||
this.migrationStateModel.tdeMigrationConfig._networkPath = this._networkPathText.value ?? '';
|
||||
}
|
||||
this._onClosed();
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
this.dialog.cancelButton.onClick(
|
||||
() => {
|
||||
this._isOpen = false;
|
||||
this._onClosed();
|
||||
}));
|
||||
|
||||
const promise = this.initializeDialog(this.dialog);
|
||||
azdata.window.openDialog(this.dialog);
|
||||
await promise;
|
||||
|
||||
await this.updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
public get isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../constants/strings';
|
||||
import { logError, TelemetryErrorName, TelemetryViews } from '../../telemtery';
|
||||
import { EOL } from 'os';
|
||||
import { MigrationStateModel, OperationResult } from '../../models/stateMachine';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { TdeMigrationState, TdeMigrationResult, TdeMigrationDbState, TdeDatabaseMigrationState, TdeMigrationDbResult } from '../../models/tdeModels';
|
||||
|
||||
const DialogName = 'TdeMigrationDialog';
|
||||
|
||||
export enum TdeValidationResultIndex {
|
||||
name = 0,
|
||||
icon = 1,
|
||||
status = 2,
|
||||
errors = 3,
|
||||
state = 4,
|
||||
updated = 5
|
||||
}
|
||||
|
||||
export const ValidationStatusLookup: constants.LookupTable<string | undefined> = {
|
||||
[TdeMigrationState.Canceled]: constants.STATE_CANCELED,
|
||||
[TdeMigrationState.Failed]: constants.STATE_FAILED,
|
||||
[TdeMigrationState.Pending]: constants.STATE_PENDING,
|
||||
[TdeMigrationState.Running]: constants.STATE_RUNNING,
|
||||
[TdeMigrationState.Succeeded]: constants.STATE_SUCCEEDED,
|
||||
default: undefined
|
||||
};
|
||||
|
||||
|
||||
export class TdeMigrationDialog {
|
||||
|
||||
//private _canceled: boolean = true;
|
||||
private _dialog: azdata.window.Dialog | undefined;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
private _isOpen: boolean = false;
|
||||
private _model!: MigrationStateModel;
|
||||
private _resultsTable!: azdata.TableComponent;
|
||||
private _startMigrationLoader!: azdata.LoadingComponent;
|
||||
private _retryMigrationButton!: azdata.ButtonComponent;
|
||||
private _copyButton!: azdata.ButtonComponent;
|
||||
private _headingText!: azdata.TextComponent;
|
||||
private _progressReportText!: azdata.TextComponent;
|
||||
private _validationResult: any[][] = [];
|
||||
private _dbRowsMap: Map<string, number> = new Map<string, number>();
|
||||
private _tdeMigrationResult: TdeMigrationResult = {
|
||||
state: TdeMigrationState.Pending,
|
||||
dbList: []
|
||||
};
|
||||
private _valdiationErrors: string[] = [];
|
||||
private _completedDatabasesCount: number = 0;
|
||||
|
||||
constructor(
|
||||
model: MigrationStateModel) {
|
||||
this._model = model;
|
||||
}
|
||||
|
||||
public async openDialog(): Promise<void> {
|
||||
if (!this._isOpen) {
|
||||
this._isOpen = true;
|
||||
this._dialog = azdata.window.createModelViewDialog(
|
||||
constants.TDE_MIGRATEDIALOG_TITLE,
|
||||
DialogName,
|
||||
600);
|
||||
|
||||
const promise = this._initializeDialog(this._dialog);
|
||||
azdata.window.openDialog(this._dialog);
|
||||
await promise;
|
||||
|
||||
await this._loadMigrationResults();
|
||||
|
||||
// This will prevent that it tryes to auto run when the last execution was successful, fails don't get persisted on the ui, only reported in the events.
|
||||
if (this._tdeMigrationResult.state === TdeMigrationState.Pending) {
|
||||
await this._runTdeMigration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.registerContent(async (view) => {
|
||||
try {
|
||||
dialog.okButton.label = constants.TDE_MIGRATE_DONE_BUTTON;
|
||||
dialog.okButton.position = 'left';
|
||||
dialog.okButton.enabled = false;
|
||||
dialog.cancelButton.position = 'left';
|
||||
|
||||
this._headingText = view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.TDE_MIGRATE_HEADING,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': '400',
|
||||
'margin-bottom': '10px',
|
||||
},
|
||||
})
|
||||
.component();
|
||||
this._startMigrationLoader = view.modelBuilder.loadingComponent()
|
||||
.withProps({
|
||||
loading: false,
|
||||
CSSStyles: { 'margin': '5px 0 0 10px' }
|
||||
})
|
||||
.component();
|
||||
this._progressReportText = view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: '',
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': '400',
|
||||
'margin-bottom': '10px',
|
||||
'margin-left': '5px'
|
||||
},
|
||||
})
|
||||
.component();
|
||||
|
||||
const headingContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
})
|
||||
.withItems([this._headingText, this._progressReportText, this._startMigrationLoader], { flex: '0 0 auto' })
|
||||
.component();
|
||||
|
||||
this._resultsTable = await this._createResultsTable(view);
|
||||
|
||||
this._retryMigrationButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.restartDataCollection,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 100,
|
||||
label: constants.TDE_MIGRATE_RETRY_VALIDATION,
|
||||
}).component();
|
||||
this._copyButton = view.modelBuilder.button()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.copy,
|
||||
iconHeight: 18,
|
||||
iconWidth: 18,
|
||||
width: 150,
|
||||
label: constants.TDE_MIGRATE_COPY_RESULTS,
|
||||
enabled: false,
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
this._retryMigrationButton.onDidClick(
|
||||
async (e) => await this._retryTdeMigration()));
|
||||
|
||||
this._disposables.push(
|
||||
this._copyButton.onDidClick(
|
||||
async (e) => this._copyValidationResults()));
|
||||
|
||||
const toolbar = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([
|
||||
{ component: this._retryMigrationButton }
|
||||
//{ component: this._copyButton }
|
||||
])
|
||||
.component();
|
||||
|
||||
const resultsHeading = view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.TDE_MIGRATE_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 async _loadMigrationResults(): Promise<void> {
|
||||
const tdeMigrationResult = this._model.tdeMigrationConfig.lastTdeMigrationResult();
|
||||
this._progressReportText.value = '';
|
||||
|
||||
if (tdeMigrationResult.state === TdeMigrationState.Pending) {
|
||||
//First time it is called. Should auto start.
|
||||
this._headingText.value = constants.TDE_MIGRATE_RESULTS_HEADING;
|
||||
|
||||
//Initialize results using the tde enabled databases;
|
||||
tdeMigrationResult.dbList = this._model.tdeMigrationConfig.getTdeEnabledDatabases().map<TdeMigrationDbState>(
|
||||
db => ({
|
||||
name: db,
|
||||
dbState: TdeDatabaseMigrationState.Running,
|
||||
message: ''
|
||||
}
|
||||
));
|
||||
|
||||
this._startMigrationLoader.loading = true;
|
||||
this._retryMigrationButton.enabled = false;
|
||||
this._copyButton.enabled = false;
|
||||
this._dialog!.okButton.enabled = false;
|
||||
this._dialog!.cancelButton.enabled = true;
|
||||
} else {
|
||||
//It already ran. Just load the previous status.
|
||||
this._headingText.value = constants.TDE_MIGRATE_RESULTS_HEADING_PREVIOUS;
|
||||
this._startMigrationLoader.loading = false;
|
||||
this._retryMigrationButton.enabled = true;
|
||||
this._copyButton.enabled = true;
|
||||
this._dialog!.okButton.enabled = true;
|
||||
this._dialog!.cancelButton.enabled = true;
|
||||
}
|
||||
|
||||
//Grab copy of data with a different result reference. Done here because it is closer to the assigment on the true path.
|
||||
this._tdeMigrationResult = {
|
||||
state: tdeMigrationResult.state,
|
||||
dbList: tdeMigrationResult.dbList
|
||||
};
|
||||
|
||||
await this._populateTableResults();
|
||||
}
|
||||
|
||||
private async _retryTdeMigration(): Promise<void> {
|
||||
const tdeMigrationResult = this._model.tdeMigrationConfig.lastTdeMigrationResult();
|
||||
tdeMigrationResult.dbList = this._model.tdeMigrationConfig.getTdeEnabledDatabases().map<TdeMigrationDbState>(
|
||||
db => ({
|
||||
name: db,
|
||||
dbState: TdeDatabaseMigrationState.Running,
|
||||
message: ''
|
||||
}
|
||||
));
|
||||
|
||||
this._tdeMigrationResult = {
|
||||
state: tdeMigrationResult.state,
|
||||
dbList: tdeMigrationResult.dbList
|
||||
};
|
||||
|
||||
await this._populateTableResults();
|
||||
|
||||
await this._runTdeMigration();
|
||||
}
|
||||
|
||||
private _updateProgressText(): void {
|
||||
this._progressReportText.value = constants.TDE_COMPLETED_STATUS(this._completedDatabasesCount, this._model.tdeMigrationConfig.getTdeEnabledDatabasesCount());
|
||||
}
|
||||
|
||||
private async _runTdeMigration(): Promise<void> {
|
||||
//Update the UI buttons
|
||||
this._headingText.value = constants.TDE_MIGRATE_RESULTS_HEADING;
|
||||
this._startMigrationLoader.loading = true;
|
||||
this._retryMigrationButton.enabled = false;
|
||||
this._copyButton.enabled = false;
|
||||
this._dialog!.okButton.enabled = false;
|
||||
this._dialog!.cancelButton.enabled = true;
|
||||
|
||||
|
||||
//Send the external command
|
||||
try {
|
||||
this._completedDatabasesCount = 0;
|
||||
this._updateProgressText();
|
||||
|
||||
//Get access token
|
||||
const accessToken = await azdata.accounts.getAccountSecurityToken(this._model._azureAccount, this._model._azureAccount.properties.tenants[0].id, azdata.AzureResource.ResourceManagement);
|
||||
|
||||
const operationResult = await this._model.startTdeMigration(accessToken!.token, this._updateTableResultRow.bind(this));
|
||||
|
||||
await this._updateTableFromOperationResult(operationResult);
|
||||
|
||||
if (operationResult.success) {
|
||||
this._dialog!.okButton.enabled = true;
|
||||
|
||||
this._tdeMigrationResult = {
|
||||
state: TdeMigrationState.Succeeded,
|
||||
dbList: operationResult.result.map<TdeMigrationDbState>(
|
||||
db => ({
|
||||
name: db.name,
|
||||
dbState: TdeDatabaseMigrationState.Succeeded,
|
||||
message: db.message
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
this._model.tdeMigrationConfig.setTdeMigrationResult(this._tdeMigrationResult); // Set value on success.
|
||||
}
|
||||
else {
|
||||
this._dialog!.okButton.enabled = false;
|
||||
const errorDetails = operationResult.errors.join(EOL);
|
||||
|
||||
logError(TelemetryViews.MigrationLocalStorage, TelemetryErrorName.StartMigrationFailed, errorDetails);
|
||||
}
|
||||
|
||||
this._startMigrationLoader.loading = false;
|
||||
this._retryMigrationButton.enabled = true;
|
||||
this._copyButton.enabled = true;
|
||||
|
||||
this._completedDatabasesCount = this._model.tdeMigrationConfig.getTdeEnabledDatabasesCount(); //Force the total to match
|
||||
this._updateProgressText();
|
||||
|
||||
} catch (error) {
|
||||
//Catch any exception and failed any pending table.
|
||||
this._startMigrationLoader.loading = false;
|
||||
this._retryMigrationButton.enabled = true;
|
||||
this._copyButton.enabled = false;
|
||||
this._dialog!.okButton.enabled = false;
|
||||
this._progressReportText.value = '';
|
||||
}
|
||||
|
||||
this._headingText.value = constants.TDE_MIGRATE_RESULTS_HEADING_COMPLETED;
|
||||
}
|
||||
|
||||
private async _copyValidationResults(): Promise<void> {
|
||||
const errorsText = this._valdiationErrors.join(EOL);
|
||||
const msg = errorsText.length === 0
|
||||
? constants.TDE_MIGRATE_VALIDATION_COMPLETED
|
||||
: constants.TDE_MIGRATE_VALIDATION_COMPLETED_ERRORS(errorsText);
|
||||
return vscode.env.clipboard.writeText(msg);
|
||||
}
|
||||
|
||||
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[TdeValidationResultIndex.status];
|
||||
const errors = results[TdeValidationResultIndex.errors];
|
||||
statusMessages.push(
|
||||
constants.TDE_MIGRATE_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: constants.TDE_MIGRATE_COLUMN_DATABASES,
|
||||
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: constants.TDE_MIGRATE_COLUMN_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 async _updateTableFromOperationResult(operationResult: OperationResult<TdeMigrationDbResult[]>): Promise<void> {
|
||||
let anyRowUpdated = false;
|
||||
|
||||
operationResult.result.forEach((element) => {
|
||||
const rowResultsIndex = this._dbRowsMap.get(element.name)!; //Checked already at the beginning of the method
|
||||
const currentRow = this._validationResult[rowResultsIndex];
|
||||
|
||||
if (!currentRow[TdeValidationResultIndex.updated]) {
|
||||
anyRowUpdated = true;
|
||||
this._updateValidationResultRow(element.name, element.success, element.message);
|
||||
}
|
||||
});
|
||||
|
||||
if (anyRowUpdated) {
|
||||
// Update the table
|
||||
await this._updateTableData();
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateTableResultRow(dbName: string, succeeded: boolean, message: string): Promise<void> {
|
||||
if (!this._dbRowsMap.has(dbName)) {
|
||||
return; //Table not found
|
||||
}
|
||||
|
||||
this._updateValidationResultRow(dbName, succeeded, message);
|
||||
|
||||
// Update the table
|
||||
await this._updateTableData();
|
||||
|
||||
// When the updates come after the method finished. Thread related, out of our control.
|
||||
if (this._completedDatabasesCount < this._model.tdeMigrationConfig.getTdeEnabledDatabasesCount()) {
|
||||
this._completedDatabasesCount++; // Increase the completed count
|
||||
this._updateProgressText();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateValidationResultRow(dbName: string, succeeded: boolean, message: string) {
|
||||
const rowResultsIndex = this._dbRowsMap.get(dbName)!; //Checked already at the beginning of the method
|
||||
const tmpRow = this._buildRow({
|
||||
name: dbName,
|
||||
dbState: (succeeded) ? TdeDatabaseMigrationState.Succeeded : TdeDatabaseMigrationState.Failed,
|
||||
message: message
|
||||
},
|
||||
true);
|
||||
|
||||
// Update the local result
|
||||
this._validationResult[rowResultsIndex] = tmpRow;
|
||||
}
|
||||
|
||||
private async _updateTableData() {
|
||||
const data = this._validationResult.map(row => [
|
||||
row[TdeValidationResultIndex.name],
|
||||
row[TdeValidationResultIndex.icon],
|
||||
row[TdeValidationResultIndex.status]]);
|
||||
|
||||
await this._resultsTable.updateProperty('data', data);
|
||||
}
|
||||
|
||||
private async _populateTableResults(): Promise<void> {
|
||||
//Create the local result from the model.
|
||||
this._validationResult = this._tdeMigrationResult.dbList.map(db => this._buildRow(db));
|
||||
this._dbRowsMap = this._validationResult.reduce(function (map: Map<string, number>, row: any[], currentIndex) {
|
||||
const dbName = row[TdeValidationResultIndex.name];
|
||||
map.set(dbName, currentIndex);
|
||||
return map;
|
||||
}, new Map<string, number>());
|
||||
|
||||
//Update the table.
|
||||
await this._updateTableData();
|
||||
}
|
||||
|
||||
private _buildRow(db: TdeMigrationDbState, updated: boolean = false): any[] {
|
||||
|
||||
const statusMsg = ValidationStatusLookup[db.dbState];
|
||||
|
||||
const statusMessage = (db.dbState === TdeDatabaseMigrationState.Failed || db.dbState === TdeDatabaseMigrationState.Canceled)
|
||||
? constants.TDE_MIGRATE_STATUS_ERROR(db.dbState, db.message)
|
||||
: statusMsg;
|
||||
|
||||
const row: any[] = [
|
||||
db.name,
|
||||
<azdata.IconColumnCellValue>{
|
||||
icon: this._getValidationStateImage(db.dbState),
|
||||
title: statusMessage,
|
||||
},
|
||||
ValidationStatusLookup[db.dbState],
|
||||
db.message,
|
||||
statusMsg,
|
||||
updated
|
||||
];
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private _getValidationStateImage(state: TdeDatabaseMigrationState): azdata.IconPath {
|
||||
switch (state) {
|
||||
case TdeDatabaseMigrationState.Canceled:
|
||||
return IconPathHelper.cancel;
|
||||
case TdeDatabaseMigrationState.Failed:
|
||||
return IconPathHelper.error;
|
||||
case TdeDatabaseMigrationState.Running:
|
||||
return IconPathHelper.inProgressMigration;
|
||||
case TdeDatabaseMigrationState.Succeeded:
|
||||
return IconPathHelper.completedMigration;
|
||||
default:
|
||||
return IconPathHelper.notStartedMigration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user