From a71be2b19365a4b56adfa6f1dd81713aa9f46b06 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Mon, 25 Feb 2019 14:04:12 -0800 Subject: [PATCH] Alanren/bdc (#4161) * wip * target cluster type page * finish target cluster type page * remove commented line --- extensions/big-data-cluster/src/interfaces.ts | 31 ++- .../create-cluster/createClusterModel.ts | 113 +++++++++- .../create-cluster/createClusterWizard.ts | 31 ++- .../pages/clusterProfilePage.ts | 10 +- ...erPage.ts => selectExistingClusterPage.ts} | 38 +--- .../pages/selectTargetClusterTypePage.ts | 196 ++++++++++++++++++ .../create-cluster/pages/settingsPage.ts | 153 ++++++++++---- .../create-cluster/pages/summaryPage.ts | 9 +- .../src/wizards/wizardBase.ts | 29 ++- .../src/wizards/wizardPageBase.ts | 11 +- 10 files changed, 512 insertions(+), 109 deletions(-) rename extensions/big-data-cluster/src/wizards/create-cluster/pages/{targetClusterPage.ts => selectExistingClusterPage.ts} (81%) create mode 100644 extensions/big-data-cluster/src/wizards/create-cluster/pages/selectTargetClusterTypePage.ts diff --git a/extensions/big-data-cluster/src/interfaces.ts b/extensions/big-data-cluster/src/interfaces.ts index ce021226e1..37d352d2d7 100644 --- a/extensions/big-data-cluster/src/interfaces.ts +++ b/extensions/big-data-cluster/src/interfaces.ts @@ -12,6 +12,35 @@ export interface ClusterInfo { export enum TargetClusterType { ExistingKubernetesCluster, - NewLocalCluster, NewAksCluster } + +export interface ClusterPorts { + sql: string; + knox: string; + controller: string; + proxy: string; + grafana: string; + kibana: string; +} + +export interface ContainerRegistryInfo { + registry: string; + repository: string; + imageTag: string; +} + +export interface TargetClusterTypeInfo { + type: TargetClusterType; + name: string; + iconPath: { + dark: string, + light: string + }; +} + +export interface ToolInfo { + name: string, + description: string, + isInstalled: boolean +} \ No newline at end of file diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts index 58d56546a7..2528727598 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts @@ -5,10 +5,15 @@ 'use strict'; import { IKubeConfigParser } from '../../data/kubeConfigParser'; -import { ClusterInfo, TargetClusterType } from '../../interfaces'; +import { ClusterInfo, TargetClusterType, ClusterPorts, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo } from '../../interfaces'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); export class CreateClusterModel { + private _tmp_tools_installed: boolean = false; + constructor(private _kubeConfigParser: IKubeConfigParser) { } @@ -16,6 +21,90 @@ export class CreateClusterModel { return this._kubeConfigParser.parse(configPath); } + public getDefaultPorts(): Thenable { + let promise = new Promise(resolve => { + resolve({ + sql: '31433', + knox: '30443', + controller: '30888', + proxy: '30909', + grafana: '30119', + kibana: '30999' + }); + }); + return promise; + } + + public getDefaultContainerRegistryInfo(): Thenable { + let promise = new Promise(resolve => { + resolve({ + registry: 'http://repo.corp.microsoft.com/', + repository: 'aris-p-master-dsmain-standard', + imageTag: 'latest' + }); + }); + return promise; + } + + public getAllTargetClusterTypeInfo(): Thenable { + let promise = new Promise(resolve => { + let aksCluster: TargetClusterTypeInfo = { + type: TargetClusterType.NewAksCluster, + name: localize('bdc-create.AKSClusterCardText', 'New AKS Cluster'), + iconPath: { + dark: 'images/cluster_inverse.svg', + light: 'images/cluster.svg' + } + }; + + let existingCluster: TargetClusterTypeInfo = { + type: TargetClusterType.ExistingKubernetesCluster, + name: localize('bdc-create.ExistingCardText', 'Existing Cluster'), + iconPath: { + dark: 'images/cluster_inverse.svg', + light: 'images/cluster.svg' + } + }; + resolve([aksCluster, existingCluster]); + }); + return promise; + } + + public getRequiredToolStatus(): Thenable { + let kubeCtl = { + name: 'KUBECTL', + description: 'KUBECTL', + isInstalled: true + }; + let mssqlCtl = { + name: 'MSSQLCTL', + description: 'MSSQLCTL', + isInstalled: true + }; + let azureCli = { + name: 'AzureCLI', + description: 'AzureCLI', + isInstalled: this._tmp_tools_installed + }; + let promise = new Promise(resolve => { + setTimeout(() => { + let tools = this.targetClusterType === TargetClusterType.ExistingKubernetesCluster ? [kubeCtl, mssqlCtl] : [kubeCtl, mssqlCtl, azureCli]; + resolve(tools); + }, 3000); + }); + return promise; + } + + public installTools(): Thenable { + let promise = new Promise(resolve => { + setTimeout(() => { + this._tmp_tools_installed = true; + resolve(); + }, 10000) + }); + return promise; + } + public targetClusterType: TargetClusterType; public selectedCluster: ClusterInfo; @@ -23,4 +112,26 @@ export class CreateClusterModel { public adminUserName: string; public adminPassword: string; + + public sqlPort: string; + + public knoxPort: string; + + public controllerPort: string; + + public proxyPort: string; + + public grafanaPort: string; + + public kibanaPort: string; + + public containerRegistry: string; + + public containerRepository: string; + + public containerImageTag: string; + + public containerRegistryUserName: string; + + public containerRegistryPassword: string; } diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts index a2ab1d87a3..d1775b6cab 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts @@ -5,7 +5,7 @@ 'use strict'; import { CreateClusterModel } from './createClusterModel'; -import { SelectTargetClusterPage } from './pages/targetClusterPage'; +import { SelectExistingClusterPage } from './pages/selectExistingClusterPage'; import { SummaryPage } from './pages/summaryPage'; import { SettingsPage } from './pages/settingsPage'; import { ClusterProfilePage } from './pages/clusterProfilePage'; @@ -13,10 +13,11 @@ import { TestKubeConfigParser } from '../../data/kubeConfigParser'; import { ExtensionContext } from 'vscode'; import { WizardBase } from '../wizardBase'; import * as nls from 'vscode-nls'; +import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage'; const localize = nls.loadMessageBundle(); -export class CreateClusterWizard extends WizardBase { +export class CreateClusterWizard extends WizardBase { constructor(context: ExtensionContext) { let configParser = new TestKubeConfigParser(); let model = new CreateClusterModel(configParser); @@ -24,22 +25,18 @@ export class CreateClusterWizard extends WizardBase { } protected initialize(): void { - let settingsPage = new SettingsPage(this.model, this); - let clusterProfilePage = new ClusterProfilePage(this.model, this); - let selectTargetClusterPage = new SelectTargetClusterPage(this.model, this); - let summaryPage = new SummaryPage(this.model, this); + let settingsPage = new SettingsPage(this); + let clusterProfilePage = new ClusterProfilePage(this); + let selectTargetClusterPage = new SelectExistingClusterPage(this); + let summaryPage = new SummaryPage(this); + let targetClusterTypePage = new SelectTargetClusterTypePage(this); + this.setPages([targetClusterTypePage, settingsPage, clusterProfilePage, selectTargetClusterPage, summaryPage]); - this.wizard.pages = [ - settingsPage.page, - clusterProfilePage.page, - selectTargetClusterPage.page, - summaryPage.page - ]; + this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts'); + this.wizardObject.generateScriptButton.hidden = true; + this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create'); - this.wizard.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts'); - this.wizard.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create'); - - this.wizard.generateScriptButton.onClick(() => { }); - this.wizard.doneButton.onClick(() => { }); + this.wizardObject.generateScriptButton.onClick(() => { }); + this.wizardObject.doneButton.onClick(() => { }); } } diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/clusterProfilePage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/clusterProfilePage.ts index deebb8eea3..1a4ab05275 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/pages/clusterProfilePage.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/clusterProfilePage.ts @@ -6,17 +6,17 @@ import * as sqlops from 'sqlops'; import { WizardPageBase } from '../../wizardPageBase'; -import { CreateClusterModel } from '../createClusterModel'; -import { WizardBase } from '../../wizardBase'; +import { CreateClusterWizard } from '../createClusterWizard'; import * as nls from 'vscode-nls'; + const localize = nls.loadMessageBundle(); -export class ClusterProfilePage extends WizardPageBase { - constructor(model: CreateClusterModel, wizard: WizardBase) { +export class ClusterProfilePage extends WizardPageBase { + constructor(wizard: CreateClusterWizard) { super(localize('bdc-create.clusterProfilePageTitle', 'Select a cluster profile'), localize('bdc-create.clusterProfilePageDescription', 'Select your requirement and we will provide you a pre-defined default scaling. You can later go to cluster configuration and customize it.'), - model, wizard); + wizard); } protected initialize(view: sqlops.ModelView): Thenable { diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/targetClusterPage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts similarity index 81% rename from extensions/big-data-cluster/src/wizards/create-cluster/pages/targetClusterPage.ts rename to extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts index 4e2fe5bd05..39838827d4 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/pages/targetClusterPage.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts @@ -8,27 +8,24 @@ import * as sqlops from 'sqlops'; import * as vscode from 'vscode'; import * as os from 'os'; import { WizardPageBase } from '../../wizardPageBase'; -import { CreateClusterModel } from '../createClusterModel'; -import { WizardBase } from '../../wizardBase'; +import { CreateClusterWizard } from '../createClusterWizard'; import { TargetClusterType } from '../../../interfaces'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); const ClusterTypeRadioButtonGroupName = 'SelectClusterType'; -export class SelectTargetClusterPage extends WizardPageBase { - constructor(model: CreateClusterModel, wizard: WizardBase) { +export class SelectExistingClusterPage extends WizardPageBase { + constructor(wizard: CreateClusterWizard) { super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'), localize('bdc-create.selectTargetClusterPageDescription', 'Select an existing Kubernetes cluster or choose a cluster type you want to deploy'), - model, wizard); + wizard); } private existingClusterOption: sqlops.RadioButtonComponent; - private createLocalClusterOption: sqlops.RadioButtonComponent; private createAksClusterOption: sqlops.RadioButtonComponent; private pageContainer: sqlops.DivContainer; private existingClusterControl: sqlops.FlexContainer; - private createLocalClusterControl: sqlops.FlexContainer; private createAksClusterControl: sqlops.FlexContainer; private clusterContextsLabel: sqlops.TextComponent; private errorLoadingClustersLabel: sqlops.TextComponent; @@ -38,33 +35,25 @@ export class SelectTargetClusterPage extends WizardPageBase protected initialize(view: sqlops.ModelView): Thenable { let self = this; - this.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster; + this.wizard.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster; this.existingClusterOption = this.createTargetTypeRadioButton(view, localize('bdc-create.existingK8sCluster', 'Existing Kubernetes cluster'), true); - this.createLocalClusterOption = this.createTargetTypeRadioButton(view, localize('bdc-create.createLocalCluster', 'Create new local cluster')); this.createAksClusterOption = this.createTargetTypeRadioButton(view, localize('bdc-create.createAksCluster', 'Create new Azure Kubernetes Service cluster')); this.existingClusterOption.onDidClick(() => { self.pageContainer.clearItems(); self.pageContainer.addItem(self.existingClusterControl); - self.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster; - }); - - this.createLocalClusterOption.onDidClick(() => { - self.pageContainer.clearItems(); - self.pageContainer.addItem(self.createLocalClusterControl); - self.model.targetClusterType = TargetClusterType.NewLocalCluster; + self.wizard.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster; }); this.createAksClusterOption.onDidClick(() => { self.pageContainer.clearItems(); self.pageContainer.addItem(self.createAksClusterControl); - self.model.targetClusterType = TargetClusterType.NewAksCluster; + self.wizard.model.targetClusterType = TargetClusterType.NewAksCluster; }); - let optionGroup = view.modelBuilder.divContainer().withItems([this.existingClusterOption, this.createLocalClusterOption, this.createAksClusterOption], + let optionGroup = view.modelBuilder.divContainer().withItems([this.existingClusterOption, this.createAksClusterOption], { CSSStyles: { 'margin-right': '30px' } }).withLayout({ width: 'auto' }).component(); this.initExistingClusterControl(view); - this.initLocalClusterControl(view); this.initAksClusterControl(view); this.pageContainer = view.modelBuilder.divContainer().withItems([this.existingClusterControl]).withLayout({ width: '100%' }).component(); let container = view.modelBuilder.flexContainer().withItems([optionGroup, this.pageContainer], { flex: '0 0 auto' }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component(); @@ -126,11 +115,11 @@ export class SelectTargetClusterPage extends WizardPageBase configFileInput.value = fileUri.fsPath; - let clusters = self.model.loadClusters(fileUri.fsPath); + let clusters = self.wizard.model.loadClusters(fileUri.fsPath); self.cards = []; if (clusters.length !== 0) { - self.model.selectedCluster = clusters[0]; + self.wizard.model.selectedCluster = clusters[0]; for (let i = 0; i < clusters.length; i++) { let cluster = clusters[i]; let card = view.modelBuilder.card().withProperties({ @@ -150,7 +139,7 @@ export class SelectTargetClusterPage extends WizardPageBase c.selected = false; } }); - self.model.selectedCluster = cluster; + self.wizard.model.selectedCluster = cluster; } }); self.cards.push(card); @@ -164,11 +153,6 @@ export class SelectTargetClusterPage extends WizardPageBase }); } - private initLocalClusterControl(view: sqlops.ModelView): void { - let placeholder = view.modelBuilder.text().withProperties({ value: 'create local cluster place holder' }).component(); - this.createLocalClusterControl = view.modelBuilder.divContainer().withItems([placeholder]).component(); - } - private initAksClusterControl(view: sqlops.ModelView): void { let placeholder = view.modelBuilder.text().withProperties({ value: 'AKS cluster place holder' }).component(); this.createAksClusterControl = view.modelBuilder.divContainer().withItems([placeholder]).component(); diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectTargetClusterTypePage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectTargetClusterTypePage.ts new file mode 100644 index 0000000000..7d2c7bc9ba --- /dev/null +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectTargetClusterTypePage.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as sqlops from 'sqlops'; +import { WizardPageBase } from '../../wizardPageBase'; +import { TargetClusterTypeInfo } from '../../../interfaces'; +import * as nls from 'vscode-nls'; +import { CreateClusterWizard } from '../createClusterWizard'; + +const localize = nls.loadMessageBundle(); + +const InstallToolsButtonText = localize('bdc-create.InstallToolsText', 'Install Tools'); +const InstallingButtonText = localize('bdc-create.InstallingButtonText', 'Installing...'); + +export class SelectTargetClusterTypePage extends WizardPageBase { + private cards: sqlops.CardComponent[]; + private toolsTable: sqlops.TableComponent; + private formBuilder: sqlops.FormBuilder; + private form: sqlops.FormContainer; + private installToolsButton: sqlops.window.Button; + private toolsLoadingWrapper: sqlops.LoadingComponent; + private refreshToolsButton: sqlops.window.Button; + private isValid: boolean = false; + private isLoading: boolean = false; + + constructor(wizard: CreateClusterWizard) { + super(localize('bdc-create.selectTargetClusterTypePageTitle', 'Where do you want to deploy this SQL Server big data cluster?'), + localize('bdc-create.selectTargetClusterTypePageDescription', 'Choose the target environment and then install the required tools.'), + wizard); + this.installToolsButton = sqlops.window.createButton(InstallToolsButtonText); + this.installToolsButton.hidden = true; + this.installToolsButton.onClick(() => { + this.toolsLoadingWrapper.loading = true; + this.wizard.wizardObject.message = null; + this.installToolsButton.label = InstallingButtonText; + this.installToolsButton.enabled = false; + this.wizard.model.installTools().then(() => { + this.installToolsButton.label = InstallToolsButtonText; + this.installToolsButton.enabled = true; + return this.updateRequiredToolStatus(); + }); + }); + this.wizard.addButton(this.installToolsButton); + + this.refreshToolsButton = sqlops.window.createButton(localize('bdc-create.RefreshToolsButtonText', 'Refresh Status')); + this.refreshToolsButton.hidden = true; + this.refreshToolsButton.onClick(() => { + this.updateRequiredToolStatus(); + }); + this.wizard.addButton(this.refreshToolsButton); + this.wizard.wizardObject.registerNavigationValidator(() => { + if (this.isLoading) { + let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.'); + let messageLevel = sqlops.window.MessageLevel.Information; + this.wizard.wizardObject.message = { + level: messageLevel, + text: messageText + }; + return false; + } + if (!this.isValid) { + let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ? + localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type') : + localize('bdc-create.MissingToolsText', 'Please install the missing tools'); + this.wizard.wizardObject.message = { + level: sqlops.window.MessageLevel.Error, + text: messageText + }; + } + return this.isValid; + }); + } + + protected initialize(view: sqlops.ModelView): Thenable { + let self = this; + return self.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => { + self.cards = []; + + clusterTypes.forEach(clusterType => { + let card = self.createCard(view, clusterType); + self.cards.push(card); + }); + let cardsContainer = view.modelBuilder.flexContainer().withItems(self.cards, { flex: '0 0 auto' }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component(); + + let toolColumn: sqlops.TableColumn = { + value: localize('bdc-create.toolNameColumnHeader', 'Tool'), + width: 100 + }; + let descriptionColumn: sqlops.TableColumn = { + value: localize('bdc-create.toolDescriptionColumnHeader', 'Description'), + width: 200 + }; + let statusColumn: sqlops.TableColumn = { + value: localize('bdc-create.toolStatusColumnHeader', 'Status'), + width: 100 + }; + + self.toolsTable = view.modelBuilder.table().withProperties({ + height: 150, + data: [], + columns: [toolColumn, descriptionColumn, statusColumn], + width: 850 + }).component(); + + self.toolsLoadingWrapper = view.modelBuilder.loadingComponent().withItem(self.toolsTable).component(); + self.formBuilder = view.modelBuilder.formContainer().withFormItems( + [ + { + component: cardsContainer, + title: localize('bdc-create.PickTargetEnvironmentText', 'Pick target environment') + } + ], + { + horizontal: false + } + ); + + + self.form = self.formBuilder.withLayout({ width: '100%' }).component(); + return view.initializeModel(self.form); + }); + } + + public onEnter(): void { + this.installToolsButton.hidden = false; + this.refreshToolsButton.hidden = false; + this.refreshToolsButton.enabled = true; + this.installToolsButton.enabled = false; + } + + public onLeave(): void { + this.installToolsButton.hidden = true; + this.refreshToolsButton.hidden = true; + } + + + private createCard(view: sqlops.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): sqlops.CardComponent { + let self = this; + let card = view.modelBuilder.card().withProperties({ + cardType: sqlops.CardType.VerticalButton, + iconPath: { + dark: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.dark), + light: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.light) + }, + label: targetClusterTypeInfo.name + }).component(); + card.onCardSelectedChanged(() => { + if (card.selected) { + self.wizard.wizardObject.message = null; + self.wizard.model.targetClusterType = targetClusterTypeInfo.type; + self.cards.forEach(c => { + if (c !== card) { + c.selected = false; + } + }); + + if (self.form.items.length === 1) { + self.formBuilder.addFormItem({ + title: localize('bdc-create.RequiredToolsText', 'Required tools'), + component: self.toolsLoadingWrapper + }); + } + self.updateRequiredToolStatus(); + } else { + if (self.cards.filter(c => { return c !== card && c.selected }).length === 0) { + card.selected = true; + } + } + }); + return card; + } + + private updateRequiredToolStatus(): Thenable { + this.isLoading = true; + this.installToolsButton.hidden = false; + this.refreshToolsButton.hidden = false; + this.toolsLoadingWrapper.loading = true; + this.refreshToolsButton.enabled = false; + this.installToolsButton.enabled = false; + return this.wizard.model.getRequiredToolStatus().then(tools => { + this.isLoading = false; + this.toolsLoadingWrapper.loading = false; + this.refreshToolsButton.enabled = true; + this.installToolsButton.enabled = tools.filter(tool => !tool.isInstalled).length !== 0; + this.isValid = !this.installToolsButton.enabled; + this.wizard.wizardObject.message = null; + let tableData = tools.map(tool => { + return [tool.name, tool.description, tool.isInstalled ? localize('bdc-create.InstalledText', 'Installed') : localize('bdc-create.NotInstalledText', 'Not Installed')]; + }); + this.toolsTable.data = tableData; + }); + } +} diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/settingsPage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/settingsPage.ts index a5aa5b168c..3cc35d5ce6 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/pages/settingsPage.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/settingsPage.ts @@ -6,78 +6,141 @@ import * as sqlops from 'sqlops'; import { WizardPageBase } from '../../wizardPageBase'; -import { CreateClusterModel } from '../createClusterModel'; -import { WizardBase } from '../../wizardBase'; import * as nls from 'vscode-nls'; +import { ClusterPorts, ContainerRegistryInfo } from '../../../interfaces'; +import { CreateClusterWizard } from '../createClusterWizard'; const localize = nls.loadMessageBundle(); +const UserNameInputWidth = '300px'; +const PortInputWidth = '100px'; -export class SettingsPage extends WizardPageBase { - constructor(model: CreateClusterModel, wizard: WizardBase) { +export class SettingsPage extends WizardPageBase { + constructor(wizard: CreateClusterWizard) { super(localize('bdc-create.settingsPageTitle', 'Settings'), localize('bdc-create.settingsPageDescription', 'Configure the settings required for deploying SQL Server big data cluster'), - model, wizard); + wizard); } protected initialize(view: sqlops.ModelView): Thenable { - let formBuilder = view.modelBuilder.formContainer(); - let adminUserNameInput = this.createInputWithLabel(view, localize('bdc-create.AdminUsernameText', 'Admin username'), true, (inputBox: sqlops.InputBoxComponent) => { - this.model.adminUserName = inputBox.value; + let clusterPorts: ClusterPorts; + let containerRegistryInfo: ContainerRegistryInfo; + + let clusterPortsPromise = this.wizard.model.getDefaultPorts().then(ports => { + clusterPorts = ports; }); - let adminPasswordInput = this.createInputWithLabel(view, localize('bdc-create.AdminUserPasswordText', 'Password'), true, (inputBox: sqlops.InputBoxComponent) => { - this.model.adminPassword = inputBox.value; - }, 'password'); - let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([adminUserNameInput, adminPasswordInput]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component(); - let dockerSettingsGroup = view.modelBuilder.groupContainer().withItems([]).withLayout({ header: localize('bdc-create.DockerSettingsText', 'Docker Settings'), collapsible: true }).component(); + let containerRegistryPromise = this.wizard.model.getDefaultContainerRegistryInfo().then(containerRegistry => { + containerRegistryInfo = containerRegistry; + }); + return Promise.all([clusterPortsPromise, containerRegistryPromise]).then(() => { + let formBuilder = view.modelBuilder.formContainer(); - let acceptEulaCheckbox = view.modelBuilder.checkBox().component(); - acceptEulaCheckbox.checked = false; + //User settings + let adminUserNameInput = this.createInputWithLabel(view, localize('bdc-create.AdminUsernameText', 'Admin username'), true, UserNameInputWidth, '', (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.adminUserName = inputBox.value; + }); + let adminPasswordInput = this.createInputWithLabel(view, localize('bdc-create.AdminUserPasswordText', 'Password'), true, UserNameInputWidth, '', (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.adminPassword = inputBox.value; + }, 'password'); - let eulaLink: sqlops.LinkArea = { - text: localize('bdc-create.LicenseAgreementText', 'License Agreement'), - url: 'https://docs.microsoft.com/en-us/sql/getting-started/about-the-sql-server-license-terms?view=sql-server-2014' - }; - let privacyPolicyLink: sqlops.LinkArea = { - text: localize('bdc-create.PrivacyPolicyText', 'Privacy Policy'), - url: 'https://privacy.microsoft.com/en-us/privacystatement' - }; + // Port settings + let sqlPortInput = this.createInputWithLabel(view, localize('bdc-create.SQLPortText', 'SQL Master port'), true, PortInputWidth, clusterPorts.sql, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.sqlPort = inputBox.value; + }); + let knoxPortInput = this.createInputWithLabel(view, localize('bdc-create.KnoxPortText', 'Knox port'), true, PortInputWidth, clusterPorts.knox, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.knoxPort = inputBox.value; + }); + let controllerPortInput = this.createInputWithLabel(view, localize('bdc-create.ControllerPortText', 'Controller port'), true, PortInputWidth, clusterPorts.controller, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.controllerPort = inputBox.value; + }); + let proxyPortInput = this.createInputWithLabel(view, localize('bdc-create.ProxyPortText', 'Proxy port'), true, PortInputWidth, clusterPorts.proxy, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.proxyPort = inputBox.value; + }); + let grafanaPortInput = this.createInputWithLabel(view, localize('bdc-create.GrafanaPortText', 'Grafana port'), true, PortInputWidth, clusterPorts.grafana, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.grafanaPort = inputBox.value; + }); + let kibanaPortInput = this.createInputWithLabel(view, localize('bdc-create.KibanaPortText', 'Kibana port'), true, PortInputWidth, clusterPorts.kibana, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.kibanaPort = inputBox.value; + }); - let checkboxText = view.modelBuilder.text().withProperties({ - value: localize({ - key: 'bdc-create.AcceptTermsText', - comment: ['{0} is the place holder for License Agreement, {1} is the place holder for Privacy Policy'] - }, 'I accept the {0} and {1}.'), - links: [eulaLink, privacyPolicyLink] - }).component(); + // Container Registry Settings + let registryInput = this.createInputWithLabel(view, localize('bdc-create.RegistryText', 'Registry'), true, UserNameInputWidth, containerRegistryInfo.registry, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.containerRegistry = inputBox.value; + }); - let eulaContainer = this.createRow(view, [acceptEulaCheckbox, checkboxText]); + let repositoryInput = this.createInputWithLabel(view, localize('bdc-create.RepositoryText', 'Repository'), true, UserNameInputWidth, containerRegistryInfo.repository, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.containerRepository = inputBox.value; + }); - let form = formBuilder.withFormItems([ - { - title: '', - component: basicSettingsGroup - }, { - title: '', - component: dockerSettingsGroup - }, { - title: '', - component: eulaContainer - }]).component(); - return view.initializeModel(form); + let imageTagInput = this.createInputWithLabel(view, localize('bdc-create.ImageTagText', 'Image tag'), true, UserNameInputWidth, containerRegistryInfo.imageTag, (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.containerRegistry = inputBox.value; + }); + + let registryUserNameInput = this.createInputWithLabel(view, localize('bdc-create.RegistryUserNameText', 'Username'), false, UserNameInputWidth, '', (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.containerRegistryUserName = inputBox.value; + }); + + let registryPasswordInput = this.createInputWithLabel(view, localize('bdc-create.RegistryPasswordText', 'Password'), false, UserNameInputWidth, '', (inputBox: sqlops.InputBoxComponent) => { + this.wizard.model.containerRegistryPassword = inputBox.value; + }); + + let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([adminUserNameInput, adminPasswordInput]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component(); + let containerSettingsGroup = view.modelBuilder.groupContainer().withItems([registryInput, repositoryInput, imageTagInput, registryUserNameInput, registryPasswordInput]).withLayout({ header: localize('bdc-create.ContainerRegistrySettings', 'Container Registry Settings'), collapsible: true }).component(); + let portSettingsGroup = view.modelBuilder.groupContainer().withItems([sqlPortInput, knoxPortInput, controllerPortInput, proxyPortInput, grafanaPortInput, kibanaPortInput]).withLayout({ header: localize('bdc-create.PortSettings', 'Port Settings (Optional)'), collapsible: true, collapsed: true }).component(); + + let acceptEulaCheckbox = view.modelBuilder.checkBox().component(); + acceptEulaCheckbox.checked = false; + + let eulaLink: sqlops.LinkArea = { + text: localize('bdc-create.LicenseAgreementText', 'License Agreement'), + url: 'https://docs.microsoft.com/en-us/sql/getting-started/about-the-sql-server-license-terms?view=sql-server-2014' + }; + let privacyPolicyLink: sqlops.LinkArea = { + text: localize('bdc-create.PrivacyPolicyText', 'Privacy Policy'), + url: 'https://privacy.microsoft.com/en-us/privacystatement' + }; + + let checkboxText = view.modelBuilder.text().withProperties({ + value: localize({ + key: 'bdc-create.AcceptTermsText', + comment: ['{0} is the place holder for License Agreement, {1} is the place holder for Privacy Policy'] + }, 'I accept the {0} and {1}.'), + links: [eulaLink, privacyPolicyLink] + }).component(); + + let eulaContainer = this.createRow(view, [acceptEulaCheckbox, checkboxText]); + + let form = formBuilder.withFormItems([ + { + title: '', + component: eulaContainer + }, + { + title: '', + component: basicSettingsGroup + }, { + title: '', + component: containerSettingsGroup + }, { + title: '', + component: portSettingsGroup + }]).component(); + return view.initializeModel(form); + }); } - private createInputWithLabel(view: sqlops.ModelView, label: string, isRequiredField: boolean, textChangedHandler: (inputBox: sqlops.InputBoxComponent) => void, inputType: string = 'text'): sqlops.FlexContainer { + private createInputWithLabel(view: sqlops.ModelView, label: string, isRequiredField: boolean, inputWidth: string, initialValue: string, textChangedHandler: (inputBox: sqlops.InputBoxComponent) => void, inputType: string = 'text'): sqlops.FlexContainer { let input = view.modelBuilder.inputBox().withProperties({ required: isRequiredField, inputType: inputType }).component(); let text = view.modelBuilder.text().withProperties({ value: label }).component(); - input.width = '300px'; + input.width = inputWidth; text.width = '150px'; input.onTextChanged(() => { textChangedHandler(input); }); + input.value = initialValue; return this.createRow(view, [text, input]); } diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/summaryPage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/summaryPage.ts index e22a4330a0..ff1f04aa70 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/pages/summaryPage.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/summaryPage.ts @@ -6,15 +6,14 @@ import * as sqlops from 'sqlops'; import { WizardPageBase } from '../../wizardPageBase'; -import { CreateClusterModel } from '../createClusterModel'; -import { WizardBase } from '../../wizardBase'; +import { CreateClusterWizard } from '../createClusterWizard'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -export class SummaryPage extends WizardPageBase { - constructor(model: CreateClusterModel, wizard: WizardBase) { - super(localize('bdc-create.summaryPageTitle', 'Summary'), '', model, wizard); +export class SummaryPage extends WizardPageBase { + constructor(wizard: CreateClusterWizard) { + super(localize('bdc-create.summaryPageTitle', 'Summary'), '', wizard); } protected initialize(view: sqlops.ModelView): Thenable { diff --git a/extensions/big-data-cluster/src/wizards/wizardBase.ts b/extensions/big-data-cluster/src/wizards/wizardBase.ts index 67a1702602..164c4fbbc8 100644 --- a/extensions/big-data-cluster/src/wizards/wizardBase.ts +++ b/extensions/big-data-cluster/src/wizards/wizardBase.ts @@ -6,19 +6,40 @@ import * as sqlops from 'sqlops'; import { ExtensionContext } from 'vscode'; +import { WizardPageBase } from './wizardPageBase'; -export abstract class WizardBase { +export abstract class WizardBase { - protected wizard: sqlops.window.Wizard; + public wizardObject: sqlops.window.Wizard; + private customButtons: sqlops.window.Button[]; + private pages: WizardPageBase[]; constructor(public model: T, public context: ExtensionContext, private title: string) { + this.customButtons = []; } public open(): Thenable { - this.wizard = sqlops.window.createWizard(this.title); + this.wizardObject = sqlops.window.createWizard(this.title); this.initialize(); - return this.wizard.open(); + this.wizardObject.customButtons = this.customButtons; + this.wizardObject.onPageChanged((e) => { + let previousPage = this.pages[e.lastPage]; + let newPage = this.pages[e.newPage]; + previousPage.onLeave(); + newPage.onEnter(); + }); + return this.wizardObject.open(); + } protected abstract initialize(): void; + + public addButton(button: sqlops.window.Button) { + this.customButtons.push(button); + } + + protected setPages(pages: WizardPageBase[]) { + this.wizardObject.pages = pages.map(p => p.pageObject); + this.pages = pages; + } } diff --git a/extensions/big-data-cluster/src/wizards/wizardPageBase.ts b/extensions/big-data-cluster/src/wizards/wizardPageBase.ts index cf0b5cbbb7..3225036959 100644 --- a/extensions/big-data-cluster/src/wizards/wizardPageBase.ts +++ b/extensions/big-data-cluster/src/wizards/wizardPageBase.ts @@ -5,20 +5,19 @@ 'use strict'; import * as sqlops from 'sqlops'; -import { WizardBase } from './wizardBase'; export abstract class WizardPageBase { private _page: sqlops.window.WizardPage; - public get page(): sqlops.window.WizardPage { + public get pageObject(): sqlops.window.WizardPage { return this._page; } - public get wizard(): WizardBase { + public get wizard(): T { return this._wizard; } - constructor(title: string, description: string, protected model: T, private _wizard: WizardBase) { + constructor(title: string, description: string, private _wizard: T) { this._page = sqlops.window.createWizardPage(title); this._page.description = description; this._page.registerContent((view: sqlops.ModelView) => { @@ -27,4 +26,8 @@ export abstract class WizardPageBase { } protected abstract initialize(view: sqlops.ModelView): Thenable; + + public onEnter(): void { } + + public onLeave(): void { } }