profile page and summary page (#4769)

* add cluster name to page

* implement profile page -1

* fix compilation error due to new method

* profile page 0328

* summary page

* make divcontainer accessible

* handle disposable

* add support for "coming soon" cards
This commit is contained in:
Alan Ren
2019-04-02 13:52:39 -07:00
committed by GitHub
parent 63485c8c78
commit e83a6f9c2e
15 changed files with 816 additions and 99 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TargetClusterType, ClusterPorts, ClusterType, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus } from '../../interfaces';
import { TargetClusterType, ClusterPorts, ClusterType, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus, ClusterProfile, PoolConfiguration, SQLServerMasterConfiguration, ClusterPoolType, ClusterResourceSummary } from '../../interfaces';
import { getContexts, KubectlContext, setContext, inferCurrentClusterType } from '../../kubectl/kubectlUtils';
import { Kubectl } from '../../kubectl/kubectl';
import { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
@@ -57,6 +57,7 @@ export class CreateClusterModel implements Scriptable {
public getAllTargetClusterTypeInfo(): Thenable<TargetClusterTypeInfo[]> {
let promise = new Promise<TargetClusterTypeInfo[]>(resolve => {
let aksCluster: TargetClusterTypeInfo = {
enabled: false,
type: TargetClusterType.NewAksCluster,
name: localize('bdc-create.AKSClusterCardText', 'New AKS Cluster'),
fullName: localize('bdc-create.AKSClusterFullName', 'New Azure Kubernetes Service cluster'),
@@ -69,9 +70,10 @@ export class CreateClusterModel implements Scriptable {
};
let existingCluster: TargetClusterTypeInfo = {
enabled: true,
type: TargetClusterType.ExistingKubernetesCluster,
name: localize('bdc-create.ExistingClusterCardText', 'Existing Cluster'),
fullName: localize('bdc-create.ExistingClusterFullName', 'Existing Kubernetes Cluster'),
fullName: localize('bdc-create.ExistingClusterFullName', 'Existing Kubernetes cluster'),
description: localize('bdc-create.ExistingClusterDescription', 'This option assumes you already have a Kubernetes cluster installed, Once a prerequisite check is done, ensure the correct cluster context is selected.'),
iconPath: {
dark: 'images/kubernetes.svg',
@@ -106,7 +108,7 @@ export class CreateClusterModel implements Scriptable {
setTimeout(() => {
let tools = this.targetClusterType === TargetClusterType.ExistingKubernetesCluster ? [kubeCtl, mssqlCtl] : [kubeCtl, mssqlCtl, azureCli];
resolve(tools);
}, 2000);
}, 1000);
});
return promise;
}
@@ -117,7 +119,7 @@ export class CreateClusterModel implements Scriptable {
tool.status = ToolInstallationStatus.Installed;
this._tmp_tools_installed = true;
resolve();
}, 2000);
}, 1000);
});
return promise;
}
@@ -126,6 +128,8 @@ export class CreateClusterModel implements Scriptable {
return path.join(os.homedir(), '.kube', 'config');
}
public clusterName: string;
public targetClusterType: TargetClusterType;
public selectedCluster: KubectlContext;
@@ -156,6 +160,8 @@ export class CreateClusterModel implements Scriptable {
public containerRegistryPassword: string;
public profile: ClusterProfile;
public async getTargetClusterPlatform(targetContextName: string): Promise<string> {
await setContext(this._kubectl, targetContextName);
let clusterType = await inferCurrentClusterType(this._kubectl);
@@ -207,4 +213,116 @@ export class CreateClusterModel implements Scriptable {
public getTargetKubectlContext(): KubectlContext {
return this.selectedCluster;
}
public getClusterResource(): Thenable<ClusterResourceSummary> {
let promise = new Promise<ClusterResourceSummary>(resolve => {
setTimeout(() => {
let resoureSummary: ClusterResourceSummary = {
hardwareLabels: [
{
name: '<Default>',
totalNodes: 10,
totalCores: 22,
totalDisks: 128,
totalMemoryInGB: 77
},
{
name: '#data',
totalNodes: 4,
totalCores: 22,
totalDisks: 200,
totalMemoryInGB: 100
},
{
name: '#compute',
totalNodes: 12,
totalCores: 124,
totalDisks: 24,
totalMemoryInGB: 100
},
{
name: '#premium',
totalNodes: 10,
totalCores: 100,
totalDisks: 200,
totalMemoryInGB: 770
}
]
};
resolve(resoureSummary);
}, 1000);
});
return promise;
}
public getProfiles(): Thenable<ClusterProfile[]> {
let promise = new Promise<ClusterProfile[]>(resolve => {
setTimeout(() => {
let profiles: ClusterProfile[] = [];
profiles.push({
name: 'Basic',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(1, 1),
computePoolConfiguration: this.createComputePoolConfiguration(2),
dataPoolConfiguration: this.createDataPoolConfiguration(2),
storagePoolConfiguration: this.createStoragePoolConfiguration(2),
sparkPoolConfiguration: this.createSparkPoolConfiguration(2)
});
profiles.push({
name: 'Standard',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(3, 9),
computePoolConfiguration: this.createComputePoolConfiguration(5),
dataPoolConfiguration: this.createDataPoolConfiguration(5),
storagePoolConfiguration: this.createStoragePoolConfiguration(5),
sparkPoolConfiguration: this.createSparkPoolConfiguration(5)
});
profiles.push({
name: 'Premium',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(5, 9),
computePoolConfiguration: this.createComputePoolConfiguration(7),
dataPoolConfiguration: this.createDataPoolConfiguration(7),
storagePoolConfiguration: this.createStoragePoolConfiguration(7),
sparkPoolConfiguration: this.createSparkPoolConfiguration(7)
});
resolve(profiles);
}, 1000);
});
return promise;
}
private createSQLPoolConfiguration(scale: number, maxScale: number): SQLServerMasterConfiguration {
return <SQLServerMasterConfiguration>{
type: ClusterPoolType.SQL,
engineOnly: false,
scale: scale,
maxScale: maxScale
};
}
private createComputePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Compute,
scale: scale
};
}
private createDataPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Data,
scale: scale
};
}
private createStoragePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Storage,
scale: scale
};
}
private createSparkPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Spark,
scale: scale
};
}
}

