mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -50,6 +50,7 @@ export interface ContainerRegistryInfo {
|
||||
}
|
||||
|
||||
export interface TargetClusterTypeInfo {
|
||||
enabled: boolean;
|
||||
type: TargetClusterType;
|
||||
name: string;
|
||||
fullName: string;
|
||||
@@ -81,3 +82,43 @@ export enum ClusterType {
|
||||
Kubernetes,
|
||||
Other
|
||||
}
|
||||
|
||||
export interface ClusterProfile {
|
||||
name: string;
|
||||
sqlServerMasterConfiguration: SQLServerMasterConfiguration;
|
||||
computePoolConfiguration: PoolConfiguration;
|
||||
dataPoolConfiguration: PoolConfiguration;
|
||||
storagePoolConfiguration: PoolConfiguration;
|
||||
sparkPoolConfiguration: PoolConfiguration;
|
||||
}
|
||||
|
||||
export interface PoolConfiguration {
|
||||
type: ClusterPoolType;
|
||||
scale: number;
|
||||
maxScale?: number;
|
||||
hardwareLabel?: string;
|
||||
}
|
||||
|
||||
export interface SQLServerMasterConfiguration extends PoolConfiguration {
|
||||
engineOnly: boolean;
|
||||
}
|
||||
|
||||
export enum ClusterPoolType {
|
||||
SQL,
|
||||
Compute,
|
||||
Data,
|
||||
Storage,
|
||||
Spark
|
||||
}
|
||||
|
||||
export interface ClusterResourceSummary {
|
||||
hardwareLabels: HardwareLabel[];
|
||||
}
|
||||
|
||||
export interface HardwareLabel {
|
||||
name: string;
|
||||
totalNodes: number;
|
||||
totalCores: number;
|
||||
totalMemoryInGB: number;
|
||||
totalDisks: number;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
9
src/sql/azdata.proposed.d.ts
vendored
9
src/sql/azdata.proposed.d.ts
vendored
@@ -2760,6 +2760,10 @@ declare module 'azdata' {
|
||||
}
|
||||
|
||||
export interface DivContainer extends Container<DivLayout, DivItemLayout>, DivContainerProperties {
|
||||
/**
|
||||
* An event called when the div is clicked
|
||||
*/
|
||||
onDidClick: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface FlexContainer extends Container<FlexLayout, FlexItemLayout> {
|
||||
@@ -3023,6 +3027,11 @@ declare module 'azdata' {
|
||||
* This is used when its child component is webview
|
||||
*/
|
||||
yOffsetChange?: number;
|
||||
|
||||
/**
|
||||
* Indicates whether the element is clickable
|
||||
*/
|
||||
clickable?: boolean;
|
||||
}
|
||||
|
||||
export interface CardComponent extends Component, CardProperties {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div *ngIf="label" [class]="getClass()" (click)="onCardClick()" (mouseover)="onCardHoverChanged($event)" (mouseout)="onCardHoverChanged($event)"
|
||||
tabIndex="0">
|
||||
<div *ngIf="label" [class]="getClass()" (click)="onCardClick()" (mouseover)="onCardHoverChanged($event)"
|
||||
(mouseout)="onCardHoverChanged($event)" tabIndex="0">
|
||||
<ng-container *ngIf="isVerticalButton || isDetailsCard">
|
||||
<span *ngIf="hasStatus" class="card-status">
|
||||
<div class="status-content" [style.backgroundColor]="statusColor"></div>
|
||||
@@ -13,6 +13,9 @@
|
||||
<div [class]="iconClass" [style.maxWidth]="iconWidth" [style.maxHeight]="iconHeight"></div>
|
||||
</div>
|
||||
<h4 class="card-label">{{label}}</h4>
|
||||
<div *ngFor="let desc of descriptions">
|
||||
<div class="list-item-description">{{desc}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -25,7 +28,8 @@
|
||||
<tr *ngFor="let action of actions">
|
||||
<td class="table-row">{{action.label}}</td>
|
||||
<td *ngIf="action.actionTitle" class="table-row">
|
||||
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
|
||||
<a class="pointer prominent"
|
||||
(click)="onDidActionClick(action)">{{action.actionTitle}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -103,7 +103,7 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
|
||||
}
|
||||
|
||||
private get selectable(): boolean {
|
||||
return this.cardType === 'VerticalButton' || this.cardType === 'ListItem';
|
||||
return this.enabled && (this.cardType === 'VerticalButton' || this.cardType === 'ListItem');
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList,
|
||||
} from '@angular/core';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
@@ -17,6 +17,8 @@ import { ContainerBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
|
||||
import types = require('vs/base/common/types');
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
class DivItem {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: azdata.DivItemLayout) { }
|
||||
@@ -24,7 +26,7 @@ class DivItem {
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width">
|
||||
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" (click)="onClick()" (keyup)="onKey($event)" [tabIndex]="tabIndex">
|
||||
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
@@ -76,17 +78,24 @@ export default class DivContainer extends ContainerBase<azdata.DivItemLayout> im
|
||||
private updateOverflowY() {
|
||||
this._overflowY = this.overflowY;
|
||||
if (this._overflowY) {
|
||||
let element = <HTMLElement> this.divContainer.nativeElement;
|
||||
let element = <HTMLElement>this.divContainer.nativeElement;
|
||||
element.style.overflowY = this._overflowY;
|
||||
}
|
||||
}
|
||||
|
||||
private updateScroll() {
|
||||
let element = <HTMLElement> this.divContainer.nativeElement;
|
||||
let element = <HTMLElement>this.divContainer.nativeElement;
|
||||
element.scrollTop = element.scrollTop - this.yOffsetChange;
|
||||
element.dispatchEvent(new Event('scroll'));
|
||||
}
|
||||
|
||||
private onClick() {
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: undefined
|
||||
});
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get height(): string {
|
||||
return this._height;
|
||||
@@ -111,6 +120,22 @@ export default class DivContainer extends ContainerBase<azdata.DivItemLayout> im
|
||||
this.setPropertyFromUI<azdata.DivContainerProperties, any>((properties, newValue) => { properties.yOffsetChange = newValue; }, newValue);
|
||||
}
|
||||
|
||||
public get clickable(): boolean {
|
||||
return this.getPropertyOrDefault<azdata.DivContainerProperties, boolean>((props) => props.clickable, false);
|
||||
}
|
||||
|
||||
public get tabIndex(): number {
|
||||
return this.clickable ? 0 : -1;
|
||||
}
|
||||
|
||||
private onKey(e: KeyboardEvent) {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
this.onClick();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private getItemOrder(item: DivItem): number {
|
||||
return item.config ? item.config.order : 0;
|
||||
}
|
||||
|
||||
@@ -1224,6 +1224,12 @@ class FileBrowserTreeComponentWrapper extends ComponentWrapper implements azdata
|
||||
}
|
||||
|
||||
class DivContainerWrapper extends ComponentWrapper implements azdata.DivContainer {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(proxy, handle, type, id);
|
||||
this.properties = {};
|
||||
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
|
||||
}
|
||||
|
||||
public get overflowY(): string {
|
||||
return this.properties['overflowY'];
|
||||
}
|
||||
@@ -1239,6 +1245,11 @@ class DivContainerWrapper extends ComponentWrapper implements azdata.DivContaine
|
||||
public set yOffsetChange(value: number) {
|
||||
this.setProperty('yOffsetChange', value);
|
||||
}
|
||||
|
||||
public get onDidClick(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeComponentWrapper<T> extends ComponentWrapper implements azdata.TreeComponent<T> {
|
||||
|
||||
@@ -832,11 +832,11 @@ export function createApiFactory(
|
||||
},
|
||||
openDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
console.warn('the method sqlops.window.modelviewdialog.openDialog has been deprecated, replace it with azdata.window.openDialog');
|
||||
return extHostModelViewDialog.openDialog(dialog);
|
||||
return extHostModelViewDialog.openDialog(dialog as azdata.window.Dialog);
|
||||
},
|
||||
closeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
console.warn('the method sqlops.window.modelviewdialog.closeDialog has been deprecated, replace it with azdata.window.closeDialog');
|
||||
return extHostModelViewDialog.closeDialog(dialog);
|
||||
return extHostModelViewDialog.closeDialog(dialog as azdata.window.Dialog);
|
||||
},
|
||||
createWizardPage(title: string): sqlops.window.modelviewdialog.WizardPage {
|
||||
console.warn('the method sqlops.window.modelviewdialog.createWizardPage has been deprecated, replace it with azdata.window.createWizardPage');
|
||||
@@ -868,10 +868,10 @@ export function createApiFactory(
|
||||
return extHostModelViewDialog.createButton(label);
|
||||
},
|
||||
openDialog(dialog: sqlops.window.Dialog) {
|
||||
return extHostModelViewDialog.openDialog(dialog);
|
||||
return extHostModelViewDialog.openDialog(dialog as azdata.window.Dialog);
|
||||
},
|
||||
closeDialog(dialog: sqlops.window.Dialog) {
|
||||
return extHostModelViewDialog.closeDialog(dialog);
|
||||
return extHostModelViewDialog.closeDialog(dialog as azdata.window.Dialog);
|
||||
},
|
||||
createWizardPage(title: string): sqlops.window.WizardPage {
|
||||
return extHostModelViewDialog.createWizardPage(title);
|
||||
|
||||
Reference in New Issue
Block a user