diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index 2713154667..eb1bc5bdaf 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath, DisplayType, Component } from 'azdata'; +import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath, DisplayType, Component, ModelView, DeclarativeTableComponent, DeclarativeDataType, FlexContainer } from 'azdata'; import * as vscode from 'vscode'; import { IconPathHelper } from '../constants/iconPathHelper'; import * as crypto from 'crypto'; @@ -14,6 +14,8 @@ import { logError, TelemetryViews } from '../telemetry'; import { AdsMigrationStatus } from '../dashboard/tabBase'; import { getMigrationMode, getMigrationStatus, getMigrationTargetType, hasRestoreBlockingReason, PipelineStatusCodes } from '../constants/helper'; import * as os from 'os'; +import * as styles from '../constants/styles'; +import { SqlMigrationService, getSqlMigrationServiceAuthKeys, regenerateSqlMigrationServiceAuthKey } from './azure'; export type TargetServerType = azure.SqlVMServer | azureResource.AzureSqlManagedInstance | azure.AzureSqlDatabaseServer; @@ -958,3 +960,210 @@ export async function isAdmin(): Promise { return isAdmin; } + +export function createAuthenticationKeyTable(view: ModelView, columnWidth: string, stretchWidth: string): DeclarativeTableComponent { + const WIZARD_INPUT_COMPONENT_WIDTH = '600px'; + + const authKeyTable = view.modelBuilder.declarativeTable() + .withProps({ + ariaLabel: constants.DATABASE_MIGRATION_SERVICE_AUTHENTICATION_KEYS, + columns: [ + { + displayName: constants.NAME, + valueType: DeclarativeDataType.string, + width: columnWidth, + isReadOnly: true, + rowCssStyles: { ...styles.BODY_CSS }, + headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' } + }, + { + displayName: constants.AUTH_KEY_COLUMN_HEADER, + valueType: DeclarativeDataType.string, + width: stretchWidth, + isReadOnly: true, + rowCssStyles: { ...styles.BODY_CSS }, + headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' } + }, + { + displayName: '', + valueType: DeclarativeDataType.component, + width: columnWidth, + isReadOnly: true, + rowCssStyles: { ...styles.BODY_CSS }, + headerCssStyles: { ...styles.BODY_CSS } + } + ], + CSSStyles: { 'margin-top': '5px', 'width': WIZARD_INPUT_COMPONENT_WIDTH } + }).component(); + return authKeyTable; +} + +export async function refreshAuthenticationKeyTable(view: ModelView, table: DeclarativeTableComponent, account: Account, subscription: azureResource.AzureResourceSubscription, resourceGroup: string, location: string, service: SqlMigrationService): Promise { + var _disposables: vscode.Disposable[] = []; + + const copyKey1Button = view.modelBuilder.button().withProps({ + title: constants.COPY_KEY1, + iconPath: IconPathHelper.copy, + ariaLabel: constants.COPY_KEY1, + }).component(); + + _disposables.push(copyKey1Button.onDidClick(async (e) => { + await vscode.env.clipboard.writeText(table.dataValues![0][1].value); + void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP); + })); + + const copyKey2Button = view.modelBuilder.button().withProps({ + title: constants.COPY_KEY2, + iconPath: IconPathHelper.copy, + ariaLabel: constants.COPY_KEY2, + }).component(); + + _disposables.push(copyKey2Button.onDidClick(async (e) => { + await vscode.env.clipboard.writeText(table.dataValues![1][1].value); + void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP); + })); + + const refreshKey1Button = view.modelBuilder.button().withProps({ + title: constants.REFRESH_KEY1, + iconPath: IconPathHelper.refresh, + ariaLabel: constants.REFRESH_KEY1, + }).component(); + + _disposables.push(refreshKey1Button.onDidClick(async (e) => { + const keys = await regenerateSqlMigrationServiceAuthKey( + account, + subscription, + resourceGroup, + location, + service.name, + 'authKey1'); + + const dataValues = table.dataValues!; + dataValues![0][1].value = keys.authKey1; + await table.setDataValues([]); + await table.setDataValues(dataValues); + await vscode.window.showInformationMessage(constants.AUTH_KEY_REFRESHED(constants.SERVICE_KEY1_LABEL)); + })); + + const refreshKey2Button = view.modelBuilder.button().withProps({ + title: constants.REFRESH_KEY2, + iconPath: IconPathHelper.refresh, + ariaLabel: constants.REFRESH_KEY2, + }).component(); + + _disposables.push(refreshKey2Button.onDidClick(async (e) => { + const keys = await regenerateSqlMigrationServiceAuthKey( + account, + subscription, + resourceGroup, + location, + service.name, + 'authKey2'); + + const dataValues = table.dataValues!; + dataValues![1][1].value = keys.authKey2; + await table.setDataValues([]); + await table.setDataValues(dataValues); + await vscode.window.showInformationMessage(constants.AUTH_KEY_REFRESHED(constants.SERVICE_KEY2_LABEL)); + })); + + const keys = await getSqlMigrationServiceAuthKeys( + account, + subscription, + resourceGroup, + location, + service.name); + + + await table.updateProperties({ + dataValues: [ + [ + { + value: constants.SERVICE_KEY1_LABEL + }, + { + value: keys.authKey1 + }, + { + value: view.modelBuilder.flexContainer().withItems([copyKey1Button, refreshKey1Button]).component() + } + ], + [ + { + value: constants.SERVICE_KEY2_LABEL + }, + { + value: keys.authKey2 + }, + { + value: view.modelBuilder.flexContainer().withItems([copyKey2Button, refreshKey2Button]).component() + } + ] + ] + }); +} + +export function createRegistrationInstructions(view: ModelView, testConnectionButton: boolean): FlexContainer { + const setupIRHeadingText = view.modelBuilder.text().withProps({ + value: constants.SERVICE_CONTAINER_HEADING, + CSSStyles: { + ...styles.LABEL_CSS + } + }).component(); + + const setupIRdescription1 = view.modelBuilder.text().withProps({ + value: constants.SERVICE_CONTAINER_DESCRIPTION1, + CSSStyles: { + ...styles.BODY_CSS + } + }).component(); + + const setupIRdescription2 = view.modelBuilder.text().withProps({ + value: constants.SERVICE_CONTAINER_DESCRIPTION2, + CSSStyles: { + ...styles.BODY_CSS + } + }).component(); + + const irSetupStep1Text = view.modelBuilder.text().withProps({ + value: constants.SERVICE_STEP1, + CSSStyles: { + ...styles.BODY_CSS + }, + links: [ + { + text: constants.SERVICE_STEP1_LINK, + url: 'https://aka.ms/sql-migration-shir-download' + } + ] + }).component(); + + const irSetupStep2Text = view.modelBuilder.text().withProps({ + value: constants.SERVICE_STEP2, + CSSStyles: { + ...styles.BODY_CSS + } + }).component(); + + const irSetupStep3Text = view.modelBuilder.text().withProps({ + value: constants.SERVICE_STEP3(testConnectionButton), + CSSStyles: { + 'margin-top': '10px', + 'margin-bottom': '10px', + ...styles.BODY_CSS + } + }).component(); + + return view.modelBuilder.flexContainer().withItems( + [ + setupIRHeadingText, + setupIRdescription1, + setupIRdescription2, + irSetupStep1Text, + irSetupStep2Text, + irSetupStep3Text, + ] + ).withLayout({ + flexFlow: 'column' + }).component(); +} diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 90d94f4c14..3e84482760 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -738,15 +738,23 @@ export const MIGRATION_SERVICE_LOCATION_INFO = localize('sql.migration.services. export const MIGRATION_SERVICE_RESOURCE_GROUP_INFO = localize('sql.migration.services.resource.group', "Resource group for your Azure Database Migration Service."); export const MIGRATION_SERVICE_NAME_INFO = localize('sql.migration.services.name', "Azure Database Migration Service name."); export const MIGRATION_SERVICE_TARGET_INFO = localize('sql.migration.services.target', "Azure SQL target selected as default."); -export const MIGRATION_SERVICE_DIALOG_DESCRIPTION = localize('sql.migration.services.container.description', "Enter the information below to add a new Azure Database Migration Service."); +export function MIGRATION_SERVICE_DIALOG_DESCRIPTION(networkShareScenario: boolean) { + return networkShareScenario + ? localize('sql.migration.services.container.description.network', "Enter the information below to add a new Azure Database Migration Service. To register self-hosted integration runtime, select 'My database backups are on a network share' on the previous page.") + : localize('sql.migration.services.container.description', "Enter the information below to add a new Azure Database Migration Service."); +} export const LOADING_MIGRATION_SERVICES = localize('sql.migration.service.container.loading.help', "Loading Migration Services"); -export const SERVICE_CONTAINER_HEADING = localize('sql.migration.service.container.heading', "Setup integration runtime"); -export const SERVICE_CONTAINER_DESCRIPTION1 = localize('sql.migration.service.container.container.description1', "Azure Database Migration Service leverages Azure Data Factory's self-hosted integration runtime to upload backups from on-premises network file share to Azure."); -export const SERVICE_CONTAINER_DESCRIPTION2 = localize('sql.migration.service.container.container.description2', "Follow the instructions below to setup self-hosted integration runtime."); +export const SERVICE_CONTAINER_HEADING = localize('sql.migration.service.container.heading', "Set up integration runtime"); +export const SERVICE_CONTAINER_DESCRIPTION1 = localize('sql.migration.service.container.container.description1', "Azure Database Migration Service leverages Azure Data Factory's self-hosted integration runtime to handle connectivity between source and target and upload backups from an on-premises network file share to Azure (if applicable)."); +export const SERVICE_CONTAINER_DESCRIPTION2 = localize('sql.migration.service.container.container.description2', "Follow the instructions below to set up self-hosted integration runtime."); export const SERVICE_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}"); export const SERVICE_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime"); -export const SERVICE_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime"); -export const SERVICE_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Click on 'Test connection' button to check the connection between Azure Database Migration Service and integration runtime"); +export const SERVICE_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use the keys below to register your integration runtime"); +export function SERVICE_STEP3(testConnectionButton: boolean) { + return testConnectionButton + ? localize('sql.migration.ir.setup.step3', "Step 3: Click on the 'Test connection' button to check the connection between Azure Database Migration Service and integration runtime") + : localize('sql.migration.ir.setup.step3.alternate', "Step 3: Click on the Refresh button above to check the connection between Azure Database Migration Service and integration runtime") +} export const SERVICE_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection status"); export const SERVICE_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1"); export const SERVICE_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2"); @@ -760,8 +768,10 @@ export const AUTH_KEY_COLUMN_HEADER = localize('sql.migration.authKeys.header', export function AUTH_KEY_REFRESHED(keyName: string): string { return localize('sql.migration.authKeys.refresh.message', "Authentication key '{0}' has been refreshed.", keyName); } -export function SERVICE_NOT_READY(serviceName: string): string { - return localize('sql.migration.service.not.ready', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted integration runtime on any node.", serviceName); +export function SERVICE_NOT_READY(serviceName: string, instructionsBelow: boolean): string { + return instructionsBelow + ? localize('sql.migration.service.not.ready.below', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted integration runtime on any node.\n\nSee below for registration instructions.", serviceName) + : localize('sql.migration.service.not.ready', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted integration runtime on any node.", serviceName); } export function SERVICE_ERROR_NOT_READY(serviceName: string, error: string): string { return localize('sql.migration.service.error.not.ready', @@ -769,8 +779,11 @@ export function SERVICE_ERROR_NOT_READY(serviceName: string, error: string): str serviceName, error); } -export function SERVICE_READY(serviceName: string, host: string): string { - return localize('sql.migration.service.ready', "Azure Database Migration Service '{0}' is connected to self-hosted integration runtime running on the node - {1}", serviceName, host); +export function SERVICE_READY(serviceName: string, nodes: string, instructionsBelow: boolean): string { + return instructionsBelow + ? localize('sql.migration.service.ready.below', "Azure Database Migration Service '{0}' is connected to self-hosted integration runtime running on node(s) - {1}\n\nFor improved performance and high availability, you can register additional nodes. See below for registration instructions.", serviceName, nodes) + : localize('sql.migration.service.ready', "Azure Database Migration Service '{0}' is connected to self-hosted integration runtime running on node(s) - {1}\n\nFor improved performance and high availability, you can register additional nodes.", serviceName, nodes); + } export const INVALID_SERVICE_NAME_ERROR = localize('sql.migration.invalid.service.name.error', "Enter a valid name for the Migration Service."); export const SERVICE_NOT_FOUND = localize('sql.migration.service.not.found', "No Migration Services found. To continue, create a new one."); diff --git a/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts index 01d7baa1fc..90081209f2 100644 --- a/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts +++ b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts @@ -5,15 +5,13 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { createSqlMigrationService, getResourceName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlMigrationService } from '../../api/azure'; +import { createSqlMigrationService, getResourceName, getSqlMigrationService, getSqlMigrationServiceMonitoringData, SqlMigrationService } from '../../api/azure'; import { MigrationStateModel } from '../../models/stateMachine'; import { logError, TelemetryViews } from '../../telemetry'; import * as constants from '../../constants/strings'; import * as os from 'os'; import { azureResource } from 'azurecore'; -import { IconPathHelper } from '../../constants/iconPathHelper'; import { CreateResourceGroupDialog } from '../createResourceGroup/createResourceGroupDialog'; -import { createAuthenticationKeyTable } from '../../wizard/integrationRuntimePage'; import * as EventEmitter from 'events'; import * as utils from '../../api/utils'; import * as styles from '../../constants/styles'; @@ -33,10 +31,6 @@ export class CreateSqlMigrationServiceDialog { private _refreshLoadingComponent!: azdata.LoadingComponent; private migrationServiceAuthKeyTable!: azdata.DeclarativeTableComponent; private _connectionStatus!: azdata.InfoBoxComponent; - private _copyKey1Button!: azdata.ButtonComponent; - private _copyKey2Button!: azdata.ButtonComponent; - private _refreshKey1Button!: azdata.ButtonComponent; - private _refreshKey2Button!: azdata.ButtonComponent; private _setupContainer!: azdata.FlexContainer; private _resourceGroupPreset!: string; @@ -119,7 +113,17 @@ export class CreateSqlMigrationServiceDialog { }; } else { await this.refreshStatus(); - await this.refreshAuthTable(); + + await utils.refreshAuthenticationKeyTable( + this._view, + this.migrationServiceAuthKeyTable, + this._model._azureAccount, + subscription, + resourceGroup.name, + location, + this._createdMigrationService); + + this._setupContainer.display = 'inline'; this._testConnectionButton.hidden = false; } @@ -214,7 +218,7 @@ export class CreateSqlMigrationServiceDialog { private async migrationServiceDropdownContainer(): Promise { const dialogDescription = this._view.modelBuilder.text().withProps({ - value: constants.MIGRATION_SERVICE_DIALOG_DESCRIPTION, + value: constants.MIGRATION_SERVICE_DIALOG_DESCRIPTION(!this._model.isSqlDbTarget), CSSStyles: { ...styles.BODY_CSS } @@ -410,56 +414,7 @@ export class CreateSqlMigrationServiceDialog { } private createServiceStatus(): azdata.FlexContainer { - - const setupIRHeadingText = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_CONTAINER_HEADING, - CSSStyles: { - ...styles.LABEL_CSS - } - }).component(); - - const setupIRdescription1 = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_CONTAINER_DESCRIPTION1, - CSSStyles: { - ...styles.BODY_CSS - } - }).component(); - - const setupIRdescription2 = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_CONTAINER_DESCRIPTION2, - CSSStyles: { - ...styles.BODY_CSS - } - }).component(); - - const irSetupStep1Text = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_STEP1, - CSSStyles: { - ...styles.BODY_CSS - }, - links: [ - { - text: constants.SERVICE_STEP1_LINK, - url: 'https://www.microsoft.com/download/details.aspx?id=39717' - } - ] - }).component(); - - const irSetupStep2Text = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_STEP2, - CSSStyles: { - ...styles.BODY_CSS - } - }).component(); - - const irSetupStep3Text = this._view.modelBuilder.text().withProps({ - value: constants.SERVICE_STEP3, - CSSStyles: { - 'margin-top': '10px', - 'margin-bottom': '10px', - ...styles.BODY_CSS - } - }).component(); + const instructions = utils.createRegistrationInstructions(this._view, true); this._connectionStatus = this._view.modelBuilder.infoBox().withProps({ text: '', @@ -480,17 +435,12 @@ export class CreateSqlMigrationServiceDialog { } }).component(); - this.migrationServiceAuthKeyTable = createAuthenticationKeyTable(this._view); + this.migrationServiceAuthKeyTable = utils.createAuthenticationKeyTable(this._view, '50px', '500px'); this._setupContainer = this._view.modelBuilder.flexContainer().withItems( [ - setupIRHeadingText, - setupIRdescription1, - setupIRdescription2, - irSetupStep1Text, - irSetupStep2Text, + instructions, this.migrationServiceAuthKeyTable, - irSetupStep3Text, this._connectionStatus, this._refreshLoadingComponent ], { @@ -550,16 +500,16 @@ export class CreateSqlMigrationServiceDialog { if (state === 'Online') { await this._connectionStatus.updateProperties({ - text: constants.SERVICE_READY(this._createdMigrationService!.name, this.irNodes.join(', ')), + text: constants.SERVICE_READY(this._createdMigrationService!.name, this.irNodes.join(', '), false), style: 'success', CSSStyles: { ...styles.BODY_CSS } }); } else { - this._connectionStatus.text = constants.SERVICE_NOT_READY(this._createdMigrationService!.name); + this._connectionStatus.text = constants.SERVICE_NOT_READY(this._createdMigrationService!.name, false); await this._connectionStatus.updateProperties({ - text: constants.SERVICE_NOT_READY(this._createdMigrationService!.name), + text: constants.SERVICE_NOT_READY(this._createdMigrationService!.name, false), style: 'warning', CSSStyles: { ...styles.BODY_CSS @@ -570,89 +520,6 @@ export class CreateSqlMigrationServiceDialog { } } - private async refreshAuthTable(): Promise { - const subscription = this._model._sqlMigrationServiceSubscription; - const resourceGroupId = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; - const resourceGroup = getResourceName(resourceGroupId); - const location = this._model._location.name; - const keys = await getSqlMigrationServiceAuthKeys( - this._model._azureAccount, - subscription, - resourceGroup, - location, - this._createdMigrationService!.name); - - this._copyKey1Button = this._view.modelBuilder.button().withProps({ - title: constants.COPY_KEY1, - iconPath: IconPathHelper.copy, - ariaLabel: constants.COPY_KEY1, - }).component(); - - this._disposables.push(this._copyKey1Button.onDidClick(async (e) => { - await vscode.env.clipboard.writeText(this.migrationServiceAuthKeyTable.dataValues![0][1].value); - void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP); - })); - - this._copyKey2Button = this._view.modelBuilder.button().withProps({ - title: constants.COPY_KEY2, - iconPath: IconPathHelper.copy, - ariaLabel: constants.COPY_KEY2, - }).component(); - - this._disposables.push(this._copyKey2Button.onDidClick(async (e) => { - await vscode.env.clipboard.writeText(this.migrationServiceAuthKeyTable.dataValues![1][1].value); - void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP); - })); - - this._refreshKey1Button = this._view.modelBuilder.button().withProps({ - title: constants.REFRESH_KEY1, - iconPath: IconPathHelper.refresh, - ariaLabel: constants.REFRESH_KEY1, - }).component(); - - this._disposables.push(this._refreshKey1Button.onDidClick((e) => { - //TODO: add refresh logic - })); - - this._refreshKey2Button = this._view.modelBuilder.button().withProps({ - title: constants.REFRESH_KEY2, - iconPath: IconPathHelper.refresh, - ariaLabel: constants.REFRESH_KEY2, - }).component(); - - this._disposables.push(this._refreshKey2Button.onDidClick((e) => { - //TODO: add refresh logic - })); - - await this.migrationServiceAuthKeyTable.updateProperties({ - dataValues: [ - [ - { - value: constants.SERVICE_KEY1_LABEL - }, - { - value: keys.authKey1 - }, - { - value: this._view.modelBuilder.flexContainer().withItems([this._copyKey1Button, this._refreshKey1Button]).component() - } - ], - [ - { - value: constants.SERVICE_KEY2_LABEL - }, - { - value: keys.authKey2 - }, - { - value: this._view.modelBuilder.flexContainer().withItems([this._copyKey2Button, this._refreshKey2Button]).component() - } - ] - ] - }); - - } - private setDialogMessage(message: string, level: azdata.window.MessageLevel = azdata.window.MessageLevel.Error): void { this._dialogObject.message = { text: message, diff --git a/extensions/sql-migration/src/dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog.ts b/extensions/sql-migration/src/dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog.ts index b68bfb02e9..2807b110b7 100644 --- a/extensions/sql-migration/src/dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog.ts +++ b/extensions/sql-migration/src/dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog.ts @@ -5,22 +5,18 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { DatabaseMigration, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, regenerateSqlMigrationServiceAuthKey } from '../../api/azure'; +import { DatabaseMigration, getSqlMigrationServiceMonitoringData } from '../../api/azure'; import { IconPathHelper } from '../../constants/iconPathHelper'; import * as constants from '../../constants/strings'; import { MigrationServiceContext } from '../../models/migrationLocalStorage'; import * as styles from '../../constants/styles'; +import { createAuthenticationKeyTable, createRegistrationInstructions, refreshAuthenticationKeyTable } from '../../api/utils'; const CONTROL_MARGIN = '10px'; -const COLUMN_WIDTH = '50px'; const STRETCH_WIDTH = '100%'; const LABEL_MARGIN = '0 10px 0 10px'; const VALUE_MARGIN = '0 10px 10px 10px'; -const INFO_VALUE_MARGIN = '0 10px 0 0'; const ICON_SIZE = '28px'; -const IMAGE_SIZE = '21px'; -const AUTH_KEY1 = 'authKey1'; -const AUTH_KEY2 = 'authKey2'; export class SqlMigrationServiceDetailsDialog { @@ -35,7 +31,7 @@ export class SqlMigrationServiceDetailsDialog { this._dialog = azdata.window.createModelViewDialog( '', 'SqlMigrationServiceDetailsDialog', - 580, + 750, 'flyout'); } @@ -62,7 +58,13 @@ export class SqlMigrationServiceDetailsDialog { } private async createServiceContent(view: azdata.ModelView, serviceContext: MigrationServiceContext, migration: DatabaseMigration): Promise { - this._migrationServiceAuthKeyTable = this._createIrTable(view); + const instructions = createRegistrationInstructions(view, false); + await instructions.updateCssStyles({ + ...styles.BODY_CSS, + 'margin': LABEL_MARGIN, + }) + + this._migrationServiceAuthKeyTable = createAuthenticationKeyTable(view, '50px', '100%'); const serviceNode = (await getSqlMigrationServiceMonitoringData( serviceContext.azureAccount!, serviceContext.subscription!, @@ -88,11 +90,7 @@ export class SqlMigrationServiceDetailsDialog { this._createTextItem(view, serviceContext.migrationService?.properties.resourceGroup!, VALUE_MARGIN), this._createTextItem(view, constants.SQL_MIGRATION_SERVICE_DETAILS_IR_LABEL, LABEL_MARGIN), this._createTextItem(view, serviceNodeName, VALUE_MARGIN), - this._createTextItem( - view, - constants.SQL_MIGRATION_SERVICE_DETAILS_AUTH_KEYS_LABEL, - INFO_VALUE_MARGIN, - constants.SQL_MIGRATION_SERVICE_DETAILS_AUTH_KEYS_TITLE), + instructions, this._migrationServiceAuthKeyTable, ]) .withLayout({ flexFlow: 'column' }) @@ -100,7 +98,16 @@ export class SqlMigrationServiceDetailsDialog { .component(); await view.initializeModel(flexContainer); - return await this._refreshAuthTable(view, serviceContext, migration); + + await refreshAuthenticationKeyTable( + view, + this._migrationServiceAuthKeyTable, + serviceContext.azureAccount!, + serviceContext.subscription!, + serviceContext.migrationService?.properties.resourceGroup!, + serviceContext.migrationService?.location.toUpperCase()!, + serviceContext.migrationService! + ); } private _createHeading(view: azdata.ModelView, migration: DatabaseMigration): azdata.FlexContainer { @@ -164,153 +171,4 @@ export class SqlMigrationServiceDetailsDialog { }) .component(); } - - private _createIrTable(view: azdata.ModelView): azdata.DeclarativeTableComponent { - return view.modelBuilder - .declarativeTable() - .withProps({ - columns: [ - this._createColumn(constants.NAME, COLUMN_WIDTH, azdata.DeclarativeDataType.string), - this._createColumn(constants.AUTH_KEY_COLUMN_HEADER, STRETCH_WIDTH, azdata.DeclarativeDataType.string), - this._createColumn('', COLUMN_WIDTH, azdata.DeclarativeDataType.component), - ], - CSSStyles: { - 'margin': VALUE_MARGIN, - 'text-align': 'left', - }, - }) - .component(); - } - - private _createColumn(name: string, width: string, valueType: azdata.DeclarativeDataType): azdata.DeclarativeTableColumn { - return { - displayName: name, - valueType: valueType, - width: width, - isReadOnly: true, - rowCssStyles: { - ...styles.BODY_CSS - }, - headerCssStyles: { - ...styles.BODY_CSS - }, - }; - } - - private async _regenerateAuthKey(view: azdata.ModelView, serviceContext: MigrationServiceContext, migration: DatabaseMigration, keyName: string): Promise { - const keys = await regenerateSqlMigrationServiceAuthKey( - serviceContext.azureAccount!, - serviceContext.subscription!, - serviceContext.migrationService?.properties.resourceGroup!, - serviceContext.migrationService?.properties.location?.toUpperCase()!, - serviceContext.migrationService?.name!, - keyName); - - if (keys?.authKey1 && keyName === AUTH_KEY1) { - await this._updateTableCell(this._migrationServiceAuthKeyTable, 0, 1, keys.authKey1, constants.SERVICE_KEY1_LABEL); - } - else if (keys?.authKey2 && keyName === AUTH_KEY2) { - await this._updateTableCell(this._migrationServiceAuthKeyTable, 1, 1, keys.authKey2, constants.SERVICE_KEY2_LABEL); - } - } - - private async _updateTableCell(table: azdata.DeclarativeTableComponent, row: number, col: number, value: string, keyName: string): Promise { - const dataValues = table.dataValues!; - dataValues![row][col].value = value; - await table.setDataValues([]); - await table.setDataValues(dataValues); - await vscode.window.showInformationMessage(constants.AUTH_KEY_REFRESHED(keyName)); - } - - private async _refreshAuthTable(view: azdata.ModelView, serviceContext: MigrationServiceContext, migration: DatabaseMigration): Promise { - const keys = await getSqlMigrationServiceAuthKeys( - serviceContext.azureAccount!, - serviceContext.subscription!, - serviceContext.migrationService?.properties.resourceGroup!, - serviceContext.migrationService?.location.toUpperCase()!, - serviceContext.migrationService?.name!); - - const copyKey1Button = view.modelBuilder - .button() - .withProps({ - title: constants.COPY_KEY1, - iconPath: IconPathHelper.copy, - height: IMAGE_SIZE, - width: IMAGE_SIZE, - ariaLabel: constants.COPY_KEY1, - }) - .component(); - - this._disposables.push(copyKey1Button.onDidClick(async (e) => { - await vscode.env.clipboard.writeText(keys.authKey1); - void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP); - })); - - const copyKey2Button = view.modelBuilder - .button() - .withProps({ - title: constants.COPY_KEY2, - iconPath: IconPathHelper.copy, - height: IMAGE_SIZE, - width: IMAGE_SIZE, - ariaLabel: constants.COPY_KEY2, - }) - .component(); - - this._disposables.push(copyKey2Button.onDidClick(async (e) => { - await vscode.env.clipboard.writeText(keys.authKey2); - void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP); - })); - - const refreshKey1Button = view.modelBuilder - .button() - .withProps({ - title: constants.REFRESH_KEY1, - iconPath: IconPathHelper.refresh, - height: IMAGE_SIZE, - width: IMAGE_SIZE, - ariaLabel: constants.REFRESH_KEY1, - }) - .component(); - this._disposables.push(refreshKey1Button.onDidClick( - async (e) => await this._regenerateAuthKey(view, serviceContext, migration, AUTH_KEY1))); - - const refreshKey2Button = view.modelBuilder - .button() - .withProps({ - title: constants.REFRESH_KEY2, - iconPath: IconPathHelper.refresh, - height: IMAGE_SIZE, - width: IMAGE_SIZE, - ariaLabel: constants.REFRESH_KEY2, - }) - .component(); - this._disposables.push(refreshKey2Button.onDidClick( - async (e) => await this._regenerateAuthKey(view, serviceContext, migration, AUTH_KEY2))); - - await this._migrationServiceAuthKeyTable.updateProperties({ - dataValues: [ - [ - { value: constants.SERVICE_KEY1_LABEL }, - { value: keys.authKey1 }, - { - value: view.modelBuilder - .flexContainer() - .withItems([copyKey1Button, refreshKey1Button]) - .component() - } - ], - [ - { value: constants.SERVICE_KEY2_LABEL }, - { value: keys.authKey2 }, - { - value: view.modelBuilder - .flexContainer() - .withItems([copyKey2Button, refreshKey2Button]) - .component() - } - ] - ] - }); - } } diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts index 689173a10f..60e3903a95 100644 --- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts +++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts @@ -10,7 +10,7 @@ import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEv import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog'; import * as constants from '../constants/strings'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; -import { getFullResourceGroupFromId, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlVMServer } from '../api/azure'; +import { getFullResourceGroupFromId, getSqlMigrationService, getSqlMigrationServiceMonitoringData, SqlVMServer } from '../api/azure'; import { IconPathHelper } from '../constants/iconPathHelper'; import { logError, TelemetryViews } from '../telemetry'; import * as utils from '../api/utils'; @@ -27,10 +27,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage { private _dmsStatusInfoBox!: azdata.InfoBoxComponent; private _authKeyTable!: azdata.DeclarativeTableComponent; private _refreshButton!: azdata.ButtonComponent; - private _copy1!: azdata.ButtonComponent; - private _copy2!: azdata.ButtonComponent; - private _refresh1!: azdata.ButtonComponent; - private _refresh2!: azdata.ButtonComponent; private _onlineButton!: azdata.RadioButtonComponent; private _offlineButton!: azdata.RadioButtonComponent; private _modeContainer!: azdata.FlexContainer; @@ -507,58 +503,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage { CSSStyles: { ...styles.BODY_CSS } }).component(); - const authenticationKeysLabel = this._view.modelBuilder.text() - .withProps({ - value: constants.AUTHENTICATION_KEYS, - CSSStyles: { ...styles.LABEL_CSS } - }).component(); + const instructions = utils.createRegistrationInstructions(this._view, false); - this._copy1 = this._view.modelBuilder.button() - .withProps({ - title: constants.COPY_KEY1, - iconPath: IconPathHelper.copy, - ariaLabel: constants.COPY_KEY1, - }).component(); - - this._disposables.push( - this._copy1.onDidClick( - async (e) => { - await vscode.env.clipboard.writeText(this._authKeyTable.dataValues![0][1].value); - void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP); - })); - - this._copy2 = this._view.modelBuilder.button() - .withProps({ - title: constants.COPY_KEY2, - iconPath: IconPathHelper.copy, - ariaLabel: constants.COPY_KEY2, - }).component(); - - this._disposables.push( - this._copy2.onDidClick(async (e) => { - await vscode.env.clipboard.writeText(this._authKeyTable.dataValues![1][1].value); - void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP); - })); - - this._refresh1 = this._view.modelBuilder.button() - .withProps({ - title: constants.REFRESH_KEY1, - iconPath: IconPathHelper.refresh, - ariaLabel: constants.REFRESH_KEY1, - }).component(); - - this._refresh2 = this._view.modelBuilder.button() - .withProps({ - title: constants.REFRESH_KEY2, - iconPath: IconPathHelper.refresh, - ariaLabel: constants.REFRESH_KEY2, - }).component(); - - this._authKeyTable = createAuthenticationKeyTable(this._view); + this._authKeyTable = utils.createAuthenticationKeyTable(this._view, '50px', '500px'); statusContainer.addItems([ this._dmsStatusInfoBox, - authenticationKeysLabel, + instructions, this._authKeyTable]); container.addItems([ @@ -686,53 +637,23 @@ export class IntergrationRuntimePage extends MigrationWizardPage { // exit if new call has started if (callSequence !== this._lastIn) { return; } - const migrationServiceAuthKeys = await getSqlMigrationServiceAuthKeys( - account, - subscription, - resourceGroup, - location, - serviceName); - - // exit if new call has started - if (callSequence !== this._lastIn) { return; } - const state = migrationService.properties.integrationRuntimeState; if (state === 'Online') { await this._dmsStatusInfoBox.updateProperties({ - text: constants.SERVICE_READY(serviceName, nodeNames.join(', ')), + text: constants.SERVICE_READY(serviceName, nodeNames.join(', '), true), style: 'success' }); } else { await this._dmsStatusInfoBox.updateProperties({ - text: constants.SERVICE_NOT_READY(serviceName), + text: constants.SERVICE_NOT_READY(serviceName, true), style: 'error' }); } - const data = [ - [ - { value: constants.SERVICE_KEY1_LABEL }, - { value: migrationServiceAuthKeys.authKey1 }, - { - value: this._view.modelBuilder.flexContainer() - .withItems([this._copy1, this._refresh1]) - .component() - } - ], - [ - { value: constants.SERVICE_KEY2_LABEL }, - { value: migrationServiceAuthKeys.authKey2 }, - { - value: this._view.modelBuilder.flexContainer() - .withItems([this._copy2, this._refresh2]) - .component() - } - ]]; - // exit if new call has started if (callSequence !== this._lastIn) { return; } - await this._authKeyTable.setDataValues(data); + await utils.refreshAuthenticationKeyTable(this._view, this._authKeyTable, account, subscription, resourceGroup, location, migrationService); this.migrationStateModel._sqlMigrationService = migrationService; this.migrationStateModel._sqlMigrationServiceSubscription = subscription; @@ -752,38 +673,3 @@ export class IntergrationRuntimePage extends MigrationWizardPage { } } } - -export function createAuthenticationKeyTable(view: azdata.ModelView,): azdata.DeclarativeTableComponent { - const authKeyTable = view.modelBuilder.declarativeTable() - .withProps({ - ariaLabel: constants.DATABASE_MIGRATION_SERVICE_AUTHENTICATION_KEYS, - columns: [ - { - displayName: constants.NAME, - valueType: azdata.DeclarativeDataType.string, - width: '50px', - isReadOnly: true, - rowCssStyles: { ...styles.BODY_CSS }, - headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' } - }, - { - displayName: constants.AUTH_KEY_COLUMN_HEADER, - valueType: azdata.DeclarativeDataType.string, - width: '500px', - isReadOnly: true, - rowCssStyles: { ...styles.BODY_CSS }, - headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' } - }, - { - displayName: '', - valueType: azdata.DeclarativeDataType.component, - width: '30px', - isReadOnly: true, - rowCssStyles: { ...styles.BODY_CSS }, - headerCssStyles: { ...styles.BODY_CSS } - } - ], - CSSStyles: { 'margin-top': '5px', 'width': WIZARD_INPUT_COMPONENT_WIDTH } - }).component(); - return authKeyTable; -}