View File

@@ -18,7 +18,7 @@ import { ScriptGenerator } from '../../scripting/scripting';
const localize = nls.loadMessageBundle();
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
private scripter : ScriptGenerator;
private scripter: ScriptGenerator;
constructor(context: ExtensionContext, kubectl: Kubectl) {
let model = new CreateClusterModel(kubectl);
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
@@ -31,19 +31,24 @@ export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateCl
let selectTargetClusterPage = new SelectExistingClusterPage(this);
let summaryPage = new SummaryPage(this);
let targetClusterTypePage = new SelectTargetClusterTypePage(this);
this.setPages([targetClusterTypePage, selectTargetClusterPage, clusterProfilePage, settingsPage, summaryPage]);
this.setPages([targetClusterTypePage, selectTargetClusterPage, settingsPage, clusterProfilePage, summaryPage]);
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
this.wizardObject.generateScriptButton.hidden = false;
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
this.wizardObject.generateScriptButton.onClick(async () => {
this.wizardObject.generateScriptButton.enabled = false;
this.scripter.generateDeploymentScript(this.model).then( () => {
this.wizardObject.generateScriptButton.enabled = true;
//TODO: Add error handling.
});
});
this.wizardObject.doneButton.onClick(() => { });
this.registerDisposable(this.wizardObject.generateScriptButton.onClick(async () => {
this.wizardObject.generateScriptButton.enabled = false;
this.scripter.generateDeploymentScript(this.model).then(() => {
this.wizardObject.generateScriptButton.enabled = true;
//TODO: Add error handling.
});
}));
}
protected onCancel(): void {
}
protected onOk(): void {
}
}

View File

