diff --git a/extensions/resource-deployment/notebooks/azuredb/create-sqldb.ipynb b/extensions/resource-deployment/notebooks/azuredb/create-sqldb.ipynb new file mode 100644 index 0000000000..8d51d66908 --- /dev/null +++ b/extensions/resource-deployment/notebooks/azuredb/create-sqldb.ipynb @@ -0,0 +1,211 @@ +{ + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python", + "version": "3.6.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "Create Azure SQL Database\n", + "============================================\n", + "\n", + "Steps of this procedure include:\n", + "1. Set variables and set up Notebook\n", + "1. Connect to Azure account and subscription\n", + "1. Provision firewall rules to allow local access\n", + "1. Create SQL database resource" + ], + "metadata": { + "azdata_cell_guid": "6af59d69-ade7-480a-b33e-52a86fe5bfd3" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Set variables\r\n", + "These variables are set based on your inputs in the deployment wizard. You can make changes to these variables but be aware of possible validation errors caused by your changes.\r\n", + "\r\n", + "\r\n", + "\r\n", + "\r\n", + "" + ], + "metadata": { + "azdata_cell_guid": "b57c46c8-4a34-49af-9b62-aa5688a02002" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Notebook Setup " + ], + "metadata": { + "azdata_cell_guid": "19ebf0fd-7010-4cd6-8bcd-d2f63dc75cfb" + } + }, + { + "cell_type": "code", + "source": [ + "import sys, os, json, time, string, random, subprocess\r\n", + "def run_command(command, json_decode = True, printOutput = True):\n", + " print(command)\n", + " process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n", + " output, error = process.communicate()\n", + " if process.returncode != 0: \n", + " print(\"Process failed %d \\n%s\" % (process.returncode, error.decode(\"utf-8\")))\n", + " raise Exception()\n", + " if output:\n", + " output = output.decode(\"utf-8\")\n", + " if printOutput:\n", + " print(output)\n", + " try:\n", + " return json.loads(output)\n", + " except:\n", + " return output\r\n", + ], + "metadata": { + "azdata_cell_guid": "c320ffe2-c488-4bd8-9886-c7deeae02996", + "tags": [] + }, + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Connecting to your Azure account\r\n", + "" + ], + "metadata": { + "azdata_cell_guid": "e34334a7-0d55-4c18-8c0a-1c4a673629cd" + } + }, + { + "cell_type": "code", + "source": [ + "subscriptions = run_command('az account list', printOutput = False)\r\n", + "if azure_sqldb_subscription not in (subscription[\"id\"] for subscription in subscriptions):\r\n", + " run_command('az login')" + ], + "metadata": { + "azdata_cell_guid": "96800b54-48a8-463b-886c-3d0e96f29765" + }, + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Setting your Azure subscription\r\n", + "" + ], + "metadata": { + "azdata_cell_guid": "ed6b781d-ce7e-4b51-a7ec-1eeeb2032c73" + } + }, + { + "cell_type": "code", + "source": [ + "run_command(\r\n", + " 'az account set '\r\n", + " '--subscription {0}'\r\n", + " .format(\r\n", + " azure_sqldb_subscription));" + ], + "metadata": { + "azdata_cell_guid": "17b57956-98cf-44de-9ab5-348469ddabf4" + }, + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Create a server firewall rule\r\n", + "\r\n", + "This firewall rule will allow you to access your server and database within IP range immediately after it is created." + ], + "metadata": { + "azdata_cell_guid": "ba895abf-3176-48b5-9e49-a060b3f74370" + } + }, + { + "cell_type": "code", + "source": [ + "create_firewall_rule_result = run_command(\r\n", + " 'az sql server firewall-rule create '\r\n", + " '--start-ip-address {0} '\r\n", + " '--end-ip-address {1} '\r\n", + " '--server {2} '\r\n", + " '--name {3} '\r\n", + " '--resource-group {4} '\r\n", + " .format(\r\n", + " azure_sqldb_ip_start, \r\n", + " azure_sqldb_ip_end, \r\n", + " azure_sqldb_server_name, \r\n", + " azure_sqldb_firewall_name, \r\n", + " azure_sqldb_resource_group_name));" + ], + "metadata": { + "azdata_cell_guid": "ceae5670-292f-4c45-9c10-4ac85baf2d07" + }, + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Create Azure SQL Database\r\n", + "\r\n", + "The database will be created based on all the settings previously specified. [Learn more](https://docs.microsoft.com/en-us/cli/azure/sql/db?view=azure-cli-latest#az_sql_db_create) about additonal options for creating the database." + ], + "metadata": { + "azdata_cell_guid": "b460ca8f-65a7-4d6c-94b7-6d7dd9655fad" + } + }, + { + "cell_type": "code", + "source": [ + "create_database_result = run_command(\r\n", + " 'az sql db create '\r\n", + " '--server {0} '\r\n", + " '--name {1} '\r\n", + " '--edition GeneralPurpose '\r\n", + " '--compute-model Serverless '\r\n", + " '--family Gen5 '\r\n", + " '--resource-group {2} '\r\n", + " '--min-capacity 0.5 '\r\n", + " '--max-size 32GB '\r\n", + " '--capacity 1 '\r\n", + " '--collation {3} '\r\n", + " .format(\r\n", + " azure_sqldb_server_name, \r\n", + " azure_sqldb_database_name, \r\n", + " azure_sqldb_resource_group_name, \r\n", + " azure_sqldb_collation));" + ], + "metadata": { + "azdata_cell_guid": "dc3b2f6f-83ac-4a4d-9d81-2f534e90913e" + }, + "outputs": [], + "execution_count": null + } + ] +} diff --git a/extensions/resource-deployment/package.json b/extensions/resource-deployment/package.json index 9e42a78c91..5450b06590 100644 --- a/extensions/resource-deployment/package.json +++ b/extensions/resource-deployment/package.json @@ -405,7 +405,20 @@ "dark": "./images/azure-sql-db.svg" }, "tags": ["SQL Server", "Cloud"], - "okButtonText": "%azure-sqldb-ok-button-text%", + "okButtonText": [ + { + "value": "%azure-sqldb-notebook-ok-button-text%", + "when": "resource-type=single-database" + }, + { + "value": "%azure-sqldb-portal-ok-button-text%", + "when": "resource-type=elastic-pool" + }, + { + "value": "%azure-sqldb-portal-ok-button-text%", + "when": "resource-type=database-server" + } + ], "options": [ { "name": "resource-type", @@ -428,8 +441,14 @@ ], "providers": [ { - "webPageUrl": "https://portal.azure.com/#create/Microsoft.SQLDatabase", - "requiredTools": [], + "azureSQLDBWizard":{ + "notebook": "./notebooks/azuredb/create-sqldb.ipynb" + }, + "requiredTools": [ + { + "name": "azure-cli" + } + ], "when": "resource-type=single-database" }, { diff --git a/extensions/resource-deployment/package.nls.json b/extensions/resource-deployment/package.nls.json index 63d6069bd9..a56184e1bf 100644 --- a/extensions/resource-deployment/package.nls.json +++ b/extensions/resource-deployment/package.nls.json @@ -70,9 +70,10 @@ "azure-sqlvm-password-label": "Password", "azure-sqlvm-password-confirm-label": "Confirm password", "azure-sqlvm-vm-summary-page-label": "Summary", - "azure-sqldb-display-name": "Azure SQL Database", - "azure-sqldb-description": "Select a resource type and then you will be taken to the Azure portal to create the Azure resource.", - "azure-sqldb-ok-button-text": "Create in Azure portal", + "azure-sqldb-display-name": "SQL Database on Azure Server", + "azure-sqldb-description": "Create SQL Databases on Azure. Best for new applications or existing on-premises applications.", + "azure-sqldb-portal-ok-button-text": "Create in Azure portal", + "azure-sqldb-notebook-ok-button-text": "Script to Notebook", "resource-type-display-name": "Resource Type", "sql-azure-single-database-display-name": "Single Database", "sql-azure-elastic-pool-display-name": "Elastic Pool", diff --git a/extensions/resource-deployment/src/interfaces.ts b/extensions/resource-deployment/src/interfaces.ts index f69a7832c4..c0940fcee5 100644 --- a/extensions/resource-deployment/src/interfaces.ts +++ b/extensions/resource-deployment/src/interfaces.ts @@ -19,7 +19,8 @@ export interface ResourceType { providers: DeploymentProvider[]; agreement?: AgreementInfo; displayIndex?: number; - okButtonText?: string; + okButtonText?: OkButtonTextValue[]; + getOkButtonText(selectedOptions: { option: string, value: string }[]): string | undefined; getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined; tags?: string[]; } @@ -40,6 +41,11 @@ export interface ResourceTypeOptionValue { displayName: string; } +export interface OkButtonTextValue { + value: string; + when: string; +} + export interface DialogDeploymentProvider extends DeploymentProviderBase { dialog: DialogInfo; } @@ -72,6 +78,10 @@ export interface AzureSQLVMDeploymentProvider extends DeploymentProviderBase { azureSQLVMWizard: AzureSQLVMWizardInfo; } +export interface AzureSQLDBDeploymentProvider extends DeploymentProviderBase { + azureSQLDBWizard: AzureSQLDBWizardInfo; +} + export function instanceOfDialogDeploymentProvider(obj: any): obj is DialogDeploymentProvider { return obj && 'dialog' in obj; } @@ -104,12 +114,16 @@ export function instanceOfAzureSQLVMDeploymentProvider(obj: any): obj is AzureSQ return obj && 'azureSQLVMWizard' in obj; } +export function instanceOfAzureSQLDBDeploymentProvider(obj: any): obj is AzureSQLDBDeploymentProvider { + return obj && 'azureSQLDBWizard' in obj; +} + export interface DeploymentProviderBase { requiredTools: ToolRequirementInfo[]; when: string; } -export type DeploymentProvider = DialogDeploymentProvider | BdcWizardDeploymentProvider | NotebookWizardDeploymentProvider | NotebookDeploymentProvider | WebPageDeploymentProvider | DownloadDeploymentProvider | CommandDeploymentProvider | AzureSQLVMDeploymentProvider; +export type DeploymentProvider = DialogDeploymentProvider | BdcWizardDeploymentProvider | NotebookWizardDeploymentProvider | NotebookDeploymentProvider | WebPageDeploymentProvider | DownloadDeploymentProvider | CommandDeploymentProvider | AzureSQLVMDeploymentProvider | AzureSQLDBDeploymentProvider; export interface BdcWizardInfo { notebook: string | NotebookPathInfo; @@ -182,6 +196,10 @@ export interface AzureSQLVMWizardInfo { notebook: string | NotebookPathInfo; } +export interface AzureSQLDBWizardInfo { + notebook: string | NotebookPathInfo; +} + export type DialogInfo = NotebookBasedDialogInfo | CommandBasedDialogInfo; export function instanceOfNotebookBasedDialogInfo(obj: any): obj is NotebookBasedDialogInfo { diff --git a/extensions/resource-deployment/src/services/resourceTypeService.ts b/extensions/resource-deployment/src/services/resourceTypeService.ts index 0230f9a1ae..f3e7c60b3e 100644 --- a/extensions/resource-deployment/src/services/resourceTypeService.ts +++ b/extensions/resource-deployment/src/services/resourceTypeService.ts @@ -10,8 +10,9 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { DeploymentProvider, instanceOfAzureSQLVMDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookDeploymentProvider, instanceOfNotebookWizardDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfWizardDeploymentProvider, NotebookInfo, NotebookPathInfo, ResourceType, ResourceTypeOption } from '../interfaces'; +import { DeploymentProvider, instanceOfAzureSQLVMDeploymentProvider, instanceOfAzureSQLDBDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookDeploymentProvider, instanceOfNotebookWizardDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfWizardDeploymentProvider, NotebookInfo, NotebookPathInfo, ResourceType, ResourceTypeOption } from '../interfaces'; import { DeployAzureSQLVMWizard } from '../ui/deployAzureSQLVMWizard/deployAzureSQLVMWizard'; +import { DeployAzureSQLDBWizard } from '../ui/deployAzureSQLDBWizard/deployAzureSQLDBWizard'; import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard'; import { DeploymentInputDialog } from '../ui/deploymentInputDialog'; import { NotebookWizard } from '../ui/notebookWizard/notebookWizard'; @@ -20,6 +21,7 @@ import { KubeService } from './kubeService'; import { INotebookService } from './notebookService'; import { IPlatformService } from './platformService'; import { IToolsService } from './toolsService'; +import * as loc from './../localizedConstants'; const localize = nls.loadMessageBundle(); @@ -46,6 +48,7 @@ export class ResourceTypeService implements IResourceTypeService { extensionResourceTypes.forEach((resourceType: ResourceType) => { this.updatePathProperties(resourceType, extension.extensionPath); resourceType.getProvider = (selectedOptions) => { return this.getProvider(resourceType, selectedOptions); }; + resourceType.getOkButtonText = (selectedOptions) => { return this.getOkButtonText(resourceType, selectedOptions); }; this._resourceTypes.push(resourceType); }); } @@ -78,6 +81,9 @@ export class ResourceTypeService implements IResourceTypeService { else if ('azureSQLVMWizard' in provider) { this.updateNotebookPath(provider.azureSQLVMWizard, extensionPath); } + else if ('azureSQLDBWizard' in provider) { + this.updateNotebookPath(provider.azureSQLDBWizard, extensionPath); + } }); } @@ -187,7 +193,8 @@ export class ResourceTypeService implements IResourceTypeService { && !instanceOfDownloadDeploymentProvider(provider) && !instanceOfWebPageDeploymentProvider(provider) && !instanceOfCommandDeploymentProvider(provider) - && !instanceOfAzureSQLVMDeploymentProvider(provider)) { + && !instanceOfAzureSQLVMDeploymentProvider(provider) + && !instanceOfAzureSQLDBDeploymentProvider(provider)) { errorMessages.push(`No deployment method defined for the provider, ${providerPositionInfo}`); } @@ -245,6 +252,21 @@ export class ResourceTypeService implements IResourceTypeService { return undefined; } + /** + * Get the ok button text based on the selected options + */ + private getOkButtonText(resourceType: ResourceType, selectedOptions: { option: string, value: string }[]): string | undefined { + if (resourceType.okButtonText && selectedOptions.length === 1) { + const optionGiven = `${selectedOptions[0].option}=${selectedOptions[0].value}`; + for (const possibleOption of resourceType.okButtonText) { + if (possibleOption.when === optionGiven || possibleOption.when === undefined || possibleOption.when.toString().toLowerCase() === 'true') { + return possibleOption.value; + } + } + } + return loc.select; + } + public startDeployment(provider: DeploymentProvider): void { const self = this; if (instanceOfWizardDeploymentProvider(provider)) { @@ -283,6 +305,9 @@ export class ResourceTypeService implements IResourceTypeService { } else if (instanceOfAzureSQLVMDeploymentProvider(provider)) { const wizard = new DeployAzureSQLVMWizard(provider.azureSQLVMWizard, this.notebookService, this.toolsService); wizard.open(); + } else if (instanceOfAzureSQLDBDeploymentProvider(provider)) { + const wizard = new DeployAzureSQLDBWizard(provider.azureSQLDBWizard, this.notebookService, this.toolsService); + wizard.open(); } } diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/constants.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/constants.ts new file mode 100644 index 0000000000..02bcf1b8e5 --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/constants.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export const standardWidth: string = '480px'; + +// Deploy Azure SQL DB wizard constants +export const WizardTitle = localize('deployAzureSQLDB.NewSQLDBTitle', "Deploy Azure SQL DB"); +export const WizardDoneButtonLabel = localize('deployAzureSQLDB.ScriptToNotebook', "Script to Notebook"); +export const MissingRequiredInformationErrorMessage = localize('deployAzureSQLDB.MissingRequiredInfoError', "Please fill out the required fields marked with red asterisks."); + +// Azure settings page constants +export const AzureSettingsPageTitle = localize('deployAzureSQLDB.AzureSettingsPageTitle', "Azure SQL Database - Azure Account Settings"); +export const AzureSettingsSummaryPageTitle = localize('deployAzureSQLDB.AzureSettingsSummaryPageTitle', "Azure Account Settings"); +export const AzureAccountDropdownLabel = localize('deployAzureSQLDB.AzureAccountDropdownLabel', "Azure Account"); +export const AzureAccountSubscriptionDropdownLabel = localize('deployAzureSQLDB.AzureSubscriptionDropdownLabel', "Subscription"); +export const AzureAccountDatabaseServersDropdownLabel = localize('deployAzureSQLDB.AzureDatabaseServersDropdownLabel', "Server"); +export const AzureAccountResourceGroupDropdownLabel = localize('deployAzureSQLDB.ResourceGroup', "Resource Group"); +//@todo alma1 9/8/20 Region label used for upcoming server creation feature. +//export const AzureAccountRegionDropdownLabel = localize('deployAzureSQLDB.AzureRegionDropdownLabel', "Region (for Public IP Address)"); + +//Azure settings Database hardware properties. //@todo alma1 9/8/20 labels used for upcoming database hardware creation feature. +// export const DatabaseHardwareInfoLabel = localize('deployAzureSQLDB.DatabaseHardwareInfo', "SQLDB Hardware Settings"); +// export const DatabaseManagedInstanceDropdownLabel = localize('deployAzureSQLDB.DatabaseManagedInstanceDropdownLabel', "SQLDB Version"); +// export const DatabaseSupportedEditionsDropdownLabel = localize('deployAzureSQLDB.DatabaseSupportedEditionsDropdownLabel', "Edition Type"); +// export const DatabaseSupportedFamilyDropdownLabel = localize('deployAzureSQLDB.DatabaseSupportedFamilyDropdownLabel', "Family Type"); +// export const DatabaseVCoreNumberDropdownLabel = localize('deployAzureSQLDB.DatabaseVCoreNumberDropdownLabel', "Number of Vcores"); +// export const DatabaseMaxMemoryTextLabel = localize('deployAzureSQLDB.DatabaseMaxMemoryTextLabel', "Maximum Data Storage Capacity in GB, can go up to 1TB (1024 GB)."); +// export const DatabaseMaxMemorySummaryTextLabel = localize('deployAzureSQLDB.DatabaseMaxMemorySummaryTextLabel', "Maximum Data Storage Capacity in GB"); + +// Database settings page constants +export const DatabaseSettingsPageTitle = localize('deployAzureSQLDB.DatabaseSettingsPageTitle', "Database settings"); +export const FirewallRuleNameLabel = localize('deployAzureSQLDB.FirewallRuleNameLabel', "Firewall rule name"); +export const DatabaseNameLabel = localize('deployAzureSQLDB.DatabaseNameLabel', "SQL database name"); +export const CollationNameLabel = localize('deployAzureSQLDB.CollationNameLabel', "Database collation"); +export const CollationNameSummaryLabel = localize('deployAzureSQLDB.CollationNameSummaryLabel', "Collation for database"); +export const IpAddressInfoLabel = localize('deployAzureSQLDB.IpAddressInfoLabel', "Enter IP Addresses in IPv4 format."); +export const StartIpAddressLabel = localize('deployAzureSQLDB.StartIpAddressLabel', "Min IP Address in firewall Ip Range"); +export const EndIpAddressLabel = localize('deployAzureSQLDB.EndIpAddressLabel', "Max IP Address in firewall IP Range"); +export const StartIpAddressShortLabel = localize('deployAzureSQLDB.StartIpAddressShortLabel', "Min IP Address"); +export const EndIpAddressShortLabel = localize('deployAzureSQLDB.EndIpAddressShortLabel', "Max IP Address"); diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizard.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizard.ts new file mode 100644 index 0000000000..d3c23d7a9a --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizard.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { INotebookService } from '../../services/notebookService'; +import { IToolsService } from '../../services/toolsService'; +import { WizardBase } from '../wizardBase'; +import { WizardPageBase } from '../wizardPageBase'; +import { DeployAzureSQLDBWizardModel } from './deployAzureSQLDBWizardModel'; +import { AzureSQLDBWizardInfo } from '../../interfaces'; +import { AzureSettingsPage } from './pages/azureSettingsPage'; +import { DatabaseSettingsPage } from './pages/databaseSettingsPage'; +import axios, { AxiosRequestConfig } from 'axios'; +import { AzureSQLDBSummaryPage } from './pages/summaryPage'; +import { EOL } from 'os'; + +export class DeployAzureSQLDBWizard extends WizardBase, DeployAzureSQLDBWizardModel> { + + constructor(private wizardInfo: AzureSQLDBWizardInfo, private _notebookService: INotebookService, private _toolsService: IToolsService) { + super( + constants.WizardTitle, + 'DeployAzureSqlDBWizard', + new DeployAzureSQLDBWizardModel(), + _toolsService + ); + } + + private cache: Map = new Map(); + + protected initialize(): void { + this.setPages(this.getPages()); + this.wizardObject.generateScriptButton.hidden = true; + this.wizardObject.doneButton.label = constants.WizardDoneButtonLabel; + } + + + public get notebookService(): INotebookService { + return this._notebookService; + } + + public get toolService(): IToolsService { + return this._toolsService; + } + + protected async onOk(): Promise { + await this.scriptToNotebook(); + } + + protected onCancel(): void { + } + + private getPages(): WizardPageBase[] { + const pages: WizardPageBase[] = []; + pages.push(new AzureSettingsPage(this)); + pages.push(new DatabaseSettingsPage(this)); + pages.push(new AzureSQLDBSummaryPage(this)); + return pages; + } + + private async scriptToNotebook(): Promise { + const variableValueStatements = this.model.getCodeCellContentForNotebook(); + const insertionPosition = 2; // Cell number 2 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook. + try { + await this.notebookService.openNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition); + } catch (error) { + vscode.window.showErrorMessage(error); + } + } + + + public async getRequest(url: string, useCache = false): Promise { + if (useCache) { + if (this.cache.has(url)) { + return this.cache.get(url); + } + } + let token = this.model.securityToken.token; + const config: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + validateStatus: () => true // Never throw + }; + const response = await axios.get(url, config); + if (response.status !== 200) { + let errorMessage: string[] = []; + errorMessage.push(response.status.toString()); + errorMessage.push(response.statusText); + if (response.data && response.data.error) { + errorMessage.push(`${response.data.error.code} : ${response.data.error.message}`); + } + vscode.window.showErrorMessage(errorMessage.join(EOL)); + } + if (useCache) { + this.cache.set(url, response); + } + return response; + } + + public createFormRowComponent(view: azdata.ModelView, title: string, description: string, component: azdata.Component, required: boolean): azdata.FlexContainer { + + component.updateProperties({ + required: required, + width: '480px' + }); + + const labelText = view.modelBuilder.text() + .withProperties( + { + value: title, + width: '250px', + description: description, + requiredIndicator: required, + }) + .component(); + + labelText.updateCssStyles({ + 'font-weight': '400', + 'font-size': '13px', + }); + + const flexContainer = view.modelBuilder.flexContainer() + .withLayout( + { + flexFlow: 'row', + alignItems: 'center', + }) + .withItems( + [labelText, component], + { + CSSStyles: { 'margin-right': '5px' } + }) + .component(); + return flexContainer; + } + + public changeComponentDisplay(component: azdata.Component, display: ('none' | 'block')) { + component.updateProperties({ + required: display === 'block' + }); + component.updateCssStyles({ + display: display + }); + } + + public changeRowDisplay(container: azdata.FlexContainer, display: ('none' | 'block')) { + container.items.map((component) => { + component.updateProperties({ + required: (display === 'block'), + }); + component.updateCssStyles({ + display: display, + }); + }); + } + + public addDropdownValues(component: azdata.DropDownComponent, values: azdata.CategoryValue[], width?: number) { + component.updateProperties({ + values: values, + width: '480px' + }); + } + + public showErrorMessage(message: string) { + this.wizardObject.message = { + text: message, + level: azdata.window.MessageLevel.Error + }; + } +} diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizardModel.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizardModel.ts new file mode 100644 index 0000000000..58bc590c9d --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/deployAzureSQLDBWizardModel.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EOL } from 'os'; +import * as azdata from 'azdata'; +import { Model } from '../model'; + +export class DeployAzureSQLDBWizardModel extends Model { + public azureAccount!: azdata.Account; + public securityToken!: any; + public azureSubscription!: string; + public azureSubscriptionDisplayName!: string; + public azureResouceGroup!: string; + public azureServerName!: string; + public azureRegion!: string; + + // public databaseEdition!: string; //@todo alma1 10/7/2020 used for upcoming database hardware creation feature + // public databaseFamily!: string; + // public vCoreNumber!: number; + // public storageInGB!: string; + + public databaseName!: string; + //public newServer!: 'True' | 'False'; //@todo alma1 9/8/2020 used for upcoming server creation feature. + public startIpAddress!: string; + public endIpAddress!: string; + public firewallRuleName!: string; + public databaseCollation!: string; + + + constructor() { + super(); + } + + public getCodeCellContentForNotebook(): string[] { + const statements: string[] = []; + + statements.push(`azure_sqldb_subscription = '${this.azureSubscription}'`); + statements.push(`azure_sqldb_resource_group_name = '${this.azureResouceGroup}'`); + statements.push(`azure_sqldb_server_name = '${this.azureServerName}'`); + //statements.push(`azure_sqldb_database_edition = '${this.databaseEdition}'`); //@todo alma1 10/7/2020 used for upcoming datbase hardware creation feature. + statements.push(`azure_sqldb_database_name = '${this.databaseName}'`); + //statements.push(`azure_sqldb_location = '${this.azureRegion}'`); //@todo alma1 9/10/2020 used for upcoming server creation feature. + statements.push(`azure_sqldb_ip_start = '${this.startIpAddress}'`); + statements.push(`azure_sqldb_ip_end = '${this.endIpAddress}'`); + statements.push(`azure_sqldb_firewall_name = '${this.firewallRuleName}'`); + statements.push(`azure_sqldb_collation = '${this.databaseCollation}'`); + // statements.push(`azure_sqldb_family = '${this.databaseFamily}'`); //@todo alma1 10/7/2020 used for upcoming datbase hardware creation feature. + // statements.push(`azure_sqldb_vcore = '${this.vCoreNumber}'`); + // statements.push(`azure_sqldb_maxmemory = '${this.storageInGB}'`); + //statements.push(`azure_sqldb_new_server = '${this.newServer}'`); //@todo alma1 9/8/2020 used for upcoming server creation feature. + + return statements.map(line => line.concat(EOL)); + } +} diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/azureSettingsPage.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/azureSettingsPage.ts new file mode 100644 index 0000000000..784106d5c3 --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/azureSettingsPage.ts @@ -0,0 +1,765 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EOL } from 'os'; +import * as constants from '../constants'; +import { DeployAzureSQLDBWizard } from '../deployAzureSQLDBWizard'; +import { apiService } from '../../../services/apiService'; +import { azureResource } from 'azureResource'; +import * as vscode from 'vscode'; +import { BasePage } from './basePage'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class AzureSettingsPage extends BasePage { + // <- means depends on + //dropdown for azure accounts + private _azureAccountsDropdown!: azdata.DropDownComponent; + private signInButton!: azdata.ButtonComponent; + private refreshButton!: azdata.ButtonComponent; + + private buttonFlexContainer!: azdata.FlexContainer; + + //dropdown for subscription accounts <- azure account dropdown + private _azureSubscriptionsDropdown!: azdata.DropDownComponent; + + //dropdown for resource groups <- subscription dropdown //@todo alma1 9/9/2020 Used for upcoming server creation feature. + // private _resourceGroupDropdown!: azdata.DropDownComponent; + + //dropdown for SQL servers <- subscription dropdown + private _serverGroupDropdown!: azdata.DropDownComponent; + + // //dropdown for azure regions <- subscription dropdown //@todo alma1 9/8/2020 Region dropdown used for upcoming server creation feature. + // private _azureRegionsDropdown!: azdata.DropDownComponent; + + // //information text about hardware settings. //@todo alma1 9/8/2020 components below are used for upcoming database hardware creation feature. + // private _dbHardwareInfoText!: azdata.TextComponent; + + // //dropdown for Managed Instance Versions <- server dropdown. + // private _dbManagedInstanceDropdown!: azdata.DropDownComponent; + + // //dropdown for Supported Editions <- Managed Instance dropdown. + // private _dbSupportedEditionsDropdown!: azdata.DropDownComponent; + + // //dropdown for Supported Family <- Supported Editions dropdown. + // private _dbSupportedFamilyDropdown!: azdata.DropDownComponent; + + // //dropdown for VCore <= Supported Family dropdown. + // private _dbVCoreDropdown!: azdata.DropDownComponent; + + + // //input box for maximum memory size, supports between 1 and 1024 GB (1 TB) + // private _dbMemoryTextBox!: azdata.InputBoxComponent; + + private _form!: azdata.FormContainer; + + private _accountsMap!: Map; + private _subscriptionsMap!: Map; + constructor(wizard: DeployAzureSQLDBWizard) { + super( + constants.AzureSettingsPageTitle, + '', + wizard + ); + this._accountsMap = new Map(); + this._subscriptionsMap = new Map(); + } + + public async initialize() { + this.pageObject.registerContent(async (view: azdata.ModelView) => { + + await Promise.all([ + this.createAzureAccountsDropdown(view), + this.createAzureSubscriptionsDropdown(view), + //this.createResourceDropdown(view), //@todo alma1 9/8/2020 used for upcoming server creation feature. + this.createServerDropdown(view), + //this.createAzureRegionsDropdown(view) //@todo alma1 9/8/2020 used for upcoming server creation feature. + // this.createDatabaseHardwareSettingsText(view), //@todo alma1 9/8/2020 used for upcoming database hardware creation feature. + // this.createManagedInstanceDropdown(view), + // this.createSupportedEditionsDropdown(view), + // this.createSupportedFamilyDropdown(view), + // this.createVCoreDropdown(view), + // this.createMaxMemoryText(view), + ]); + this.populateAzureAccountsDropdown(); + + this._form = view.modelBuilder.formContainer() + .withFormItems( + [ + { + component: this.wizard.createFormRowComponent(view, constants.AzureAccountDropdownLabel, '', this._azureAccountsDropdown, true) + }, + { + component: this.buttonFlexContainer + }, + { + component: this.wizard.createFormRowComponent(view, constants.AzureAccountSubscriptionDropdownLabel, '', this._azureSubscriptionsDropdown, true) + }, + // { //@todo alma1 9/9/2020 Used for upcoming server creation feature. + // component: this.wizard.createFormRowComponent(view, constants.AzureAccountResourceGroupDropdownLabel, '', this._resourceGroupDropdown, true) + // }, + { + component: this.wizard.createFormRowComponent(view, constants.AzureAccountDatabaseServersDropdownLabel, '', this._serverGroupDropdown, true) + }, + // { //@todo alma1 9/8/2020 Used for upcoming server creation feature. + // component: this.wizard.createFormRowComponent(view, constants.AzureAccountRegionDropdownLabel, '', this._azureRegionsDropdown, true) + // } + // { //@todo alma1 9/8/2020 Used for upcoming database hardware creation feature. + // component: this._dbHardwareInfoText + // }, + // { + // component: this.wizard.createFormRowComponent(view, constants.DatabaseManagedInstanceDropdownLabel, '', this._dbManagedInstanceDropdown, true) + // }, + // { + // component: this.wizard.createFormRowComponent(view, constants.DatabaseSupportedEditionsDropdownLabel, '', this._dbSupportedEditionsDropdown, true) + // }, + // { + // component: this.wizard.createFormRowComponent(view, constants.DatabaseSupportedFamilyDropdownLabel, '', this._dbSupportedFamilyDropdown, true) + // }, + // { + // component: this.wizard.createFormRowComponent(view, constants.DatabaseVCoreNumberDropdownLabel, '', this._dbVCoreDropdown, true) + // }, + // { + // component: this.wizard.createFormRowComponent(view, constants.DatabaseMaxMemoryTextLabel, '', this._dbMemoryTextBox, true) + // } + ], + { + horizontal: false, + componentWidth: '100%' + }) + .withLayout({ width: '100%' }) + .component(); + return view.initializeModel(this._form); + }); + } + + public async onEnter(): Promise { + this.wizard.wizardObject.registerNavigationValidator(async (pcInfo) => { + if (pcInfo.newPage < pcInfo.lastPage) { + return true; + } + let errorMessage = await this.validate(); + + if (errorMessage !== '') { + return false; + } + return true; + }); + } + + public async onLeave(): Promise { + this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { + return true; + }); + } + + private async createAzureAccountsDropdown(view: azdata.ModelView) { + + this._azureAccountsDropdown = view.modelBuilder.dropDown().withProperties({}).component(); + + this._azureAccountsDropdown.onValueChanged(async (value) => { + this.wizard.model.azureAccount = this._accountsMap.get(value.selected)!; + this.populateAzureSubscriptionsDropdown(); + }); + + this.signInButton = view.modelBuilder.button().withProperties({ + label: 'Sign In', + width: '100px' + }).component(); + this.refreshButton = view.modelBuilder.button().withProperties({ + label: 'Refresh', + width: '100px' + }).component(); + + this.signInButton.onDidClick(async (event) => { + await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount'); + await this.populateAzureAccountsDropdown(); + }); + this.refreshButton.onDidClick(async (event) => { + await this.populateAzureAccountsDropdown(); + }); + this.buttonFlexContainer = view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'row' + }).withItems([this.signInButton, this.refreshButton], { CSSStyles: { 'margin-right': '5px', } }).component(); + + } + + private async populateAzureAccountsDropdown() { + this._azureAccountsDropdown.loading = true; + let accounts = await azdata.accounts.getAllAccounts(); + + if (accounts.length === 0) { + this.wizard.showErrorMessage(localize('deployAzureSQLDB.azureSignInError', "Sign in to an Azure account first")); + return; + } else { + this.wizard.showErrorMessage(''); + } + + this.wizard.addDropdownValues( + this._azureAccountsDropdown, + accounts.map((account): azdata.CategoryValue => { + let accountCategoryValue = { + displayName: account.displayInfo.displayName, + name: account.displayInfo.displayName + }; + this._accountsMap.set(accountCategoryValue.displayName, account); + return accountCategoryValue; + }), + ); + + this.wizard.model.azureAccount = accounts[0]; + this._azureAccountsDropdown.loading = false; + + await this.populateAzureSubscriptionsDropdown(); + } + + private async createAzureSubscriptionsDropdown(view: azdata.ModelView) { + this._azureSubscriptionsDropdown = view.modelBuilder.dropDown().component(); + + this._azureSubscriptionsDropdown.onValueChanged(async (value) => { + + let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue; + this.wizard.model.azureSubscription = currentSubscriptionValue.name; + this.wizard.model.azureSubscriptionDisplayName = currentSubscriptionValue.displayName; + + this.wizard.model.securityToken = await azdata.accounts.getAccountSecurityToken( + this.wizard.model.azureAccount, + this._subscriptionsMap.get(currentSubscriptionValue.name)?.tenant!, + azdata.AzureResource.ResourceManagement + ); + + await this.populateServerGroupDropdown(); + //@todo alma1 9/8/2020 used for upcoming server creation feature. + //this.populateResourceGroupDropdown(); + //this.populateAzureRegionsDropdown(); + }); + } + + private async populateAzureSubscriptionsDropdown() { + this._azureSubscriptionsDropdown.loading = true; + let subService = await apiService.azurecoreApi; + let currentAccountDropdownValue = (this._azureAccountsDropdown.value as azdata.CategoryValue); + if (currentAccountDropdownValue === undefined) { + this._azureSubscriptionsDropdown.loading = false; + await this.populateServerGroupDropdown(); + //@todo alma1 9/8/2020 used for upcoming server creation feature. + //await this.populateResourceGroupDropdown(); + //await this.populateAzureRegionsDropdown(); + return; + } + let currentAccount = this._accountsMap.get(currentAccountDropdownValue.name); + let subscriptions = (await subService.getSubscriptions(currentAccount, true)).subscriptions; + if (subscriptions === undefined || subscriptions.length === 0) { + this._azureSubscriptionsDropdown.updateProperties({ + values: [] + }); + this._azureSubscriptionsDropdown.loading = false; + await this.populateServerGroupDropdown(); + //@todo alma1 9/8/2020 used for upcoming server creation feature. + //await this.populateResourceGroupDropdown(); + //await this.populateAzureRegionsDropdown(); + return; + } + subscriptions.sort((a: any, b: any) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase())); + + this.wizard.addDropdownValues( + this._azureSubscriptionsDropdown, + subscriptions.map((subscription: any): azdata.CategoryValue => { + let subscriptionCategoryValue = { + displayName: subscription.name + ' - ' + subscription.id, + name: subscription.id + }; + this._subscriptionsMap.set(subscriptionCategoryValue.name, subscription); + return subscriptionCategoryValue; + }) + ); + + this.wizard.model.azureSubscription = (this._azureSubscriptionsDropdown.value as azdata.CategoryValue).name; + this.wizard.model.azureSubscriptionDisplayName = (this._azureSubscriptionsDropdown.value as azdata.CategoryValue).displayName; + + this.wizard.model.securityToken = await azdata.accounts.getAccountSecurityToken( + this.wizard.model.azureAccount, + this._subscriptionsMap.get((this._azureSubscriptionsDropdown.value as azdata.CategoryValue).name)?.tenant!, + azdata.AzureResource.ResourceManagement + ); + this._azureSubscriptionsDropdown.loading = false; + await this.populateServerGroupDropdown(); + //@todo alma1 9/8/2020 used for upcoming server creation feature. + //await this.populateResourceGroupDropdown(); + //await this.populateAzureRegionsDropdown(); + } + + private async createServerDropdown(view: azdata.ModelView) { + this._serverGroupDropdown = view.modelBuilder.dropDown().withProperties({ + required: true, + }).component(); + this._serverGroupDropdown.onValueChanged(async (value) => { + if (value.selected === ((this._serverGroupDropdown.value as azdata.CategoryValue).displayName)) { + this.wizard.model.azureServerName = value.selected; + this.wizard.model.azureResouceGroup = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), ''); + this.wizard.model.azureRegion = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/location/'), ''); + //this.populateManagedInstanceDropdown(); //@todo alma1 9/8/2020 functions below are used for upcoming database hardware creation feature. + } + }); + } + + private async populateServerGroupDropdown() { + this._serverGroupDropdown.loading = true; + let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue; + if (currentSubscriptionValue === undefined || currentSubscriptionValue.displayName === '') { + this._serverGroupDropdown.updateProperties({ + values: [] + }); + this._serverGroupDropdown.loading = false; + // await this.populateManagedInstanceDropdown(); //@todo alma1 9/8/2020 functions below are used for upcoming database hardware creation feature. + return; + } + let url = `https://management.azure.com/subscriptions/${this.wizard.model.azureSubscription}/providers/Microsoft.Sql/servers?api-version=2019-06-01-preview`; + let response = await this.wizard.getRequest(url); + if (response.data.value.length === 0) { + this._serverGroupDropdown.updateProperties({ + values: [ + { + displayName: localize('deployAzureSQLDB.NoServerLabel', "No servers found"), + name: '' + } + ], + }); + this._serverGroupDropdown.loading = false; + // await this.populateManagedInstanceDropdown(); //@todo alma1 9/8/2020 functions below are used for upcoming database hardware creation feature. + return; + } else { + response.data.value.sort((a: azdata.CategoryValue, b: azdata.CategoryValue) => (a!.name > b!.name) ? 1 : -1); + } + this.wizard.addDropdownValues( + this._serverGroupDropdown, + response.data.value.map((value: any) => { + return { + displayName: value.name, + // remove location from this line and others when region population is enabled again. + name: value.id + '/location/' + value.location, + }; + }) + ); + if (this._serverGroupDropdown.value) { + this.wizard.model.azureServerName = (this._serverGroupDropdown.value as azdata.CategoryValue).displayName; + this.wizard.model.azureResouceGroup = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), ''); + this.wizard.model.azureRegion = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/location/'), ''); + } + this._serverGroupDropdown.loading = false; + // await this.populateManagedInstanceDropdown(); //@todo alma1 9/8/2020 functions below are used for upcoming database hardware creation feature. + return; + } + + //@todo alma1 9/8/2020 functions below are used for upcoming server creation feature. + + // private async createResourceDropdown(view: azdata.ModelView) { + // this._resourceGroupDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true + // }).component(); + // this._resourceGroupDropdown.onValueChanged(async (value) => { + // this.wizard.model.azureResouceGroup = value.selected; + // this.populateServerGroupDropdown(); + // }); + // } + + // private async populateResourceGroupDropdown() { + // this._resourceGroupDropdown.loading = true; + // let subService = await apiService.azurecoreApi; + // let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue; + // if (currentSubscriptionValue === undefined || currentSubscriptionValue.displayName === '') { + + // this._resourceGroupDropdown.updateProperties({ + // values: [] + // }); + // this._resourceGroupDropdown.loading = false; + // await this.populateServerGroupDropdown(); + // return; + // } + // let currentSubscription = this._subscriptionsMap.get(currentSubscriptionValue.name); + // let resourceGroups = (await subService.getResourceGroups(this.wizard.model.azureAccount, currentSubscription, true)).resourceGroups; + // if (resourceGroups === undefined || resourceGroups.length === 0) { + // this._resourceGroupDropdown.loading = false; + // this._resourceGroupDropdown.updateProperties({ + // values: [] + // }); + // await this.populateServerGroupDropdown(); + // return; + // } + + // resourceGroups.sort((a: any, b: any) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase())); + // this._resourceGroupDropdown.updateProperties({ + // values: resourceGroups.map((resourceGroup: any) => { + // return { + // displayName: resourceGroup.name, + // name: resourceGroup.name + // }; + // }) + // }); + // this.wizard.model.azureResouceGroup = (this._resourceGroupDropdown.value as azdata.CategoryValue).name; + // this._resourceGroupDropdown.loading = false; + // await this.populateServerGroupDropdown(); + // } + + // private async createAzureRegionsDropdown(view: azdata.ModelView) { + // this._azureRegionsDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true + // }).component(); + + + // this._azureRegionsDropdown.onValueChanged((value) => { + // this.wizard.model.azureRegion = (this._azureRegionsDropdown.value as azdata.CategoryValue).name; + // }); + // } + + // private async populateAzureRegionsDropdown() { + // this._azureRegionsDropdown.loading = true; + + // let supportedRegions = 'eastus, eastus2, westus, centralus, northcentralus, southcentralus, northeurope, westeurope, eastasia, southeastasia, japaneast, japanwest, australiaeast, australiasoutheast, australiacentral, brazilsouth, southindia, centralindia, westindia, canadacentral, canadaeast, westus2, westcentralus, uksouth, ukwest, koreacentral, koreasouth, francecentral, southafricanorth, uaenorth, switzerlandnorth, germanywestcentral, norwayeast'; + // let supportedRegionsArray = supportedRegions.split(', '); + // let url = `https://management.azure.com/subscriptions/${this.wizard.model.azureSubscription}/locations?api-version=2020-01-01`; + // const response = await this.wizard.getRequest(url, true); + // response.data.value = response.data.value.sort((a: any, b: any) => (a.displayName > b.displayName) ? 1 : -1); + + // this.wizard.addDropdownValues( + // this._azureRegionsDropdown, + // response.data.value.filter((value: any) => { + // return supportedRegionsArray.includes(value.name); + // }).map((value: any) => { + // return { + // displayName: value.displayName, + // name: value.name + // }; + // }) + // ); + // this.wizard.model.azureRegion = (this._azureRegionsDropdown.value as azdata.CategoryValue).name; + // this._azureRegionsDropdown.loading = false; + // } + + //@todo alma1 9/8/2020 functions below are used for upcoming database hardware creation feature. + + // private createDatabaseHardwareSettingsText(view: azdata.ModelView) { + // this._dbHardwareInfoText = view.modelBuilder.text() + // .withProperties({ + // value: constants.DatabaseHardwareInfoLabel + // }).component(); + // } + + // private async createManagedInstanceDropdown(view: azdata.ModelView) { + // this._dbManagedInstanceDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true, + // }).component(); + // this._dbManagedInstanceDropdown.onValueChanged(async (value) => { + // this.populateSupportedEditionsDropdown(); + // }); + // } + + // private async populateManagedInstanceDropdown() { + // this._dbManagedInstanceDropdown.loading = true; + // let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue; + // if (!currentSubscriptionValue || currentSubscriptionValue.displayName === '') { + // this._dbManagedInstanceDropdown.updateProperties({ + // values: [] + // }); + // this._dbManagedInstanceDropdown.loading = false; + // await this.populateSupportedEditionsDropdown(); + // return; + // } + // let currentServerValue = this._serverGroupDropdown.value as azdata.CategoryValue; + + // if (currentServerValue.name === '') { + // this._dbManagedInstanceDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoServerLabel', "No servers found"), + // name: '', + // supportedEditions: undefined + // } + // ] + // }); + // this._dbManagedInstanceDropdown.loading = false; + // await this.populateSupportedEditionsDropdown(); + // return; + // } + + // let url = `https://management.azure.com/subscriptions/${this.wizard.model.azureSubscription}/providers/Microsoft.Sql/locations/${this.wizard.model.azureRegion}/capabilities?api-version=2017-10-01-preview`; + // let response = await this.wizard.getRequest(url); + + // if (response.data.supportedManagedInstanceVersions.length === 0) { + // this._dbManagedInstanceDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoHardwareConfigLabel', "No database hardware configuration found"), + // name: '', + // supportedEditions: undefined + // } + // ], + // }); + // this._dbManagedInstanceDropdown.loading = false; + // await this.populateSupportedEditionsDropdown(); + // return; + // } else { + // response.data.supportedManagedInstanceVersions.sort((a: any, b: any) => (a!.name > b!.name) ? 1 : -1); + // } + // this.wizard.addDropdownValues( + // this._dbManagedInstanceDropdown, + // response.data.supportedManagedInstanceVersions.map((value: any) => { + // return { + // displayName: value.name, + // name: value.name, + // supportedEditions: value.supportedEditions + // }; + // }) + // ); + // // if (this._serverGroupDropdown.value) { + // // this.wizard.model.azureServerName = (this._serverGroupDropdown.value as azdata.CategoryValue).displayName; + // // this.wizard.model.azureResouceGroup = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), ''); + // // this.wizard.model.azureRegion = (this._serverGroupDropdown.value as azdata.CategoryValue).name.replace(RegExp('^(.*?)/location/'), ''); + // // } + // this._dbManagedInstanceDropdown.loading = false; + // await this.populateSupportedEditionsDropdown(); + // return; + // } + + // private async createSupportedEditionsDropdown(view: azdata.ModelView) { + // this._dbSupportedEditionsDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true, + // }).component(); + // this._dbSupportedEditionsDropdown.onValueChanged(async (value) => { + // this.wizard.model.databaseEdition = value.selected; + // this.populateSupportedFamilyDropdown(); + // }); + // } + + // private async populateSupportedEditionsDropdown() { + // this._dbSupportedEditionsDropdown.loading = true; + // if (!this._dbManagedInstanceDropdown.values || this._dbManagedInstanceDropdown.values!.length === 0) { + // this._dbSupportedEditionsDropdown.updateProperties({ + // values: [] + // }); + // this._dbSupportedEditionsDropdown.loading = false; + // await this.populateSupportedFamilyDropdown(); + // return; + // } + // let currentManagedInstanceValue = this._dbManagedInstanceDropdown.value as any; + // if (!currentManagedInstanceValue.supportedEditions) { + // this._dbSupportedEditionsDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoManagedInstanceLabel', "Managed instance not selected"), + // name: '' + // } + // ] + // }); + // this._dbSupportedEditionsDropdown.loading = false; + // await this.populateSupportedFamilyDropdown(); + // return; + // } + + // if (currentManagedInstanceValue.supportedEditions.length === 0) { + // this._dbSupportedEditionsDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoSupportedEditionsLabel', "No supported editions found"), + // name: '' + // } + // ], + // }); + // this._dbSupportedEditionsDropdown.loading = false; + // await this.populateSupportedFamilyDropdown(); + // return; + // } else { + // currentManagedInstanceValue.supportedEditions.sort((a: any, b: any) => (a!.name > b!.name) ? 1 : -1); + // } + // this.wizard.addDropdownValues( + // this._dbSupportedEditionsDropdown, + // currentManagedInstanceValue.supportedEditions.map((value: any) => { + // return { + // displayName: value.name, + // name: value.supportedFamilies + // }; + // }) + // ); + // if (this._dbSupportedEditionsDropdown.value) { + // this.wizard.model.databaseEdition = (this._dbSupportedEditionsDropdown.value as azdata.CategoryValue).displayName; + // } + // this._dbSupportedEditionsDropdown.loading = false; + // await this.populateSupportedFamilyDropdown(); + // return; + // } + + // private async createSupportedFamilyDropdown(view: azdata.ModelView) { + // this._dbSupportedFamilyDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true, + // }).component(); + // this._dbSupportedFamilyDropdown.onValueChanged(async (value) => { + // this.wizard.model.databaseFamily = value.selected; + // this.populateVCoreDropdown(); + // }); + // } + + // private async populateSupportedFamilyDropdown() { + // this._dbSupportedFamilyDropdown.loading = true; + // if (!this._dbSupportedEditionsDropdown.values || this._dbSupportedEditionsDropdown.values!.length === 0) { + // this._dbSupportedFamilyDropdown.updateProperties({ + // values: [] + // }); + // this._dbSupportedFamilyDropdown.loading = false; + // await this.populateVCoreDropdown(); + // return; + // } + // let currentSupportedEditionValue = this._dbSupportedEditionsDropdown.value as any; + // if (!currentSupportedEditionValue.name) { + // this._dbSupportedFamilyDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoSupportedEditionLabel', "Supported Edition not selected"), + // name: '' + // } + // ] + // }); + // this._dbSupportedFamilyDropdown.loading = false; + // await this.populateVCoreDropdown(); + // return; + // } + + // if (currentSupportedEditionValue.name.length === 0) { + // this._dbSupportedFamilyDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoSupportedFamiliesLabel', "No database family types found."), + // name: '' + // } + // ], + // }); + // this._dbSupportedFamilyDropdown.loading = false; + // await this.populateVCoreDropdown(); + // return; + // } else { + // currentSupportedEditionValue.name.sort((a: any, b: any) => (a!.name > b!.name) ? 1 : -1); + // } + // this.wizard.addDropdownValues( + // this._dbSupportedFamilyDropdown, + // currentSupportedEditionValue.name.map((value: any) => { + // return { + // displayName: value.name, + // name: value + // }; + // }) + // ); + // if (this._dbSupportedFamilyDropdown.value) { + // this.wizard.model.databaseFamily = (this._dbSupportedFamilyDropdown.value as any).displayName; + // } + // this._dbSupportedFamilyDropdown.loading = false; + // await this.populateVCoreDropdown(); + // return; + // } + + // private async createVCoreDropdown(view: azdata.ModelView) { + // this._dbVCoreDropdown = view.modelBuilder.dropDown().withProperties({ + // required: true, + // }).component(); + // this._dbVCoreDropdown.onValueChanged(async (value) => { + // this.wizard.model.vCoreNumber = value.selected; + // }); + // } + + // private async populateVCoreDropdown() { + // this._dbVCoreDropdown.loading = true; + // if (!this._dbSupportedFamilyDropdown.values || this._dbSupportedFamilyDropdown.values!.length === 0) { + // this._dbVCoreDropdown.updateProperties({ + // values: [] + // }); + // this._dbVCoreDropdown.loading = false; + // return; + // } + // let currentSupportedFamilyValue = this._dbSupportedFamilyDropdown.value as any; + // if (!currentSupportedFamilyValue.name && !currentSupportedFamilyValue.name.supportedVcoresValues) { + // this._dbVCoreDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoSupportedFamilyLabel', "Supported Family not selected"), + // name: '' + // } + // ] + // }); + // this._dbVCoreDropdown.loading = false; + // return; + // } + + // if (currentSupportedFamilyValue.name.supportedVcoresValues === 0) { + // this._dbVCoreDropdown.updateProperties({ + // values: [ + // { + // displayName: localize('deployAzureSQLDB.NoSupportedVCoreValuesLabel', "No VCore values found."), + // name: '' + // } + // ], + // }); + // this._dbVCoreDropdown.loading = false; + // return; + // } else { + // currentSupportedFamilyValue.name.supportedVcoresValues.sort((a: any, b: any) => (a!.value > b!.value) ? 1 : -1); + // } + + // this.wizard.addDropdownValues( + // this._dbVCoreDropdown, + // currentSupportedFamilyValue.name.supportedVcoresValues.map((value: any) => { + // return { + // displayName: String(value.value), + // name: value.status + // }; + // }) + // ); + // for (let i = 0; i < this._dbVCoreDropdown.values!.length; i++) { + // let value = this._dbVCoreDropdown.values![i] as azdata.CategoryValue; + // if (value.name === 'Default') { + // this._dbVCoreDropdown.value = this._dbVCoreDropdown.values![i]; + // break; + // } + // } + + // if (this._dbVCoreDropdown.value) { + // this.wizard.model.vCoreNumber = Number((this._dbVCoreDropdown.value as any).displayName); + // } + // this._dbVCoreDropdown.loading = false; + // return; + // } + + // private createMaxMemoryText(view: azdata.ModelView) { + // this._dbMemoryTextBox = view.modelBuilder.inputBox().withProperties({ + // inputType: 'number', + // max: 1024, + // min: 1, + // value: '32', + // required: true + // }).component(); + + // this._dbMemoryTextBox.onTextChanged((value) => { + // this.wizard.model.storageInGB = value + 'GB'; + // }); + // } + + + protected async validate(): Promise { + let errorMessages = []; + let serverName = (this._serverGroupDropdown.value as azdata.CategoryValue).name; + if (serverName === '') { + errorMessages.push(localize('deployAzureSQLDB.NoServerError', "No servers found in current subscription.\nSelect a different subscription containing at least one server")); + } + // let supportedEditionName = (this._dbSupportedEditionsDropdown.value as azdata.CategoryValue).name; + // if (supportedEditionName === '') { + // errorMessages.push(localize('deployAzureSQLDB.SupportedEditionError', "No Supported DB Edition found in current server.\nSelect a different server")); + // } + // let familyName = (this._dbSupportedFamilyDropdown.value as azdata.CategoryValue).name; + // if (familyName === '') { + // errorMessages.push(localize('deployAzureSQLDB.SupportedFamiliesError', "No Supported Family found in current DB edition.\nSelect a different edition")); + // } + + this.wizard.showErrorMessage(errorMessages.join(EOL)); + return errorMessages.join(EOL); + } +} diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/basePage.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/basePage.ts new file mode 100644 index 0000000000..2528167213 --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/basePage.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WizardPageBase } from '../../wizardPageBase'; +import { DeployAzureSQLDBWizard } from '../deployAzureSQLDBWizard'; + +export abstract class BasePage extends WizardPageBase { + public abstract initialize(): void; +} diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/databaseSettingsPage.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/databaseSettingsPage.ts new file mode 100644 index 0000000000..2bd0fc4ab5 --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/databaseSettingsPage.ts @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EOL } from 'os'; +import { DeployAzureSQLDBWizard } from '../deployAzureSQLDBWizard'; +import * as constants from '../constants'; +import { BasePage } from './basePage'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export class DatabaseSettingsPage extends BasePage { + + private _startIpAddressTextRow!: azdata.FlexContainer; + private _startIpAddressTextbox!: azdata.InputBoxComponent; + private _endIpAddressTextRow!: azdata.FlexContainer; + private _endIpAddressTextbox!: azdata.InputBoxComponent; + private _firewallRuleNameTextbox!: azdata.InputBoxComponent; + private _firewallRuleNameTextRow!: azdata.FlexContainer; + private _databaseNameTextbox!: azdata.InputBoxComponent; + private _databaseNameTextRow!: azdata.FlexContainer; + private _collationTextbox!: azdata.InputBoxComponent; + private _collationTextRow!: azdata.FlexContainer; + private _IpInfoText!: azdata.TextComponent; + + private _form!: azdata.FormContainer; + + constructor(wizard: DeployAzureSQLDBWizard) { + super( + constants.DatabaseSettingsPageTitle, + '', + wizard + ); + } + + public async initialize() { + this.pageObject.registerContent(async (view: azdata.ModelView) => { + await Promise.all([ + this.createIpAddressText(view), + this.createFirewallNameText(view), + this.createDatabaseNameText(view), + this.createCollationText(view) + ]); + this._form = view.modelBuilder.formContainer() + .withFormItems( + [ + { + component: this._databaseNameTextRow + }, + { + component: this._collationTextRow + }, + { + component: this._firewallRuleNameTextRow + }, + { + component: this._startIpAddressTextRow + }, + { + component: this._endIpAddressTextRow + }, + { + component: this._IpInfoText + } + ], + { + horizontal: false, + componentWidth: '100%' + }) + .withLayout({ width: '100%' }) + .component(); + + return view.initializeModel(this._form); + }); + } + + public async onEnter(): Promise { + this.wizard.wizardObject.registerNavigationValidator(async (pcInfo) => { + if (pcInfo.newPage < pcInfo.lastPage) { + return true; + } + let errorMessage = await this.validate(); + + if (errorMessage !== '') { + return false; + } + return true; + }); + } + + public async onLeave(): Promise { + this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { + return true; + }); + } + + private createIpAddressText(view: azdata.ModelView) { + + this._IpInfoText = view.modelBuilder.text() + .withProperties({ + value: constants.IpAddressInfoLabel + }).component(); + + //Start IP Address Section: + + this._startIpAddressTextbox = view.modelBuilder.inputBox().withProperties({ + inputType: 'text' + }).component(); + + this._startIpAddressTextbox.onTextChanged((value) => { + this.wizard.model.startIpAddress = value; + }); + + this._startIpAddressTextRow = this.wizard.createFormRowComponent(view, constants.StartIpAddressLabel, '', this._startIpAddressTextbox, true); + + //End IP Address Section: + + this._endIpAddressTextbox = view.modelBuilder.inputBox().withProperties({ + inputType: 'text' + }).component(); + + this._endIpAddressTextbox.onTextChanged((value) => { + this.wizard.model.endIpAddress = value; + }); + + this._endIpAddressTextRow = this.wizard.createFormRowComponent(view, constants.EndIpAddressLabel, '', this._endIpAddressTextbox, true); + } + + private createFirewallNameText(view: azdata.ModelView) { + + this._firewallRuleNameTextbox = view.modelBuilder.inputBox().component(); + + this._firewallRuleNameTextRow = this.wizard.createFormRowComponent(view, constants.FirewallRuleNameLabel, '', this._firewallRuleNameTextbox, true); + + this._firewallRuleNameTextbox.onTextChanged((value) => { + this.wizard.model.firewallRuleName = value; + }); + } + + private createDatabaseNameText(view: azdata.ModelView) { + + this._databaseNameTextbox = view.modelBuilder.inputBox().component(); + + this._databaseNameTextRow = this.wizard.createFormRowComponent(view, constants.DatabaseNameLabel, '', this._databaseNameTextbox, true); + + this._databaseNameTextbox.onTextChanged((value) => { + this.wizard.model.databaseName = value; + }); + } + + private createCollationText(view: azdata.ModelView) { + this._collationTextbox = view.modelBuilder.inputBox().withProperties({ + inputType: 'text', + value: 'SQL_Latin1_General_CP1_CI_AS' + }).component(); + + this._collationTextbox.onTextChanged((value) => { + this.wizard.model.databaseCollation = value; + }); + + this._collationTextRow = this.wizard.createFormRowComponent(view, constants.CollationNameLabel, '', this._collationTextbox, true); + } + + + protected async validate(): Promise { + let errorMessages = []; + let ipRegex = /(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)/; + let startipvalue = this._startIpAddressTextbox.value!; + let endipvalue = this._endIpAddressTextbox.value!; + let firewallname = this._firewallRuleNameTextbox.value!; + let databasename = this._databaseNameTextbox.value!; + let collationname = this._collationTextbox.value!; + + if (!(ipRegex.test(startipvalue))) { + errorMessages.push(localize('deployAzureSQLDB.DBMinIpInvalidError', "Min Ip address is invalid")); + } + + if (!(ipRegex.test(endipvalue))) { + errorMessages.push(localize('deployAzureSQLDB.DBMaxIpInvalidError', "Max Ip address is invalid")); + } + + if (/^\d+$/.test(firewallname)) { + errorMessages.push(localize('deployAzureSQLDB.DBFirewallOnlyNumericNameError', "Firewall name cannot contain only numbers.")); + } + if (firewallname.length < 1 || firewallname.length > 100) { + errorMessages.push(localize('deployAzureSQLDB.DBFirewallLengthError', "Firewall name must be between 1 and 100 characters long.")); + } + if (/[\\\/"\'\[\]:\|<>\+=;,\?\*@\&,]/g.test(firewallname)) { + errorMessages.push(localize('deployAzureSQLDB.DBFirewallSpecialCharError', "Firewall name cannot contain special characters \/\"\"[]:|<>+=;,?*@&, .")); + } + if (/[A-Z]/g.test(firewallname)) { + errorMessages.push(localize('deployAzureSQLDB.DBFirewallUpperCaseError', "Upper case letters are not allowed for firealll name")); + } + + if (/^\d+$/.test(databasename)) { + errorMessages.push(localize('deployAzureSQLDB.DBNameOnlyNumericNameError', "Database name cannot contain only numbers.")); + } + if (databasename.length < 1 || databasename.length > 100) { + errorMessages.push(localize('deployAzureSQLDB.DBNameLengthError', "Database name must be between 1 and 100 characters long.")); + } + if (/[\\\/"\'\[\]:\|<>\+=;,\?\*@\&,]/g.test(databasename)) { + errorMessages.push(localize('deployAzureSQLDB.DBNameSpecialCharError', "Database name cannot contain special characters \/\"\"[]:|<>+=;,?*@&, .")); + } + if (await this.databaseNameExists(databasename)) { + errorMessages.push(localize('deployAzureSQLDB.DBNameExistsError', "Database name must be unique in the current server.")); + } + + if (/^\d+$/.test(collationname)) { + errorMessages.push(localize('deployAzureSQLDB.DBCollationOnlyNumericNameError', "Collation name cannot contain only numbers.")); + } + if (collationname.length < 1 || collationname.length > 100) { + errorMessages.push(localize('deployAzureSQLDB.DBCollationLengthError', "Collation name must be between 1 and 100 characters long.")); + } + if (/[\\\/"\'\[\]:\|<>\+=;,\?\*@\&,]/g.test(collationname)) { + errorMessages.push(localize('deployAzureSQLDB.DBCollationSpecialCharError', "Collation name cannot contain special characters \/\"\"[]:|<>+=;,?*@&, .")); + } + + this.wizard.showErrorMessage(errorMessages.join(EOL)); + return errorMessages.join(EOL); + } + + protected async databaseNameExists(dbName: string): Promise { + const url = `https://management.azure.com` + + `/subscriptions/${this.wizard.model.azureSubscription}` + + `/resourceGroups/${this.wizard.model.azureResouceGroup}` + + `/providers/Microsoft.Sql` + + `/servers/${this.wizard.model.azureServerName}` + + `/databases?api-version=2017-10-01-preview`; + + let response = await this.wizard.getRequest(url, true); + + let nameArray = response.data.value.map((v: any) => { return v.name; }); + return (nameArray.includes(dbName)); + } +} diff --git a/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/summaryPage.ts b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/summaryPage.ts new file mode 100644 index 0000000000..87e0725604 --- /dev/null +++ b/extensions/resource-deployment/src/ui/deployAzureSQLDBWizard/pages/summaryPage.ts @@ -0,0 +1,225 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { WizardPageBase } from '../../wizardPageBase'; +import { DeployAzureSQLDBWizard } from '../deployAzureSQLDBWizard'; +import * as constants from '../constants'; +import { SectionInfo, LabelPosition, FontWeight, FieldType } from '../../../interfaces'; +import { createSection } from '../../modelViewUtils'; + +export class AzureSQLDBSummaryPage extends WizardPageBase { + + private formItems: azdata.FormComponent[] = []; + private _form!: azdata.FormBuilder; + private _view!: azdata.ModelView; + + constructor(wizard: DeployAzureSQLDBWizard) { + super( + 'Summary', + '', + wizard + ); + + } + + public async initialize() { + this.pageObject.registerContent(async (view: azdata.ModelView) => { + this._view = view; + this._form = view.modelBuilder.formContainer(); + return view.initializeModel(this._form!.withLayout({ width: '100%' }).component()); + }); + } + + public async onEnter(): Promise { + + this.formItems.forEach(item => { + this._form.removeFormItem(item); + }); + + this.formItems = []; + + let model = this.wizard.model; + + const labelWidth = '150px'; + const inputWidth = '400px'; + const fieldHeight = '20px'; + + const auzreSettingSection: SectionInfo = { + labelPosition: LabelPosition.Left, + labelWidth: labelWidth, + inputWidth: inputWidth, + fieldHeight: fieldHeight, + spaceBetweenFields: '0', + title: constants.AzureSettingsSummaryPageTitle, + fields: [ + { + type: FieldType.ReadonlyText, + label: constants.AzureAccountDropdownLabel, + defaultValue: model.azureAccount.displayInfo.displayName, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.AzureAccountSubscriptionDropdownLabel, + defaultValue: model.azureSubscriptionDisplayName, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.AzureAccountResourceGroupDropdownLabel, + defaultValue: model.azureResouceGroup, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.AzureAccountDatabaseServersDropdownLabel, + defaultValue: model.azureServerName, + labelCSSStyles: { fontWeight: FontWeight.Bold } + } + ] + }; + + // const databaseHardwareSettingSection: SectionInfo = { //@todo alma1 9/8/2020 section used for upcoming database hardware creation feature. + // labelPosition: LabelPosition.Left, + // labelWidth: labelWidth, + // inputWidth: inputWidth, + // fieldHeight: fieldHeight, + // spaceBetweenFields: '0', + // title: constants.DatabaseHardwareInfoLabel, + // fields: [ + // { + // type: FieldType.ReadonlyText, + // label: constants.DatabaseSupportedEditionsDropdownLabel, + // defaultValue: model.databaseEdition, + // labelCSSStyles: { fontWeight: FontWeight.Bold } + // }, + // { + // type: FieldType.ReadonlyText, + // label: constants.DatabaseSupportedFamilyDropdownLabel, + // defaultValue: model.databaseFamily, + // labelCSSStyles: { fontWeight: FontWeight.Bold } + // }, + // { + // type: FieldType.ReadonlyText, + // label: constants.DatabaseVCoreNumberDropdownLabel, + // defaultValue: String(model.vCoreNumber), + // labelCSSStyles: { fontWeight: FontWeight.Bold } + // }, + // { + // type: FieldType.ReadonlyText, + // label: constants.DatabaseMaxMemorySummaryTextLabel, + // defaultValue: model.storageInGB, + // labelCSSStyles: { fontWeight: FontWeight.Bold } + // } + // ] + // }; + + const databaseSettingSection: SectionInfo = { + labelPosition: LabelPosition.Left, + labelWidth: labelWidth, + inputWidth: inputWidth, + fieldHeight: fieldHeight, + title: constants.DatabaseSettingsPageTitle, + fields: [ + { + type: FieldType.ReadonlyText, + label: constants.DatabaseNameLabel, + defaultValue: model.databaseName, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.CollationNameSummaryLabel, + defaultValue: model.databaseCollation, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.FirewallRuleNameLabel, + defaultValue: model.firewallRuleName, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.StartIpAddressShortLabel, + defaultValue: model.startIpAddress, + labelCSSStyles: { fontWeight: FontWeight.Bold } + }, + { + type: FieldType.ReadonlyText, + label: constants.EndIpAddressShortLabel, + defaultValue: model.endIpAddress, + labelCSSStyles: { fontWeight: FontWeight.Bold } + } + ] + }; + + + const createSectionFunc = async (sectionInfo: SectionInfo): Promise => { + return { + title: '', + component: await createSection({ + container: this.wizard.wizardObject, + inputComponents: {}, + sectionInfo: sectionInfo, + view: this._view, + onNewDisposableCreated: () => { }, + onNewInputComponentCreated: () => { }, + onNewValidatorCreated: () => { }, + toolsService: this.wizard.toolsService + }) + }; + }; + + const azureSection = await createSectionFunc(auzreSettingSection); + //const databaseHardwareSection = await createSectionFunc(databaseHardwareSettingSection); //@todo alma1 9/8/2020 used for upcoming database hardware creation feature. + const databaseSection = await createSectionFunc(databaseSettingSection); + + this.formItems.push(azureSection, /*databaseHardwareSection,*/ databaseSection); + this._form.addFormItems(this.formItems); + + this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { + return true; + }); + } + + public async onLeave(): Promise { + this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { + return true; + }); + } + + public createSummaryRow(view: azdata.ModelView, title: string, textComponent: azdata.TextComponent): azdata.FlexContainer { + + const labelText = view.modelBuilder.text() + .withProperties( + { + value: title, + width: '250px', + }) + .component(); + + labelText.updateCssStyles({ + 'font-weight': '400', + 'font-size': '13px', + }); + + const flexContainer = view.modelBuilder.flexContainer() + .withLayout( + { + flexFlow: 'row', + alignItems: 'center', + }) + .withItems( + [labelText, textComponent], + { + CSSStyles: { 'margin-right': '5px' } + }) + .component(); + + return flexContainer; + } +} diff --git a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts index b90c2f0f4b..63944a452b 100644 --- a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts +++ b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts @@ -274,9 +274,6 @@ export class ResourceTypePickerDialog extends DialogBase { private selectResourceType(resourceType: ResourceType): void { this._currentResourceTypeDisposables.forEach(disposable => disposable.dispose()); this._selectedResourceType = resourceType; - //handle special case when resource type has different OK button. - this._dialogObject.okButton.label = this._selectedResourceType.okButtonText || loc.select; - this._agreementCheckboxChecked = false; this._agreementContainer.clearItems(); if (resourceType.agreement) { @@ -299,15 +296,26 @@ export class ResourceTypePickerDialog extends DialogBase { ariaLabel: option.displayName }).component(); - this._currentResourceTypeDisposables.push(optionSelectBox.onValueChanged(() => { this.updateToolsDisplayTable(); })); + this._currentResourceTypeDisposables.push(optionSelectBox.onValueChanged(() => { + this.updateOkButtonText(); + this.updateToolsDisplayTable(); + })); + this._optionDropDownMap.set(option.name, optionSelectBox); const row = this._view.modelBuilder.flexContainer().withItems([optionLabel, optionSelectBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); this._optionsContainer.addItem(row); }); } + this.updateOkButtonText(); this.updateToolsDisplayTable(); } + private updateOkButtonText(): void { + //handle special case when resource type has different OK button. + let text = this.getCurrentOkText(); + this._dialogObject.okButton.label = text || loc.select; + } + private updateToolsDisplayTable(): void { this.toolRefreshTimestamp = new Date().getTime(); const currentRefreshTimestamp = this.toolRefreshTimestamp; @@ -482,6 +490,17 @@ export class ResourceTypePickerDialog extends DialogBase { return this._selectedResourceType.getProvider(options)!; } + private getCurrentOkText(): string { + const options: { option: string, value: string }[] = []; + + this._optionDropDownMap.forEach((selectBox, option) => { + let selectedValue: azdata.CategoryValue = selectBox.value as azdata.CategoryValue; + options.push({ option: option, value: selectedValue.name }); + }); + + return this._selectedResourceType.getOkButtonText(options)!; + } + protected async onComplete(): Promise { this.toolsService.toolsForCurrentProvider = this._tools; this.resourceTypeService.startDeployment(this.getCurrentProvider());