@@ -5,29 +5,387 @@
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
import { ClusterProfile, PoolConfiguration, ClusterPoolType, SQLServerMasterConfiguration, ClusterResourceSummary } from '../../../interfaces';
const localize = nls.loadMessageBundle();
const LabelWidth = '200px';
const InputWidth = '300px';
export class ClusterProfilePage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private clusterProfiles: ClusterProfile[];
private poolList: azdata.FlexContainer;
private detailContainer: azdata.FlexContainer;
private clusterResourceView: azdata.GroupContainer;
private poolListMap = {};
private clusterResourceContainer: azdata.FlexContainer;
private clusterResourceLoadingComponent: azdata.LoadingComponent;
private clusterResource: ClusterResourceSummary;
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.'),
wizard);
}
public onEnter() {
public onEnter(): void {
this.updatePoolList();
this.clusterResourceLoadingComponent.loading = true;
this.wizard.model.getClusterResource().then((resource) => {
this.clusterResource = resource;
this.initializeClusterResourceView();
});
this.wizard.wizardObject.registerNavigationValidator(() => {
return true;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.component();
return view.initializeModel(form);
this.view = view;
let fetchProfilePromise = this.wizard.model.getProfiles().then(p => { this.clusterProfiles = p; });
return Promise.all([fetchProfilePromise]).then(() => {
this.wizard.model.profile = this.clusterProfiles[0];
this.clusterResourceView = this.view.modelBuilder.groupContainer().withLayout({
header: localize('bdc-create.TargetClusterOverview', 'Target cluster scale overview'),
collapsed: true,
collapsible: true
}).component();
this.clusterResourceContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.clusterResourceLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterResourceContainer).component();
this.clusterResourceView.addItem(this.clusterResourceLoadingComponent);
let profileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.clusterProfileLabel', 'Deployment profile') }).component();
let profileDropdown = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
values: this.clusterProfiles.map(profile => profile.name),
width: '300px'
}).component();
let dropdownRow = this.view.modelBuilder.flexContainer().withItems([profileLabel, profileDropdown], { CSSStyles: { 'margin-right': '30px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
let poolContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', width: '100%', height: '100%' }).component();
this.poolList = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '300px', height: '100%' }).component();
poolContainer.addItem(this.poolList, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-right-style': 'solid',
'border-right-width': '2px',
'border-color': 'lightgray'
}
});
this.detailContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '760px', height: '100%' }).component();
poolContainer.addItem(this.detailContainer, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-color': 'lightgray'
}
});
this.wizard.registerDisposable(profileDropdown.onValueChanged(() => {
let profiles = this.clusterProfiles.filter(p => profileDropdown.value === p.name);
if (profiles && profiles.length === 1) {
this.wizard.model.profile = profiles[0];
this.updatePoolList();
this.clearPoolDetail();
}
}));
this.initializePoolList();
let pageContainer = this.view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
height: '800px'
}).component();
pageContainer.addItem(this.clusterResourceView, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px',
'padding-bottom': '5px',
'padding-top': '5px'
}
});
pageContainer.addItem(dropdownRow, {
flex: '0 0 auto',
CSSStyles: { 'margin-bottom': '10px' }
});
pageContainer.addItem(poolContainer, {
flex: '1 1 auto',
CSSStyles: {
'display': 'flex'
}
});
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.withFormItems([{
title: '',
component: pageContainer
}], {
horizontal: false,
componentWidth: '100%'
}).component();
return view.initializeModel(form);
});
}
private initializeClusterResourceView(): void {
this.clusterResourceContainer.clearItems();
let text = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileText', 'Hardware profile') }).component();
let height = (this.clusterResource.hardwareLabels.length * 25) + 30;
let labelColumn: azdata.TableColumn = {
value: localize('bdc-create.HardwareLabelColumnName', 'Label'),
width: 100
};
let totalNodesColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalNodesColumnName', 'Nodes'),
width: 50
};
let totalCoresColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalCoresColumnName', 'Cores'),
width: 50
};
let totalMemoryColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalMemoryColumnName', 'Memory'),
width: 50
};
let totalDisksColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalDisksColumnName', 'Disks'),
width: 50
};
let table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
height: `${height}px`,
data: this.clusterResource.hardwareLabels.map(label => [label.name, label.totalNodes, label.totalCores, label.totalMemoryInGB, label.totalDisks]),
columns: [labelColumn, totalNodesColumn, totalCoresColumn, totalMemoryColumn, totalDisksColumn],
width: '300px'
}).component();
this.clusterResourceContainer.addItems([text, table]);
this.clusterResourceLoadingComponent.loading = false;
}
private initializePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let poolSummaryButton = this.view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: true }).component();
let container = this.view.modelBuilder.flexContainer().component();
this.wizard.registerDisposable(poolSummaryButton.onDidClick(() => {
this.clearPoolDetail();
let currentPool: PoolConfiguration;
switch (pool.type) {
case ClusterPoolType.SQL:
currentPool = this.wizard.model.profile.sqlServerMasterConfiguration;
break;
case ClusterPoolType.Compute:
currentPool = this.wizard.model.profile.computePoolConfiguration;
break;
case ClusterPoolType.Data:
currentPool = this.wizard.model.profile.dataPoolConfiguration;
break;
case ClusterPoolType.Storage:
currentPool = this.wizard.model.profile.storagePoolConfiguration;
break;
case ClusterPoolType.Spark:
currentPool = this.wizard.model.profile.sparkPoolConfiguration;
break;
default:
break;
}
if (currentPool) {
this.detailContainer.addItem(this.createPoolConfigurationPart(currentPool), { CSSStyles: { 'margin-left': '10px' } });
}
}));
let text = this.view.modelBuilder.text().component();
this.poolListMap[pool.type] = text;
text.width = '250px';
let chrevron = this.view.modelBuilder.text().withProperties({ value: '>' }).component();
chrevron.width = '30px';
container.addItem(text);
container.addItem(chrevron, {
CSSStyles: {
'font-size': '20px',
'line-height': '0px'
}
});
poolSummaryButton.addItem(container);
this.poolList.addItem(poolSummaryButton, {
CSSStyles: {
'border-bottom-style': 'solid',
'border-bottom-width': '1px',
'border-color': 'lightgray',
'cursor': 'pointer'
}
});
});
}
private createPoolConfigurationPart(configuration: PoolConfiguration): azdata.Component {
let container = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
switch (configuration.type) {
case ClusterPoolType.SQL:
this.createSQLConfigurationPart(container, configuration as SQLServerMasterConfiguration);
break;
default:
this.createDefaultPoolConfigurationPart(container, configuration);
break;
}
return container;
}
private createSQLConfigurationPart(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
this.createDefaultPoolConfigurationPart(container, configuration);
this.addFeatureSetRow(container, configuration);
}
private createDefaultPoolConfigurationPart(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
this.addPoolNameLabel(container, this.getPoolDisplayName(configuration.type));
this.addPoolDescriptionLabel(container, this.getPoolDescription(configuration.type));
this.addScaleRow(container, configuration);
this.addHardwareLabelRow(container, configuration);
}
private addPoolNameLabel(container: azdata.FlexContainer, text: string): void {
let poolNameLabel = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(poolNameLabel, {
flex: '0 0 auto', CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
});
}
private addPoolDescriptionLabel(container: azdata.FlexContainer, text: string): void {
let label = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(label, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px'
}
});
}
private addScaleRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.ScaleLabel', 'Scale') }).component();
label.width = LabelWidth;
let input = this.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
inputType: 'number',
value: configuration.scale.toString(),
min: 1,
max: configuration.maxScale
}).component();
this.wizard.registerDisposable(input.onTextChanged(() => {
configuration.scale = Number(input.value);
this.updatePoolList();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addHardwareLabelRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileLabel', 'Hardware profile label') }).component();
label.width = LabelWidth;
let optionalValues = this.clusterResource.hardwareLabels.map(label => label.name);
configuration.hardwareLabel = configuration.hardwareLabel ? configuration.hardwareLabel : optionalValues[0];
let input = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ value: configuration.hardwareLabel, values: optionalValues }).component();
this.wizard.registerDisposable(input.onValueChanged(() => {
configuration.hardwareLabel = input.value.toString();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addFeatureSetRow(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
const radioGroupName = 'featureset';
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.FeatureSetLabel', 'Feature set') }).component();
label.width = LabelWidth;
let engineOnlyOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineOnlyText', 'Engine only'), name: radioGroupName, checked: configuration.engineOnly }).component();
let engineWithFeaturesOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineWithFeaturesText', 'Engine with optional features'), name: radioGroupName, checked: !configuration.engineOnly }).component();
let optionContainer = this.view.modelBuilder.divContainer().component();
optionContainer.width = InputWidth;
optionContainer.addItems([engineOnlyOption, engineWithFeaturesOption]);
container.addItem(this.createRow([label, optionContainer]));
this.wizard.registerDisposable(engineOnlyOption.onDidClick(() => {
configuration.engineOnly = true;
}));
this.wizard.registerDisposable(engineWithFeaturesOption.onDidClick(() => {
configuration.engineOnly = false;
}));
}
private createRow(items: azdata.Component[]): azdata.FlexContainer {
return this.view.modelBuilder.flexContainer().withItems(items, {
CSSStyles: {
'margin-right': '5px'
}
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
}
private getPoolDisplayName(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDisplayName', 'SQL Server master');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDisplayName', 'Compute pool');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDisplayName', 'Data pool');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDisplayName', 'Storage pool');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDisplayName', 'Spark pool');
default:
throw 'unknown pool type';
}
}
private getPoolDescription(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDescription', 'The SQL Server instance provides an externally accessible TDS endpoint for the cluster');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDescription', 'TODO: Add description');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDescription', 'TODO: Add description');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDescription', 'TODO: Add description');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDescription', 'TODO: Add description');
default:
throw 'unknown pool type';
}
}
private updatePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let text = this.poolListMap[pool.type] as azdata.TextComponent;
if (text) {
text.value = localize({
key: 'bdc-create.poolLabelTemplate',
comment: ['{0} is the pool name, {1} is the scale number']
}, '{0} ({1})', this.getPoolDisplayName(pool.type), pool.scale);
}
});
}
private clearPoolDetail(): void {
this.detailContainer.clearItems();
}
}

View File

@@ -21,7 +21,7 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
private existingClusterControl: azdata.FlexContainer;
private clusterContextsLabel: azdata.TextComponent;
private errorLoadingClustersLabel: azdata.TextComponent;
private clusterContextContainer: azdata.DivContainer;
private clusterContextList: azdata.DivContainer;
private clusterContextLoadingComponent: azdata.LoadingComponent;
private configFileInput: azdata.InputBoxComponent;
private browseFileButton: azdata.ButtonComponent;
@@ -80,7 +80,9 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
private initExistingClusterControl(): void {
let self = this;
const labelWidth = '150px';
let configFileLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'Kube config file path') }).component();
configFileLabel.width = labelWidth;
this.configFileInput = this.view.modelBuilder.inputBox().withProperties({ width: '300px' }).component();
this.configFileInput.enabled = false;
this.browseFileButton = this.view.modelBuilder.button().withProperties({ label: localize('bdc-browseText', 'Browse'), width: '100px' }).component();
@@ -88,14 +90,21 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
.withLayout({ flexFlow: 'row', alignItems: 'baseline' })
.withItems([configFileLabel, this.configFileInput, this.browseFileButton], { CSSStyles: { 'margin-right': '10px' } }).component();
this.clusterContextsLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-clusterContextsLabelText', 'Cluster Contexts') }).component();
this.clusterContextsLabel.width = labelWidth;
this.errorLoadingClustersLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
this.clusterContextContainer = this.view.modelBuilder.divContainer().component();
this.clusterContextLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterContextContainer).component();
this.clusterContextList = this.view.modelBuilder.divContainer().component();
this.clusterContextLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterContextList).component();
this.existingClusterControl = this.view.modelBuilder.divContainer().component();
this.existingClusterControl.addItem(configFileContainer, { CSSStyles: { 'margin-top': '0px' } });
this.existingClusterControl.addItem(this.clusterContextLoadingComponent, { CSSStyles: { 'width': '400px', 'margin-top': '10px' } });
let clusterContextContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'start' }).component();
clusterContextContainer.addItem(this.clusterContextsLabel, { flex: '0 0 auto' });
clusterContextContainer.addItem(this.clusterContextLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'width': '400px', 'margin-left': '10px', 'margin-top': '10px' } });
this.browseFileButton.onDidClick(async () => {
this.existingClusterControl.addItem(configFileContainer, { CSSStyles: { 'margin-top': '0px' } });
this.existingClusterControl.addItem(clusterContextContainer, {
CSSStyles: { 'margin- top': '10px' }
});
this.wizard.registerDisposable(this.browseFileButton.onDidClick(async () => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
@@ -112,12 +121,12 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
if (!fileUris || fileUris.length === 0) {
return;
}
self.clusterContextContainer.clearItems();
self.clusterContextList.clearItems();
let fileUri = fileUris[0];
self.loadClusterContexts(fileUri.fsPath);
});
}));
}
private async loadClusterContexts(configPath: string): Promise<void> {
@@ -140,17 +149,15 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
self.wizard.wizardObject.message = null;
}
option.onDidClick(() => {
this.wizard.registerDisposable(option.onDidClick(() => {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
});
}));
return option;
});
self.clusterContextContainer.addItem(self.clusterContextsLabel);
self.clusterContextContainer.addItems(options);
self.clusterContextList.addItems(options);
} else {
self.clusterContextContainer.addItem(this.errorLoadingClustersLabel);
self.clusterContextList.addItem(this.errorLoadingClustersLabel);
}
this.clusterContextLoadingComponent.loading = false;
}

View File

@@ -35,7 +35,7 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
wizard);
this.installToolsButton = azdata.window.createButton(InstallToolsButtonText);
this.installToolsButton.hidden = true;
this.installToolsButton.onClick(async () => {
this.wizard.registerDisposable(this.installToolsButton.onClick(async () => {
this.wizard.wizardObject.message = null;
this.installToolsButton.label = InstallingButtonText;
this.installToolsButton.enabled = false;
@@ -53,14 +53,14 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
this.installToolsButton.label = InstallToolsButtonText;
this.updateRequiredToolStatus();
});
}));
this.wizard.addButton(this.installToolsButton);
this.refreshToolsButton = azdata.window.createButton(localize('bdc-create.RefreshToolsButtonText', 'Refresh Status'));
this.refreshToolsButton.hidden = true;
this.refreshToolsButton.onClick(() => {
this.wizard.registerDisposable(this.refreshToolsButton.onClick(() => {
this.updateRequiredToolStatus();
});
}));
this.wizard.addButton(this.refreshToolsButton);
}
@@ -115,7 +115,6 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
}
);
self.form = self.formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(self.form);
});
@@ -160,51 +159,61 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
private createCard(view: azdata.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): azdata.CardComponent {
let self = this;
let descriptions = targetClusterTypeInfo.enabled ? [] : [localize('bdc-create.ComingSoonText', '(Coming Soon)')];
let card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
cardType: azdata.CardType.VerticalButton,
iconPath: {
dark: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.dark),
light: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.light)
},
label: targetClusterTypeInfo.name
label: targetClusterTypeInfo.name,
descriptions: descriptions
}).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;
}
});
self.targetDescriptionText.value = targetClusterTypeInfo.description;
card.enabled = targetClusterTypeInfo.enabled;
if (self.form.items.length === 1) {
self.formBuilder.addFormItem({
title: localize('bdc-create.RequiredToolsText', 'Required tools'),
component: self.toolsLoadingWrapper
});
} else {
self.formBuilder.removeFormItem(self.targetDescriptionGroup);
}
self.targetDescriptionGroup = {
title: targetClusterTypeInfo.fullName,
component: self.targetDescriptionText
};
self.formBuilder.insertFormItem(self.targetDescriptionGroup, 1);
self.updateRequiredToolStatus();
} else {
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
card.selected = true;
}
}
});
self.wizard.registerDisposable(card.onCardSelectedChanged(() => {
self.onCardSelected(card, targetClusterTypeInfo);
}));
return card;
}
private onCardSelected(card: azdata.CardComponent, targetClusterTypeInfo: TargetClusterTypeInfo): void {
let self = this;
if (card.selected) {
self.wizard.wizardObject.message = null;
self.wizard.model.targetClusterType = targetClusterTypeInfo.type;
self.cards.forEach(c => {
if (c !== card) {
c.selected = false;
}
});
self.targetDescriptionText.value = targetClusterTypeInfo.description;
if (self.form.items.length === 1) {
self.formBuilder.addFormItem({
title: localize('bdc-create.RequiredToolsText', 'Required tools'),
component: self.toolsLoadingWrapper
});
} else {
self.formBuilder.removeFormItem(self.targetDescriptionGroup);
}
self.targetDescriptionGroup = {
title: targetClusterTypeInfo.fullName,
component: self.targetDescriptionText
};
self.formBuilder.insertFormItem(self.targetDescriptionGroup, 1);
self.updateRequiredToolStatus();
} else {
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
card.selected = true;
}
}
}
private updateRequiredToolStatus(): Thenable<void> {
this.isLoading = true;
this.installToolsButton.hidden = false;

View File

@@ -16,12 +16,32 @@ const PortInputWidth = '100px';
const RestoreDefaultValuesText = localize('bdc-create.RestoreDefaultValuesText', 'Restore Default Values');
export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
private acceptEulaCheckbox: azdata.CheckBoxComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.settingsPageTitle', 'Settings'),
localize('bdc-create.settingsPageDescription', 'Configure the settings required for deploying SQL Server big data cluster'),
wizard);
}
public onEnter(): void {
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
if (!this.acceptEulaCheckbox.checked) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.EulaNotAccepted', 'You need to accept the terms of services and privacy policy in order to proceed'),
level: azdata.window.MessageLevel.Error
};
} else {
this.wizard.wizardObject.message = null;
}
return this.acceptEulaCheckbox.checked;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let clusterPorts: ClusterPorts;
let containerRegistryInfo: ContainerRegistryInfo;
@@ -37,6 +57,14 @@ export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
let formBuilder = view.modelBuilder.formContainer();
// User settings
let clusterNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ClusterName', 'Cluster name'),
inputWidth: UserNameInputWidth,
isRequiredField: true
}, (input) => {
this.wizard.model.clusterName = input.value;
});
let adminUserNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.AdminUsernameText', 'Admin username'),
isRequiredField: true,
@@ -106,14 +134,14 @@ export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
label: RestoreDefaultValuesText,
width: 200
}).component();
restorePortSettingsButton.onDidClick(() => {
this.wizard.registerDisposable(restorePortSettingsButton.onDidClick(() => {
sqlPortInput.input.value = clusterPorts.sql;
knoxPortInput.input.value = clusterPorts.knox;
controllerPortInput.input.value = clusterPorts.controller;
proxyPortInput.input.value = clusterPorts.proxy;
grafanaPortInput.input.value = clusterPorts.grafana;
kibanaPortInput.input.value = clusterPorts.kibana;
});
}));
// Container Registry Settings
const registryUserNamePasswordHintText = localize('bdc-create.RegistryUserNamePasswordHintText', 'only required for private registries');
@@ -166,18 +194,18 @@ export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
label: RestoreDefaultValuesText,
width: 200
}).component();
restoreContainerSettingsButton.onDidClick(() => {
this.wizard.registerDisposable(restoreContainerSettingsButton.onDidClick(() => {
registryInput.input.value = containerRegistryInfo.registry;
repositoryInput.input.value = containerRegistryInfo.repository;
imageTagInput.input.value = containerRegistryInfo.imageTag;
});
}));
let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([adminUserNameInput.row, adminPasswordInput.row]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component();
let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([clusterNameInput.row, adminUserNameInput.row, adminPasswordInput.row]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component();
let containerSettingsGroup = view.modelBuilder.groupContainer().withItems([registryInput.row, repositoryInput.row, imageTagInput.row, registryUserNameInput.row, registryPasswordInput.row, restoreContainerSettingsButton]).withLayout({ header: localize('bdc-create.ContainerRegistrySettings', 'Container Registry Settings'), collapsible: true }).component();
let portSettingsGroup = view.modelBuilder.groupContainer().withItems([sqlPortInput.row, knoxPortInput.row, controllerPortInput.row, proxyPortInput.row, grafanaPortInput.row, kibanaPortInput.row, restorePortSettingsButton]).withLayout({ header: localize('bdc-create.PortSettings', 'Port Settings (Optional)'), collapsible: true, collapsed: true }).component();
let acceptEulaCheckbox = view.modelBuilder.checkBox().component();
acceptEulaCheckbox.checked = false;
this.acceptEulaCheckbox = view.modelBuilder.checkBox().component();
this.acceptEulaCheckbox.checked = false;
let eulaLink: azdata.LinkArea = {
text: localize('bdc-create.LicenseTerms', 'license terms'),
@@ -196,14 +224,13 @@ export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
links: [eulaLink, privacyPolicyLink]
}).component();
let eulaContainer = this.createRow(view, [acceptEulaCheckbox, checkboxText]);
let eulaContainer = this.createRow(view, [this.acceptEulaCheckbox, checkboxText]);
let form = formBuilder.withFormItems([
{
title: '',
component: eulaContainer
},
{
}, {
title: '',
component: basicSettingsGroup
}, {
@@ -234,9 +261,9 @@ export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
input.width = options.inputWidth;
text.width = '150px';
input.placeHolder = options.placeHolder;
input.onTextChanged(() => {
this.wizard.registerDisposable(input.onTextChanged(() => {
textChangedHandler(input);
});
}));
input.value = options.initialValue;
let row = this.createRow(view, [text, input]);
return {

View File

@@ -10,23 +10,95 @@ import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const LabelWidth = '250px';
export class SummaryPage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private targetTypeText: azdata.TextComponent;
private targetClusterContextText: azdata.TextComponent;
private clusterNameText: azdata.TextComponent;
private clusterAdminUsernameText: azdata.TextComponent;
private acceptEulaText: azdata.TextComponent;
private deploymentProfileText: azdata.TextComponent;
private sqlServerMasterScaleText: azdata.TextComponent;
private storagePoolScaleText: azdata.TextComponent;
private computePoolScaleText: azdata.TextComponent;
private dataPoolScaleText: azdata.TextComponent;
private sparkPoolScaleText: azdata.TextComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.summaryPageTitle', 'Summary'), '', wizard);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
let targetClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
let bdcClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.targetTypeText = this.view.modelBuilder.text().component();
this.targetClusterContextText = this.view.modelBuilder.text().component();
this.clusterNameText = this.view.modelBuilder.text().component();
this.clusterAdminUsernameText = this.view.modelBuilder.text().component();
this.acceptEulaText = this.view.modelBuilder.text().component();
this.deploymentProfileText = this.view.modelBuilder.text().component();
this.sqlServerMasterScaleText = this.view.modelBuilder.text().component();
this.storagePoolScaleText = this.view.modelBuilder.text().component();
this.computePoolScaleText = this.view.modelBuilder.text().component();
this.dataPoolScaleText = this.view.modelBuilder.text().component();
this.sparkPoolScaleText = this.view.modelBuilder.text().component();
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.TargetClusterTypeText', 'Cluster type'), this.targetTypeText));
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterContextText', 'Cluster context'), this.targetClusterContextText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterNameText', 'Cluster name'), this.clusterNameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterAdminUsernameText', 'Cluster Admin username'), this.clusterAdminUsernameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.AcceptEulaText', 'Accept license agreement'), this.acceptEulaText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DeploymentProfileText', 'Deployment profile'), this.deploymentProfileText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SqlServerMasterScaleText', 'SQL Server master scale'), this.sqlServerMasterScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ComputePoolScaleText', 'Compute pool scale'), this.computePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DataPoolScaleText', 'Data pool scale'), this.dataPoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.StoragePoolScaleText', 'Storage pool scale'), this.storagePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SparkPoolScaleText', 'Spark pool scale'), this.sparkPoolScaleText));
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.component();
let form = formBuilder.withFormItems([{
title: localize('bdc-create.TargetClusterGroupTitle', 'TARGET CLUSTER'),
component: targetClusterInfoGroup
}, {
title: localize('bdc-create.BigDataClusterGroupTitle', 'SQL SERVER BIG DATA CLUSTER'),
component: bdcClusterInfoGroup
}]).component();
return view.initializeModel(form);
}
public onEnter(): void {
this.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
let selectedClusterType = clusterTypes.filter(clusterType => clusterType.type === this.wizard.model.targetClusterType)[0];
this.targetTypeText.value = selectedClusterType.fullName;
this.targetClusterContextText.value = this.wizard.model.selectedCluster.contextName;
this.clusterNameText.value = this.wizard.model.clusterName;
this.clusterAdminUsernameText.value = this.wizard.model.adminUserName;
this.acceptEulaText.value = localize('bdc-create.YesText', 'Yes');
this.deploymentProfileText.value = this.wizard.model.profile.name;
this.sqlServerMasterScaleText.value = this.wizard.model.profile.sqlServerMasterConfiguration.scale.toString();
this.computePoolScaleText.value = this.wizard.model.profile.computePoolConfiguration.scale.toString();
this.dataPoolScaleText.value = this.wizard.model.profile.dataPoolConfiguration.scale.toString();
this.storagePoolScaleText.value = this.wizard.model.profile.storagePoolConfiguration.scale.toString();
this.sparkPoolScaleText.value = this.wizard.model.profile.sparkPoolConfiguration.scale.toString();
});
this.wizard.wizardObject.generateScriptButton.hidden = false;
}
public onLeave(): void {
this.wizard.wizardObject.generateScriptButton.hidden = true;
}
private createRow(label: string, textComponent: azdata.TextComponent): azdata.FlexContainer {
let row = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'baseline' }).component();
let labelComponent = this.view.modelBuilder.text().withProperties({ value: label }).component();
labelComponent.width = LabelWidth;
textComponent.width = LabelWidth;
row.addItems([labelComponent, textComponent]);
return row;
}
}

View File

@@ -5,15 +5,17 @@
'use strict';
import * as azdata from 'azdata';
import { ExtensionContext } from 'vscode';
import { ExtensionContext, Disposable } from 'vscode';
import { WizardPageBase } from './wizardPageBase';
export abstract class WizardBase<T,W> {
export abstract class WizardBase<T, W> {
public wizardObject: azdata.window.Wizard;
private customButtons: azdata.window.Button[];
private pages: WizardPageBase<W>[];
private toDispose: Disposable[] = [];
constructor(public model: T, public context: ExtensionContext, private title: string) {
this.customButtons = [];
}
@@ -22,17 +24,33 @@ export abstract class WizardBase<T,W> {
this.wizardObject = azdata.window.createWizard(this.title);
this.initialize();
this.wizardObject.customButtons = this.customButtons;
this.wizardObject.onPageChanged((e) => {
this.toDispose.push(this.wizardObject.onPageChanged((e) => {
let previousPage = this.pages[e.lastPage];
let newPage = this.pages[e.newPage];
previousPage.onLeave();
newPage.onEnter();
}));
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
this.onOk();
this.dispose();
}));
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
this.onCancel();
this.dispose();
}));
return this.wizardObject.open().then(() => {
if (this.pages && this.pages.length > 0) {
this.pages[0].onEnter();
}
});
return this.wizardObject.open();
}
protected abstract initialize(): void;
protected abstract onOk(): void;
protected abstract onCancel(): void;
public addButton(button: azdata.window.Button) {
this.customButtons.push(button);
@@ -42,4 +60,17 @@ export abstract class WizardBase<T,W> {
this.wizardObject.pages = pages.map(p => p.pageObject);
this.pages = pages;
}
private dispose() {
this.toDispose.forEach((disposable: Disposable) => {
try {
disposable.dispose();
}
catch{ }
});
}
public registerDisposable(disposable: Disposable): void {
this.toDispose.push(disposable);
}
}