mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
wizard for deploying bdc (#7183)
* wip * wip2 * wip eod 820 * wip 822 * text component improvements and misc changes * aria-label * targetClusterPage wip * target cluster page * target cluster page * wip 827 * wip deployment profile page * profile page * service settings page * wip 0903 * 0909 wip * 0910 * 0911 * sql instance and working directory * notebooks * docker version on windows * EULA env var * 917 updates * address comments * use async file access * fix the summary page display issue for ad auth * add save json file buttons * use promise for private methds * review feedbacks * refactor * pass json to notebooks * fix no tool scenario * bypass tool check if installed * update hint text * update notebooks * workaround azdata first time use * comments * accept eula and some text update * fix the error in package.json * promise instead of thenable * comments * fix typo
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const DeploymentProfile_VariableName = 'AZDATA_NB_VAR_BDC_DEPLOYMENT_PROFILE';
|
||||
export const ClusterName_VariableName = 'AZDATA_NB_VAR_BDC_CLUSTER_NAME';
|
||||
export const AdminUserName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_USERNAME';
|
||||
export const AdminPassword_VariableName = 'AZDATA_NB_VAR_BDC_ADMIN_PASSWORD';
|
||||
export const AuthenticationMode_VariableName = 'AZDATA_NB_VAR_BDC_AUTHENTICATION_MODE';
|
||||
export const DistinguishedName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DN';
|
||||
export const AdminPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_ADMIN_PRINCIPALS';
|
||||
export const UserPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_USER_PRINCIPALS';
|
||||
export const UpstreamIPAddresses_VariableName = 'AZDATA_NB_VAR_BDC_AD_UPSTREAM_IPADDRESSES';
|
||||
export const DnsName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DNS_NAME';
|
||||
export const Realm_VariableName = 'AZDATA_NB_VAR_BDC_AD_REALM';
|
||||
export const AppOwnerPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_OWNER_PRINCIPALS';
|
||||
export const AppReaderPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_READER_PRINCIPALS';
|
||||
export const SubscriptionId_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_SUBSCRIPTION';
|
||||
export const ResourceGroup_VariableName = 'AZDATA_NB_VAR_BDC_RESOURCEGROUP_NAME';
|
||||
export const Region_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_REGION';
|
||||
export const AksName_VariableName = 'AZDATA_NB_VAR_BDC_AKS_NAME';
|
||||
export const VMSize_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_VM_SIZE';
|
||||
export const VMCount_VariableName = 'AZDATA_NB_VAR_BDC_VM_COUNT';
|
||||
export const KubeConfigPath_VariableName = 'AZDATA_NB_VAR_BDC_KUBECONFIG_PATH';
|
||||
export const ClusterContext_VariableName = 'AZDATA_NB_VAR_BDC_CLUSTER_CONTEXT';
|
||||
export const SQLServerScale_VariableName = 'AZDATA_NB_VAR_BDC_SQLSERVER_SCALE';
|
||||
export const HDFSPoolScale_VariableName = 'AZDATA_NB_VAR_BDC_HDFSPOOL_SCALE';
|
||||
export const HDFSNameNodeScale_VariableName = 'AZDATA_NB_VAR_BDC_NAMENODE_SCALE';
|
||||
export const ZooKeeperScale_VariableName = 'AZDATA_NB_VAR_BDC_ZOOKEEPER_SCALE';
|
||||
export const SparkHeadScale_VariableName = 'AZDATA_NB_VAR_BDC_SPARKHEAD_SCALE';
|
||||
export const IncludeSpark_VariableName = 'AZDATA_NB_VAR_BDC_INCLUDESPARK';
|
||||
export const ComputePoolScale_VariableName = 'AZDATA_NB_VAR_BDC_COMPUTEPOOL_SCALE';
|
||||
export const DataPoolScale_VariableName = 'AZDATA_NB_VAR_BDC_DATAPOOL_SCALE';
|
||||
export const SparkPoolScale_VariableName = 'AZDATA_NB_VAR_BDC_SPARKPOOL_SCALE';
|
||||
export const ControllerDataStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_DATA_STORAGE_CLASS';
|
||||
export const ControllerDataStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_DATA_STORAGE_SIZE';
|
||||
export const ControllerLogsStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_LOGS_STORAGE_CLASS';
|
||||
export const ControllerLogsStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_LOGS_STORAGE_SIZE';
|
||||
export const DataPoolDataStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_DATA_DATA_STORAGE_CLASS';
|
||||
export const DataPoolDataStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_DATA_DATA_STORAGE_SIZE';
|
||||
export const DataPoolLogsStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_DATA_LOGS_STORAGE_CLASS';
|
||||
export const DataPoolLogsStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_DATA_LOGS_STORAGE_SIZE';
|
||||
export const HDFSDataStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_HDFS_DATA_STORAGE_CLASS';
|
||||
export const HDFSDataStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_HDFS_DATA_STORAGE_SIZE';
|
||||
export const HDFSLogsStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_HDFS_LOGS_STORAGE_CLASS';
|
||||
export const HDFSLogsStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_HDFS_LOGS_STORAGE_SIZE';
|
||||
export const SQLServerDataStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_SQL_DATA_STORAGE_CLASS';
|
||||
export const SQLServerDataStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_SQL_DATA_STORAGE_SIZE';
|
||||
export const SQLServerLogsStorageClassName_VariableName = 'AZDATA_NB_VAR_BDC_SQL_LOGS_STORAGE_CLASS';
|
||||
export const SQLServerLogsStorageSize_VariableName = 'AZDATA_NB_VAR_BDC_SQL_LOGS_STORAGE_SIZE';
|
||||
export const ControllerDNSName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_DNS';
|
||||
export const ControllerPort_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_PORT';
|
||||
export const SQLServerDNSName_VariableName = 'AZDATA_NB_VAR_BDC_SQL_DNS';
|
||||
export const SQLServerPort_VariableName = 'AZDATA_NB_VAR_BDC_SQL_PORT';
|
||||
export const GatewayDNSName_VariableName = 'AZDATA_NB_VAR_BDC_GATEWAY_DNS';
|
||||
export const GateWayPort_VariableName = 'AZDATA_NB_VAR_BDC_GATEWAY_PORT';
|
||||
export const ReadableSecondaryDNSName_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_DNS';
|
||||
export const ReadableSecondaryPort_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_PORT';
|
||||
export const EnableHADR_VariableName = 'AZDATA_NB_VAR_BDC_ENABLE_HADR';
|
||||
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { WizardInfo, BdcDeploymentType } from '../../interfaces';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import { AzureSettingsPage } from './pages/azureSettingsPage';
|
||||
import { ClusterSettingsPage } from './pages/clusterSettingsPage';
|
||||
import { ServiceSettingsPage } from './pages/serviceSettingsPage';
|
||||
import { TargetClusterContextPage } from './pages/targetClusterPage';
|
||||
import { IKubeService } from '../../services/kubeService';
|
||||
import { IAzdataService } from '../../services/azdataService';
|
||||
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
|
||||
import { INotebookService } from '../../services/notebookService';
|
||||
import { DeployClusterWizardModel } from './deployClusterWizardModel';
|
||||
import * as VariableNames from './constants';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> {
|
||||
|
||||
public get kubeService(): IKubeService {
|
||||
return this._kubeService;
|
||||
}
|
||||
|
||||
public get azdataService(): IAzdataService {
|
||||
return this._azdataService;
|
||||
}
|
||||
|
||||
public get notebookService(): INotebookService {
|
||||
return this._notebookService;
|
||||
}
|
||||
|
||||
constructor(private wizardInfo: WizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService) {
|
||||
super(DeployClusterWizard.getTitle(wizardInfo.type), new DeployClusterWizardModel(wizardInfo.type));
|
||||
}
|
||||
|
||||
public get deploymentType(): BdcDeploymentType {
|
||||
return this.wizardInfo.type;
|
||||
}
|
||||
|
||||
protected initialize(): void {
|
||||
this.setPages(this.getPages());
|
||||
this.wizardObject.generateScriptButton.hidden = true;
|
||||
this.wizardObject.doneButton.label = localize('deployCluster.openNotebook', 'Open Notebook');
|
||||
}
|
||||
|
||||
protected onCancel(): void {
|
||||
}
|
||||
|
||||
protected onOk(): void {
|
||||
process.env[VariableNames.AdminPassword_VariableName] = this.model.getStringValue(VariableNames.AdminPassword_VariableName);
|
||||
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => {
|
||||
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
|
||||
editBuilder.insertCell({
|
||||
cell_type: 'code',
|
||||
source: this.model.getCodeCellContentForNotebook()
|
||||
}, 7);
|
||||
});
|
||||
}, (error) => {
|
||||
vscode.window.showErrorMessage(error);
|
||||
});
|
||||
}
|
||||
|
||||
private getPages(): WizardPageBase<DeployClusterWizard>[] {
|
||||
const pages: WizardPageBase<DeployClusterWizard>[] = [];
|
||||
switch (this.deploymentType) {
|
||||
case BdcDeploymentType.NewAKS:
|
||||
pages.push(
|
||||
new DeploymentProfilePage(this),
|
||||
new AzureSettingsPage(this),
|
||||
new ClusterSettingsPage(this),
|
||||
new ServiceSettingsPage(this),
|
||||
new SummaryPage(this));
|
||||
break;
|
||||
case BdcDeploymentType.ExistingAKS:
|
||||
pages.push(
|
||||
new DeploymentProfilePage(this),
|
||||
new TargetClusterContextPage(this),
|
||||
new ClusterSettingsPage(this),
|
||||
new ServiceSettingsPage(this),
|
||||
new SummaryPage(this));
|
||||
break;
|
||||
case BdcDeploymentType.ExistingKubeAdm:
|
||||
pages.push(
|
||||
new DeploymentProfilePage(this),
|
||||
new TargetClusterContextPage(this),
|
||||
new ClusterSettingsPage(this),
|
||||
new ServiceSettingsPage(this),
|
||||
new SummaryPage(this));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown deployment type: ${this.deploymentType}`);
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
static getTitle(type: BdcDeploymentType): string {
|
||||
switch (type) {
|
||||
case BdcDeploymentType.NewAKS:
|
||||
return localize('deployCluster.NewAKSWizardTitle', "Deploy SQL Server 2019 Big Data Cluster on a new AKS cluster");
|
||||
case BdcDeploymentType.ExistingAKS:
|
||||
return localize('deployCluster.ExistingAKSWizardTitle', "Deploy SQL Server 2019 Big Data Cluster on an existing AKS cluster");
|
||||
case BdcDeploymentType.ExistingKubeAdm:
|
||||
return localize('deployCluster.ExistingKubeAdm', "Deploy SQL Server 2019 Big Data Cluster on an existing kubeadm cluster");
|
||||
default:
|
||||
throw new Error(`Unknown deployment type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Model } from '../model';
|
||||
import * as VariableNames from './constants';
|
||||
import { BigDataClusterDeploymentProfile, DataResource, SqlServerMasterResource, HdfsResource } from '../../services/bigDataClusterDeploymentProfile';
|
||||
import { BdcDeploymentType } from '../../interfaces';
|
||||
import { EOL } from 'os';
|
||||
|
||||
export class DeployClusterWizardModel extends Model {
|
||||
constructor(public deploymentTarget: BdcDeploymentType) {
|
||||
super();
|
||||
}
|
||||
public adAuthSupported: boolean = false;
|
||||
|
||||
public get hadrEnabled(): boolean {
|
||||
return this.getBooleanValue(VariableNames.EnableHADR_VariableName);
|
||||
}
|
||||
|
||||
public set hadrEnabled(value: boolean) {
|
||||
this.setPropertyValue(VariableNames.EnableHADR_VariableName, value);
|
||||
}
|
||||
|
||||
public get authenticationMode(): string | undefined {
|
||||
return this.getStringValue(VariableNames.AuthenticationMode_VariableName);
|
||||
}
|
||||
|
||||
public set authenticationMode(value: string | undefined) {
|
||||
this.setPropertyValue(VariableNames.AuthenticationMode_VariableName, value);
|
||||
}
|
||||
|
||||
public getStorageSettingValue(propertyName: string, defaultValuePropertyName: string): string | undefined {
|
||||
const value = this.getStringValue(propertyName);
|
||||
return (value === undefined || value === '') ? this.getStringValue(defaultValuePropertyName) : value;
|
||||
}
|
||||
|
||||
private setStorageSettingValue(propertyName: string, defaultValuePropertyName: string): void {
|
||||
const value = this.getStringValue(propertyName);
|
||||
if (value === undefined || value === '') {
|
||||
this.setPropertyValue(propertyName, this.getStringValue(defaultValuePropertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private setStorageSettingValues(): void {
|
||||
this.setStorageSettingValue(VariableNames.DataPoolDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.DataPoolDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.DataPoolLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.DataPoolLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName);
|
||||
|
||||
this.setStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.HDFSLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName);
|
||||
|
||||
this.setStorageSettingValue(VariableNames.SQLServerDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.SQLServerDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.SQLServerLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName);
|
||||
this.setStorageSettingValue(VariableNames.SQLServerLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName);
|
||||
}
|
||||
|
||||
public setEnvironmentVariables(): void {
|
||||
this.setStorageSettingValues();
|
||||
}
|
||||
|
||||
public selectedProfile: BigDataClusterDeploymentProfile | undefined;
|
||||
|
||||
public createTargetProfile(): BigDataClusterDeploymentProfile {
|
||||
// create a copy of the source files to avoid changing the source profile values
|
||||
const sourceBdcJson = Object.assign({}, this.selectedProfile!.bdcConfig);
|
||||
const sourceControlJson = Object.assign({}, this.selectedProfile!.controlConfig);
|
||||
const targetDeploymentProfile = new BigDataClusterDeploymentProfile('', sourceBdcJson, sourceControlJson);
|
||||
// cluster name
|
||||
targetDeploymentProfile.clusterName = this.getStringValue(VariableNames.ClusterName_VariableName)!;
|
||||
// storage settings
|
||||
targetDeploymentProfile.controllerDataStorageClass = this.getStringValue(VariableNames.ControllerDataStorageClassName_VariableName)!;
|
||||
targetDeploymentProfile.controllerDataStorageSize = this.getIntegerValue(VariableNames.ControllerDataStorageSize_VariableName)!;
|
||||
targetDeploymentProfile.controllerLogsStorageClass = this.getStringValue(VariableNames.ControllerLogsStorageClassName_VariableName)!;
|
||||
targetDeploymentProfile.controllerLogsStorageSize = this.getIntegerValue(VariableNames.ControllerLogsStorageSize_VariableName)!;
|
||||
targetDeploymentProfile.setResourceStorage(DataResource,
|
||||
this.getStorageSettingValue(VariableNames.DataPoolDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.DataPoolDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName)!),
|
||||
this.getStorageSettingValue(VariableNames.DataPoolLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.DataPoolLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)!)
|
||||
);
|
||||
targetDeploymentProfile.setResourceStorage(SqlServerMasterResource,
|
||||
this.getStorageSettingValue(VariableNames.SQLServerDNSName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.SQLServerDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName)!),
|
||||
this.getStorageSettingValue(VariableNames.SQLServerLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.SQLServerLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)!)
|
||||
);
|
||||
targetDeploymentProfile.setResourceStorage(HdfsResource,
|
||||
this.getStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName)!),
|
||||
this.getStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName)!,
|
||||
Number.parseInt(this.getStorageSettingValue(VariableNames.HDFSLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)!)
|
||||
);
|
||||
|
||||
// scale settings
|
||||
targetDeploymentProfile.dataReplicas = this.getIntegerValue(VariableNames.DataPoolScale_VariableName);
|
||||
targetDeploymentProfile.computeReplicas = this.getIntegerValue(VariableNames.ComputePoolScale_VariableName);
|
||||
targetDeploymentProfile.hdfsReplicas = this.getIntegerValue(VariableNames.HDFSPoolScale_VariableName);
|
||||
targetDeploymentProfile.sqlServerReplicas = this.getIntegerValue(VariableNames.SQLServerScale_VariableName);
|
||||
targetDeploymentProfile.hdfsNameNodeReplicas = this.getIntegerValue(VariableNames.HDFSNameNodeScale_VariableName);
|
||||
targetDeploymentProfile.sparkHeadReplicas = this.getIntegerValue(VariableNames.SparkHeadScale_VariableName);
|
||||
targetDeploymentProfile.zooKeeperReplicas = this.getIntegerValue(VariableNames.ZooKeeperScale_VariableName);
|
||||
const sparkScale = this.getIntegerValue(VariableNames.SparkPoolScale_VariableName);
|
||||
if (sparkScale > 0) {
|
||||
targetDeploymentProfile.addSparkResource(sparkScale);
|
||||
}
|
||||
|
||||
targetDeploymentProfile.includeSpark = this.getBooleanValue(VariableNames.IncludeSpark_VariableName);
|
||||
targetDeploymentProfile.hadrEnabled = this.getBooleanValue(VariableNames.EnableHADR_VariableName);
|
||||
|
||||
// port settings
|
||||
targetDeploymentProfile.gatewayPort = this.getIntegerValue(VariableNames.GateWayPort_VariableName);
|
||||
targetDeploymentProfile.sqlServerPort = this.getIntegerValue(VariableNames.SQLServerPort_VariableName);
|
||||
targetDeploymentProfile.controllerPort = this.getIntegerValue(VariableNames.ControllerPort_VariableName);
|
||||
targetDeploymentProfile.sqlServerReadableSecondaryPort = this.getIntegerValue(VariableNames.ReadableSecondaryPort_VariableName);
|
||||
|
||||
return targetDeploymentProfile;
|
||||
}
|
||||
|
||||
public getCodeCellContentForNotebook(): string {
|
||||
const profile = this.createTargetProfile();
|
||||
const statements: string[] = [];
|
||||
if (this.deploymentTarget === BdcDeploymentType.NewAKS) {
|
||||
statements.push(`azure_subscription_id = '${this.getStringValue(VariableNames.SubscriptionId_VariableName, '')}'`);
|
||||
statements.push(`azure_region = '${this.getStringValue(VariableNames.Region_VariableName)}'`);
|
||||
statements.push(`azure_resource_group = '${this.getStringValue(VariableNames.ResourceGroup_VariableName)}'`);
|
||||
statements.push(`azure_vm_size = '${this.getStringValue(VariableNames.VMSize_VariableName)}'`);
|
||||
statements.push(`azure_vm_count = '${this.getStringValue(VariableNames.VMCount_VariableName)}'`);
|
||||
statements.push(`aks_cluster_name = '${this.getStringValue(VariableNames.AksName_VariableName)}'`);
|
||||
} else if (this.deploymentTarget === BdcDeploymentType.ExistingAKS || this.deploymentTarget === BdcDeploymentType.ExistingKubeAdm) {
|
||||
statements.push(`mssql_kube_config_path = '${this.getStringValue(VariableNames.KubeConfigPath_VariableName)}'`);
|
||||
statements.push(`mssql_cluster_context = '${this.getStringValue(VariableNames.ClusterContext_VariableName)}'`);
|
||||
statements.push('os.environ["KUBECONFIG"] = mssql_kube_config_path');
|
||||
}
|
||||
statements.push(`mssql_cluster_name = '${this.getStringValue(VariableNames.ClusterName_VariableName)}'`);
|
||||
statements.push(`mssql_controller_username = '${this.getStringValue(VariableNames.AdminUserName_VariableName)}'`);
|
||||
statements.push(`bdc_json = '${profile.getBdcJson(false)}'`);
|
||||
statements.push(`control_json = '${profile.getControlJson(false)}'`);
|
||||
statements.push(`print('Variables have been set successfully.')`);
|
||||
return statements.join(EOL);
|
||||
}
|
||||
}
|
||||
|
||||
export enum AuthenticationMode {
|
||||
ActiveDirectory = 'ad',
|
||||
Basic = 'basic'
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { createSection, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
|
||||
import { SubscriptionId_VariableName, ResourceGroup_VariableName, Region_VariableName, AksName_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
private inputComponents: InputComponents = {};
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.AzureSettingsPageTitle', "Azure settings"),
|
||||
localize('deployCluster.AzureSettingsPageDescription', "Configure the settings to create an Azure Kubernetes Service cluster"), wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
const self = this;
|
||||
const azureSection: SectionInfo = {
|
||||
title: '',
|
||||
labelPosition: LabelPosition.Left,
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.SubscriptionField', "Subscription id"),
|
||||
required: false,
|
||||
variableName: SubscriptionId_VariableName,
|
||||
placeHolder: localize('deployCluster.SubscriptionPlaceholder', "Use my default Azure subscription"),
|
||||
description: localize('deployCluster.SubscriptionDescription', "The default subscription will be used if you leave this field blank.")
|
||||
}, {
|
||||
type: FieldType.DateTimeText,
|
||||
label: localize('deployCluster.ResourceGroupName', "New resource group name"),
|
||||
required: true,
|
||||
variableName: ResourceGroup_VariableName,
|
||||
defaultValue: 'mssql-'
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.Region', "Region"),
|
||||
required: true,
|
||||
variableName: Region_VariableName,
|
||||
defaultValue: 'eastus'
|
||||
}, {
|
||||
type: FieldType.DateTimeText,
|
||||
label: localize('deployCluster.AksName', "AKS cluster name"),
|
||||
required: true,
|
||||
variableName: AksName_VariableName,
|
||||
defaultValue: 'mssql-',
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.VMCount', "VM count"),
|
||||
required: true,
|
||||
variableName: VMCount_VariableName,
|
||||
defaultValue: '5',
|
||||
min: 1,
|
||||
max: 999
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.VMSize', "VM size"),
|
||||
required: true,
|
||||
variableName: VMSize_VariableName,
|
||||
defaultValue: 'Standard_E4s_v3'
|
||||
}
|
||||
]
|
||||
};
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
|
||||
const azureGroup = createSection({
|
||||
sectionInfo: azureSection,
|
||||
view: view,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent): void => {
|
||||
self.inputComponents[name] = component;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
},
|
||||
container: this.wizard.wizardObject
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[{
|
||||
title: '',
|
||||
component: azureGroup
|
||||
}],
|
||||
{
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}
|
||||
);
|
||||
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
|
||||
import { createSection, InputComponents, setModelValues, Validator, isInputBoxEmpty, getInputBoxComponent, isValidSQLPassword, getInvalidSQLPasswordMessage, getPasswordMismatchMessage, MissingRequiredInformationErrorMessage } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { EOL } from 'os';
|
||||
import { AuthenticationMode } from '../deployClusterWizardModel';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const ConfirmPasswordName = 'ConfirmPassword';
|
||||
export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
private inputComponents: InputComponents = {};
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.ClusterSettingsPageTitle', "Cluster settings"),
|
||||
localize('deployCluster.ClusterSettingsPageDescription', "Configure the SQL Server Big Data Cluster settings"), wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
const self = this;
|
||||
const basicSection: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
title: '',
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.ClusterName', "Cluster name"),
|
||||
required: true,
|
||||
variableName: VariableNames.ClusterName_VariableName,
|
||||
defaultValue: 'mssql-cluster',
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.ControllerUsername', "Controller username"),
|
||||
required: true,
|
||||
variableName: VariableNames.AdminUserName_VariableName,
|
||||
defaultValue: 'admin',
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Password,
|
||||
label: localize('deployCluster.AdminPassword', "Password"),
|
||||
required: true,
|
||||
variableName: VariableNames.AdminPassword_VariableName,
|
||||
defaultValue: '',
|
||||
useCustomValidator: true,
|
||||
description: localize('deployCluster.AdminPasswordDescription', "You can also use this password to access SQL Server and gateway.")
|
||||
}, {
|
||||
type: FieldType.Password,
|
||||
label: localize('deployCluster.ConfirmPassword', "Confirm password"),
|
||||
required: true,
|
||||
variableName: ConfirmPasswordName,
|
||||
defaultValue: '',
|
||||
useCustomValidator: true,
|
||||
}, {
|
||||
type: FieldType.Options,
|
||||
label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
|
||||
required: true,
|
||||
variableName: VariableNames.AuthenticationMode_VariableName,
|
||||
defaultValue: AuthenticationMode.Basic,
|
||||
options: [
|
||||
{
|
||||
name: AuthenticationMode.Basic,
|
||||
displayName: localize('deployCluster.AuthenticationMode.Basic', "Basic")
|
||||
},
|
||||
{
|
||||
name: AuthenticationMode.ActiveDirectory,
|
||||
displayName: localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory")
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const activeDirectorySection: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
title: localize('deployCluster.ActiveDirectorySettings', "Active Directory settings"),
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.DistinguishedName', "Distinguished name"),
|
||||
required: true,
|
||||
variableName: VariableNames.DistinguishedName_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.AdminPrincipals', "Admin principals"),
|
||||
required: true,
|
||||
variableName: VariableNames.AdminPrincipals_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.UserPrincipals', "User principals"),
|
||||
required: true,
|
||||
variableName: VariableNames.UserPrincipals_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.UpstreamIPAddresses', "Upstream IP Addresses"),
|
||||
required: true,
|
||||
variableName: VariableNames.UpstreamIPAddresses_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.DNSName', "DNS name"),
|
||||
required: true,
|
||||
variableName: VariableNames.DnsName_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.Realm', "Realm"),
|
||||
required: true,
|
||||
variableName: VariableNames.Realm_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.AppOnwerPrincipals', "App owner principals"),
|
||||
required: true,
|
||||
variableName: VariableNames.AppOwnerPrincipals_VariableName,
|
||||
useCustomValidator: true
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.AppReaderPrincipals', "App reader principals"),
|
||||
required: true,
|
||||
variableName: VariableNames.AppReaderPrincipals_VariableName,
|
||||
useCustomValidator: true
|
||||
}
|
||||
]
|
||||
};
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
const basicSettingsGroup = createSection({
|
||||
view: view,
|
||||
container: self.wizard.wizardObject,
|
||||
sectionInfo: basicSection,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: azdata.DropDownComponent | azdata.InputBoxComponent | azdata.CheckBoxComponent): void => {
|
||||
self.inputComponents[name] = component;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
}
|
||||
});
|
||||
const activeDirectorySettingsGroup = createSection({
|
||||
view: view,
|
||||
container: self.wizard.wizardObject,
|
||||
sectionInfo: activeDirectorySection,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: azdata.DropDownComponent | azdata.InputBoxComponent | azdata.CheckBoxComponent): void => {
|
||||
self.inputComponents[name] = component;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
}
|
||||
});
|
||||
const basicSettingsFormItem = { title: '', component: basicSettingsGroup };
|
||||
const activeDirectoryFormItem = { title: '', component: activeDirectorySettingsGroup };
|
||||
const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName];
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[basicSettingsFormItem],
|
||||
{
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}
|
||||
);
|
||||
this.wizard.registerDisposable(authModeDropdown.onValueChanged(() => {
|
||||
const isBasicAuthMode = (<azdata.CategoryValue>authModeDropdown.value).name === 'basic';
|
||||
|
||||
if (isBasicAuthMode) {
|
||||
formBuilder.removeFormItem(activeDirectoryFormItem);
|
||||
} else {
|
||||
formBuilder.insertFormItem(activeDirectoryFormItem);
|
||||
}
|
||||
}));
|
||||
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName];
|
||||
if (authModeDropdown) {
|
||||
authModeDropdown.enabled = this.wizard.model.adAuthSupported;
|
||||
}
|
||||
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
if (pcInfo.newPage > pcInfo.lastPage) {
|
||||
const messages: string[] = [];
|
||||
const authMode = typeof authModeDropdown.value === 'string' ? authModeDropdown.value : authModeDropdown.value!.name;
|
||||
const requiredFieldsFilled: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ClusterName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminUserName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))
|
||||
&& (!(authMode === AuthenticationMode.ActiveDirectory) || (
|
||||
!isInputBoxEmpty(getInputBoxComponent(VariableNames.DistinguishedName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPrincipals_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.UserPrincipals_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.UpstreamIPAddresses_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DnsName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.Realm_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppOwnerPrincipals_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppReaderPrincipals_VariableName, this.inputComponents))));
|
||||
if (!requiredFieldsFilled) {
|
||||
messages.push(MissingRequiredInformationErrorMessage);
|
||||
}
|
||||
|
||||
if (!isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))) {
|
||||
const password = getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents).value!;
|
||||
const confirmPassword = getInputBoxComponent(ConfirmPasswordName, this.inputComponents).value!;
|
||||
if (password !== confirmPassword) {
|
||||
messages.push(getPasswordMismatchMessage(localize('deployCluster.AdminPasswordField', "Password")));
|
||||
}
|
||||
if (!isValidSQLPassword(password)) {
|
||||
messages.push(getInvalidSQLPasswordMessage(localize('deployCluster.AdminPasswordField', "Password")));
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: messages.length === 1 ? messages[0] : localize('deployCluster.ValidationError', "There are some errors on this page, click 'Show Details' to view the errors."),
|
||||
description: messages.length === 1 ? undefined : messages.join(EOL),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return messages.length === 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { createFlexContainer } from '../../modelViewUtils';
|
||||
import { BdcDeploymentType } from '../../../interfaces';
|
||||
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
private _cards: azdata.CardComponent[] = [];
|
||||
private _cardContainer: azdata.FlexContainer | undefined;
|
||||
private _loadingComponent: azdata.LoadingComponent | undefined;
|
||||
private _view: azdata.ModelView | undefined;
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.summaryPageTitle', "Deployment configuration template"),
|
||||
localize('deployCluster.summaryPageDescription', "Select the target configuration template"), wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
this._cardContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', flexWrap: 'wrap' }).component();
|
||||
const hintText = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: localize('deployCluster.ProfileHintText', "Note: The settings of the deployment profile can be customized in later steps.")
|
||||
}).component();
|
||||
const container = createFlexContainer(view, [this._cardContainer, hintText], false);
|
||||
this._loadingComponent = view.modelBuilder.loadingComponent().withItem(container).withProperties<azdata.LoadingComponentProperties>({
|
||||
loading: true
|
||||
}).component();
|
||||
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
title: '',
|
||||
component: this._loadingComponent
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
).withLayout({ width: '100%', height: '100%' });
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
this.loadCards().then(() => {
|
||||
this._loadingComponent!.loading = false;
|
||||
}, (error) => {
|
||||
this.wizard.wizardObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message)
|
||||
};
|
||||
this._loadingComponent!.loading = false;
|
||||
});
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
private createProfileCard(profile: BigDataClusterDeploymentProfile, view: azdata.ModelView): azdata.CardComponent {
|
||||
const descriptions: azdata.CardDescriptionItem[] = [{
|
||||
label: localize('deployCluster.serviceLabel', "Service"),
|
||||
value: localize('deployCluster.instancesLabel', "Instances"),
|
||||
fontWeight: 'bold'
|
||||
}, {
|
||||
label: localize('deployCluster.masterPoolLabel', "SQL Server Master"),
|
||||
value: profile.sqlServerReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.computePoolLable', "Compute"),
|
||||
value: profile.computeReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.dataPoolLabel', "Data"),
|
||||
value: profile.dataReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.hdfsLabel', "HDFS + Spark"),
|
||||
value: profile.hdfsReplicas.toString()
|
||||
}, {
|
||||
label: '' // line separator
|
||||
}, {
|
||||
label: localize('deployCluster.defaultDataStorage', "Data storage size (GB)"),
|
||||
value: profile.controllerDataStorageSize.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.defaultLogStorage', "Log storage size (GB)"),
|
||||
value: profile.controllerLogsStorageSize.toString()
|
||||
}, {
|
||||
label: '' // line separator
|
||||
}
|
||||
];
|
||||
if (profile.activeDirectorySupported) {
|
||||
descriptions.push({
|
||||
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"),
|
||||
value: '✅'
|
||||
});
|
||||
} else {
|
||||
descriptions.push({
|
||||
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
|
||||
value: '✅'
|
||||
});
|
||||
}
|
||||
|
||||
if (profile.hadrEnabled) {
|
||||
descriptions.push({
|
||||
label: localize('deployCluster.hadr', "High Availability"),
|
||||
value: '✅'
|
||||
});
|
||||
}
|
||||
|
||||
const card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
label: profile.profileName,
|
||||
descriptions: descriptions,
|
||||
width: '240px',
|
||||
height: '300px',
|
||||
}).component();
|
||||
this._cards.push(card);
|
||||
this.wizard.registerDisposable(card.onCardSelectedChanged(() => {
|
||||
if (card.selected) {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
this.setModelValuesByProfile(profile);
|
||||
// clear the selected state of the previously selected card
|
||||
this._cards.forEach(c => {
|
||||
if (c !== card) {
|
||||
c.selected = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// keep the selected state if no other card is selected
|
||||
if (this._cards.filter(c => { return c !== card && c.selected; }).length === 0) {
|
||||
card.selected = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private setModelValuesByProfile(selectedProfile: BigDataClusterDeploymentProfile): void {
|
||||
this.wizard.model.setPropertyValue(VariableNames.DeploymentProfile_VariableName, selectedProfile.profileName);
|
||||
this.wizard.model.setPropertyValue(VariableNames.SparkPoolScale_VariableName, selectedProfile.sparkReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.DataPoolScale_VariableName, selectedProfile.dataReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.HDFSPoolScale_VariableName, selectedProfile.hdfsReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ComputePoolScale_VariableName, selectedProfile.computeReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.HDFSNameNodeScale_VariableName, selectedProfile.hdfsNameNodeReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.SQLServerScale_VariableName, selectedProfile.sqlServerReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.SparkHeadScale_VariableName, selectedProfile.sparkHeadReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ZooKeeperScale_VariableName, selectedProfile.zooKeeperReplicas);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageSize_VariableName, selectedProfile.controllerDataStorageSize);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageSize_VariableName, selectedProfile.controllerLogsStorageSize);
|
||||
this.wizard.model.setPropertyValue(VariableNames.EnableHADR_VariableName, selectedProfile.hadrEnabled);
|
||||
this.wizard.model.setPropertyValue(VariableNames.SQLServerPort_VariableName, selectedProfile.sqlServerPort);
|
||||
this.wizard.model.setPropertyValue(VariableNames.GateWayPort_VariableName, selectedProfile.gatewayPort);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ControllerPort_VariableName, selectedProfile.controllerPort);
|
||||
this.wizard.model.setPropertyValue(VariableNames.IncludeSpark_VariableName, selectedProfile.includeSpark);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageClassName_VariableName, selectedProfile.controllerDataStorageClass);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageClassName_VariableName, selectedProfile.controllerLogsStorageClass);
|
||||
this.wizard.model.setPropertyValue(VariableNames.ReadableSecondaryPort_VariableName, selectedProfile.sqlServerReadableSecondaryPort);
|
||||
this.wizard.model.adAuthSupported = selectedProfile.activeDirectorySupported;
|
||||
this.wizard.model.selectedProfile = selectedProfile;
|
||||
}
|
||||
|
||||
private loadCards(): Promise<void> {
|
||||
return this.wizard.azdataService.getDeploymentProfiles().then((profiles: BigDataClusterDeploymentProfile[]) => {
|
||||
const defaultProfile: string = this.getDefaultProfile();
|
||||
|
||||
profiles.forEach(profile => {
|
||||
const card = this.createProfileCard(profile, this._view!);
|
||||
if (profile.profileName === defaultProfile) {
|
||||
card.selected = true;
|
||||
this.setModelValuesByProfile(profile);
|
||||
}
|
||||
this._cardContainer!.addItem(card, { flex: '0 0 auto' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
if (pcInfo.newPage > pcInfo.lastPage) {
|
||||
const isValid = this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName) !== undefined;
|
||||
if (!isValid) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('deployCluster.ProfileNotSelectedError', "Please select a deployment profile."),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private getDefaultProfile(): string {
|
||||
switch (this.wizard.deploymentType) {
|
||||
case BdcDeploymentType.NewAKS:
|
||||
case BdcDeploymentType.ExistingAKS:
|
||||
return 'aks-dev-test';
|
||||
case BdcDeploymentType.ExistingKubeAdm:
|
||||
return 'kubeadm-dev-test';
|
||||
default:
|
||||
throw new Error(`Unknown deployment type: ${this.wizard.deploymentType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType } from '../../../interfaces';
|
||||
import { Validator, InputComponents, createSection, createGroupContainer, createLabel, createFlexContainer, createTextInput, createNumberInput, setModelValues, getInputBoxComponent, getCheckboxComponent, isInputBoxEmpty, getDropdownComponent, MissingRequiredInformationErrorMessage } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { AuthenticationMode } from '../deployClusterWizardModel';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const PortInputWidth = '100px';
|
||||
const inputWidth = '180px';
|
||||
const labelWidth = '150px';
|
||||
const spaceBetweenFields = '5px';
|
||||
|
||||
export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
private inputComponents: InputComponents = {};
|
||||
private endpointHeaderRow!: azdata.FlexContainer;
|
||||
private dnsColumnHeader!: azdata.TextComponent;
|
||||
private portColumnHeader!: azdata.TextComponent;
|
||||
private controllerDNSInput!: azdata.InputBoxComponent;
|
||||
private controllerPortInput!: azdata.InputBoxComponent;
|
||||
private controllerEndpointRow!: azdata.FlexContainer;
|
||||
private sqlServerDNSInput!: azdata.InputBoxComponent;
|
||||
private sqlServerEndpointRow!: azdata.FlexContainer;
|
||||
private sqlServerPortInput!: azdata.InputBoxComponent;
|
||||
private gatewayDNSInput!: azdata.InputBoxComponent;
|
||||
private gatewayPortInput!: azdata.InputBoxComponent;
|
||||
private gatewayEndpointRow!: azdata.FlexContainer;
|
||||
private readableSecondaryDNSInput!: azdata.InputBoxComponent;
|
||||
private readableSecondaryPortInput!: azdata.InputBoxComponent;
|
||||
private readableSecondaryEndpointRow!: azdata.FlexContainer;
|
||||
private endpointNameColumnHeader!: azdata.TextComponent;
|
||||
private controllerNameLabel!: azdata.TextComponent;
|
||||
private SqlServerNameLabel!: azdata.TextComponent;
|
||||
private gatewayNameLabel!: azdata.TextComponent;
|
||||
private readableSecondaryNameLabel!: azdata.TextComponent;
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.ServiceSettingsPageTitle', "Service settings"), '', wizard);
|
||||
}
|
||||
public initialize(): void {
|
||||
const scaleSectionInfo: SectionInfo = {
|
||||
title: localize('deployCluster.scaleSectionTitle', "Scale settings"),
|
||||
labelWidth: labelWidth,
|
||||
inputWidth: inputWidth,
|
||||
spaceBetweenFields: spaceBetweenFields,
|
||||
rows: [{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.ComputeText', "Compute"),
|
||||
min: 1,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.ComputePoolScale_VariableName,
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.DataText', "Data"),
|
||||
min: 1,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.DataPoolScale_VariableName,
|
||||
}]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.HDFSText', "HDFS"),
|
||||
min: 1,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.HDFSPoolScale_VariableName
|
||||
}, {
|
||||
type: FieldType.Checkbox,
|
||||
label: localize('deployCluster.includeSparkInHDFSPool', "Include Spark"),
|
||||
defaultValue: 'true',
|
||||
variableName: VariableNames.IncludeSpark_VariableName,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.SparkText', "Spark"),
|
||||
min: 0,
|
||||
max: 100,
|
||||
defaultValue: '0',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.SparkPoolScale_VariableName
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const hadrSectionInfo: SectionInfo = {
|
||||
title: localize('deployCluster.HadrSection', "High availability settings"),
|
||||
labelWidth: labelWidth,
|
||||
inputWidth: inputWidth,
|
||||
spaceBetweenFields: spaceBetweenFields,
|
||||
rows: [{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Options,
|
||||
label: localize('deployCluster.MasterSqlText', "SQL Server Master"),
|
||||
options: ['1', '3', '4', '5', '6', '7', '8', '9'],
|
||||
defaultValue: '1',
|
||||
required: true,
|
||||
variableName: VariableNames.SQLServerScale_VariableName,
|
||||
}, {
|
||||
type: FieldType.Checkbox,
|
||||
label: localize('deployCluster.EnableHADR', "Enable Availability Groups"),
|
||||
defaultValue: 'false',
|
||||
variableName: VariableNames.EnableHADR_VariableName,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.HDFSNameNodeText', "HDFS name node"),
|
||||
min: 1,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.HDFSNameNodeScale_VariableName
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.SparkHeadText', "SparkHead"),
|
||||
min: 0,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.SparkHeadScale_VariableName
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.ZooKeeperText', "ZooKeeper"),
|
||||
min: 0,
|
||||
max: 100,
|
||||
defaultValue: '1',
|
||||
useCustomValidator: true,
|
||||
required: true,
|
||||
variableName: VariableNames.ZooKeeperScale_VariableName
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const hintTextForStorageFields = localize('deployCluster.storageFieldTooltip', "Use controller settings");
|
||||
const storageSectionInfo: SectionInfo = {
|
||||
title: '',
|
||||
labelWidth: '0px',
|
||||
inputWidth: inputWidth,
|
||||
spaceBetweenFields: spaceBetweenFields,
|
||||
rows: [{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
required: false,
|
||||
defaultValue: localize('deployCluster.DataStorageClassName', "Storage class for data"),
|
||||
variableName: '',
|
||||
labelWidth: labelWidth
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
required: false,
|
||||
defaultValue: localize('deployCluster.DataClaimSize', "Claim size for data (GB)"),
|
||||
variableName: ''
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
required: false,
|
||||
defaultValue: localize('deployCluster.LogStorageClassName', "Storage class for logs"),
|
||||
variableName: '',
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
required: false,
|
||||
defaultValue: localize('deployCluster.LogsClaimSize', "Claim size for logs (GB)"),
|
||||
variableName: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.ControllerText', "Controller"),
|
||||
useCustomValidator: true,
|
||||
variableName: VariableNames.ControllerDataStorageClassName_VariableName,
|
||||
required: true,
|
||||
description: localize('deployCluster.AdvancedStorageDescription', "By default Controller storage settings will be applied to other services as well, you can expand the advanced storage settings to configure storage for other services."),
|
||||
labelWidth: labelWidth
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
useCustomValidator: true,
|
||||
min: 1,
|
||||
variableName: VariableNames.ControllerDataStorageSize_VariableName,
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: '',
|
||||
useCustomValidator: true,
|
||||
min: 1,
|
||||
variableName: VariableNames.ControllerLogsStorageClassName_VariableName,
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
useCustomValidator: true,
|
||||
min: 1,
|
||||
variableName: VariableNames.ControllerLogsStorageSize_VariableName,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const advancedStorageSectionInfo: SectionInfo = {
|
||||
title: localize('deployCluster.AdvancedStorageSectionTitle', "Advanced storage settings"),
|
||||
labelWidth: '0px',
|
||||
inputWidth: inputWidth,
|
||||
spaceBetweenFields: spaceBetweenFields,
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
rows: [{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.HDFSText', "HDFS"),
|
||||
required: false,
|
||||
variableName: VariableNames.HDFSDataStorageClassName_VariableName,
|
||||
placeHolder: hintTextForStorageFields,
|
||||
labelWidth: labelWidth
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.HDFSDataStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: '',
|
||||
required: false,
|
||||
variableName: VariableNames.HDFSLogsStorageClassName_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.HDFSLogsStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.DataText', "Data"),
|
||||
required: false,
|
||||
variableName: VariableNames.DataPoolDataStorageClassName_VariableName,
|
||||
labelWidth: labelWidth,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.DataPoolDataStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: '',
|
||||
required: false,
|
||||
variableName: VariableNames.DataPoolLogsStorageClassName_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.DataPoolLogsStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.MasterSqlText', "SQL Server Master"),
|
||||
required: false,
|
||||
variableName: VariableNames.SQLServerDataStorageClassName_VariableName,
|
||||
labelWidth: labelWidth,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.SQLServerDataStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: '',
|
||||
required: false,
|
||||
variableName: VariableNames.SQLServerLogsStorageClassName_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}, {
|
||||
type: FieldType.Number,
|
||||
label: '',
|
||||
required: false,
|
||||
min: 1,
|
||||
variableName: VariableNames.SQLServerLogsStorageSize_VariableName,
|
||||
placeHolder: hintTextForStorageFields
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
const createSectionFunc = (sectionInfo: SectionInfo): azdata.GroupContainer => {
|
||||
return createSection({
|
||||
view: view,
|
||||
container: this.wizard.wizardObject,
|
||||
sectionInfo: sectionInfo,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
this.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: azdata.DropDownComponent | azdata.InputBoxComponent | azdata.CheckBoxComponent): void => {
|
||||
this.inputComponents[name] = component;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
}
|
||||
});
|
||||
};
|
||||
const scaleSection = createSectionFunc(scaleSectionInfo);
|
||||
const hadrSection = createSectionFunc(hadrSectionInfo);
|
||||
const endpointSection = this.createEndpointSection(view);
|
||||
const storageSection = createSectionFunc(storageSectionInfo);
|
||||
const advancedStorageSection = createSectionFunc(advancedStorageSectionInfo);
|
||||
const storageContainer = createGroupContainer(view, [storageSection, advancedStorageSection], {
|
||||
header: localize('deployCluster.StorageSectionTitle', "Storage settings"),
|
||||
collapsible: true
|
||||
});
|
||||
this.setSQLServerMasterFieldEventHandler();
|
||||
const form = view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
title: '',
|
||||
component: scaleSection
|
||||
}, {
|
||||
title: '',
|
||||
component: hadrSection
|
||||
}, {
|
||||
title: '',
|
||||
component: endpointSection
|
||||
}, {
|
||||
title: '',
|
||||
component: storageContainer
|
||||
}
|
||||
]).withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
private createEndpointSection(view: azdata.ModelView): azdata.GroupContainer {
|
||||
this.endpointNameColumnHeader = createLabel(view, { text: '', width: labelWidth });
|
||||
this.dnsColumnHeader = createLabel(view, { text: localize('deployCluster.DNSNameHeader', "DNS name"), width: inputWidth });
|
||||
this.portColumnHeader = createLabel(view, { text: localize('deployCluster.PortHeader', "Port"), width: PortInputWidth });
|
||||
this.endpointHeaderRow = createFlexContainer(view, [this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]);
|
||||
|
||||
this.controllerNameLabel = createLabel(view, { text: localize('deployCluster.ControllerText', "Controller"), width: labelWidth, required: true });
|
||||
this.controllerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ControllerDNSName', "Controller DNS name"), required: false, width: inputWidth });
|
||||
this.controllerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ControllerPortName', "Controller port"), required: true, width: PortInputWidth, min: 1 });
|
||||
this.controllerEndpointRow = createFlexContainer(view, [this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput]);
|
||||
this.inputComponents[VariableNames.ControllerDNSName_VariableName] = this.controllerDNSInput;
|
||||
this.inputComponents[VariableNames.ControllerPort_VariableName] = this.controllerPortInput;
|
||||
|
||||
this.SqlServerNameLabel = createLabel(view, { text: localize('deployCluster.MasterSqlText', "SQL Server Master"), width: labelWidth, required: true });
|
||||
this.sqlServerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerDNSName', "SQL Server Master DNS name"), required: false, width: inputWidth });
|
||||
this.sqlServerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerPortName', "SQL Server Master port"), required: true, width: PortInputWidth, min: 1 });
|
||||
this.sqlServerEndpointRow = createFlexContainer(view, [this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput]);
|
||||
this.inputComponents[VariableNames.SQLServerDNSName_VariableName] = this.sqlServerDNSInput;
|
||||
this.inputComponents[VariableNames.SQLServerPort_VariableName] = this.sqlServerPortInput;
|
||||
|
||||
this.gatewayNameLabel = createLabel(view, { text: localize('deployCluster.GatewayText', "Gateway"), width: labelWidth, required: true });
|
||||
this.gatewayDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.GatewayDNSName', "Gateway DNS name"), required: false, width: inputWidth });
|
||||
this.gatewayPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.GatewayPortName', "Gateway port"), required: true, width: PortInputWidth, min: 1 });
|
||||
this.gatewayEndpointRow = createFlexContainer(view, [this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput]);
|
||||
this.inputComponents[VariableNames.GatewayDNSName_VariableName] = this.gatewayDNSInput;
|
||||
this.inputComponents[VariableNames.GateWayPort_VariableName] = this.gatewayPortInput;
|
||||
|
||||
this.readableSecondaryNameLabel = createLabel(view, { text: localize('deployCluster.ReadableSecondaryText', "Readable secondary"), width: labelWidth, required: true });
|
||||
this.readableSecondaryDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryDNSName', "Readable secondary DNS name"), required: false, width: inputWidth });
|
||||
this.readableSecondaryPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryPortName', "Readable secondary port"), required: false, width: PortInputWidth, min: 1 });
|
||||
this.readableSecondaryEndpointRow = createFlexContainer(view, [this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput]);
|
||||
this.inputComponents[VariableNames.ReadableSecondaryDNSName_VariableName] = this.readableSecondaryDNSInput;
|
||||
this.inputComponents[VariableNames.ReadableSecondaryPort_VariableName] = this.readableSecondaryPortInput;
|
||||
|
||||
return createGroupContainer(view, [this.endpointHeaderRow, this.controllerEndpointRow, this.sqlServerEndpointRow, this.gatewayEndpointRow, this.readableSecondaryEndpointRow], {
|
||||
header: localize('deployCluster.EndpointSettings', "Endpoint settings"),
|
||||
collapsible: true
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter(): void {
|
||||
this.setDropdownValue(VariableNames.SQLServerScale_VariableName);
|
||||
this.setCheckboxValue(VariableNames.EnableHADR_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ComputePoolScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.DataPoolScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.HDFSPoolScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.HDFSNameNodeScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.SparkPoolScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.SparkHeadScale_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ZooKeeperScale_VariableName);
|
||||
this.setCheckboxValue(VariableNames.IncludeSpark_VariableName);
|
||||
this.setEnableHadrCheckboxState(this.wizard.model.getIntegerValue(VariableNames.SQLServerScale_VariableName));
|
||||
this.setInputBoxValue(VariableNames.ControllerPort_VariableName);
|
||||
this.setInputBoxValue(VariableNames.SQLServerPort_VariableName);
|
||||
this.setInputBoxValue(VariableNames.GateWayPort_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ReadableSecondaryPort_VariableName);
|
||||
|
||||
this.setInputBoxValue(VariableNames.ControllerDataStorageClassName_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ControllerDataStorageSize_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ControllerLogsStorageClassName_VariableName);
|
||||
this.setInputBoxValue(VariableNames.ControllerLogsStorageSize_VariableName);
|
||||
|
||||
this.endpointHeaderRow.clearItems();
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
this.endpointHeaderRow.addItems([this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]);
|
||||
}
|
||||
this.loadEndpointRow(this.controllerEndpointRow, this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput);
|
||||
this.loadEndpointRow(this.gatewayEndpointRow, this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput);
|
||||
this.loadEndpointRow(this.sqlServerEndpointRow, this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput);
|
||||
this.updateReadableSecondaryEndpointComponents(this.wizard.model.hadrEnabled);
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
if (pcInfo.newPage > pcInfo.lastPage) {
|
||||
const isValid: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ComputePoolScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DataPoolScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSNameNodeScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSPoolScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SparkPoolScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SparkHeadScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ZooKeeperScale_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageClassName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageSize_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerLogsStorageClassName_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerLogsStorageSize_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerPort_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SQLServerPort_VariableName, this.inputComponents))
|
||||
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.GateWayPort_VariableName, this.inputComponents))
|
||||
&& (!getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents).checked
|
||||
|| !isInputBoxEmpty(this.readableSecondaryPortInput))
|
||||
&& (this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory
|
||||
|| (!isInputBoxEmpty(this.gatewayDNSInput)
|
||||
&& !isInputBoxEmpty(this.controllerDNSInput)
|
||||
&& !isInputBoxEmpty(this.sqlServerDNSInput)
|
||||
&& !isInputBoxEmpty(this.readableSecondaryDNSInput)
|
||||
));
|
||||
if (!isValid) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: MissingRequiredInformationErrorMessage,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private setInputBoxValue(variableName: string): void {
|
||||
getInputBoxComponent(variableName, this.inputComponents).value = this.wizard.model.getStringValue(variableName);
|
||||
}
|
||||
|
||||
private setCheckboxValue(variableName: string): void {
|
||||
getCheckboxComponent(variableName, this.inputComponents).checked = this.wizard.model.getBooleanValue(variableName);
|
||||
}
|
||||
|
||||
private setDropdownValue(variableName: string): void {
|
||||
getDropdownComponent(variableName, this.inputComponents).value = this.wizard.model.getStringValue(variableName);
|
||||
}
|
||||
|
||||
private setSQLServerMasterFieldEventHandler() {
|
||||
const sqlScaleDropdown = getDropdownComponent(VariableNames.SQLServerScale_VariableName, this.inputComponents);
|
||||
const enableHadrCheckbox = getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents);
|
||||
this.wizard.registerDisposable(sqlScaleDropdown.onValueChanged(() => {
|
||||
const selectedValue = typeof sqlScaleDropdown.value === 'string' ? sqlScaleDropdown.value : sqlScaleDropdown.value!.name;
|
||||
this.setEnableHadrCheckboxState(Number.parseInt(selectedValue));
|
||||
}));
|
||||
this.wizard.registerDisposable(enableHadrCheckbox.onChanged(() => {
|
||||
this.updateReadableSecondaryEndpointComponents(enableHadrCheckbox.checked);
|
||||
}));
|
||||
}
|
||||
|
||||
private setEnableHadrCheckboxState(sqlInstances: number) {
|
||||
// 1. it is ok to enable HADR when there is only 1 replica
|
||||
// 2. if there are multiple replicas, the hadr.enabled switch must be set to true.
|
||||
const enableHadrCheckbox = getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents);
|
||||
const hadrEnabled = sqlInstances === 1 ? enableHadrCheckbox.checked : true;
|
||||
if (sqlInstances === 1) {
|
||||
enableHadrCheckbox.enabled = true;
|
||||
} else {
|
||||
enableHadrCheckbox.enabled = false;
|
||||
}
|
||||
enableHadrCheckbox.checked = hadrEnabled;
|
||||
this.updateReadableSecondaryEndpointComponents(hadrEnabled);
|
||||
}
|
||||
|
||||
private updateReadableSecondaryEndpointComponents(hadrEnabled: boolean) {
|
||||
this.readableSecondaryEndpointRow.clearItems();
|
||||
if (hadrEnabled) {
|
||||
this.loadEndpointRow(this.readableSecondaryEndpointRow, this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput);
|
||||
}
|
||||
}
|
||||
|
||||
private loadEndpointRow(row: azdata.FlexContainer, label: azdata.TextComponent, dnsInput: azdata.InputBoxComponent, portInput: azdata.InputBoxComponent): void {
|
||||
row.clearItems();
|
||||
const itemLayout: azdata.FlexItemLayout = { CSSStyles: { 'margin-right': '20px' } };
|
||||
row.addItem(label);
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
row.addItem(dnsInput, itemLayout);
|
||||
}
|
||||
row.addItem(portInput);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType, LabelPosition, FontStyle, BdcDeploymentType } from '../../../interfaces';
|
||||
import { createSection, createGroupContainer, createFlexContainer, createLabel } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import * as os from 'os';
|
||||
import { join } from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { AuthenticationMode } from '../deployClusterWizardModel';
|
||||
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
private formItems: azdata.FormComponent[] = [];
|
||||
private form!: azdata.FormBuilder;
|
||||
private view!: azdata.ModelView;
|
||||
private targetDeploymentProfile!: BigDataClusterDeploymentProfile;
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.summaryPageTitle', "Summary"), '', wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
this.view = view;
|
||||
const deploymentJsonSection = createGroupContainer(view, [
|
||||
view.modelBuilder.flexContainer().withItems([
|
||||
this.createSaveJsonButton(localize('deployCluster.SaveBdcJson', "Save bdc.json"), 'bdc.json', () => { return this.targetDeploymentProfile.getBdcJson(); }),
|
||||
this.createSaveJsonButton(localize('deployCluster.SaveControlJson', "Save control.json"), 'control.json', () => { return this.targetDeploymentProfile.getControlJson(); })
|
||||
], {
|
||||
CSSStyles: { 'margin-right': '10px' }
|
||||
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component()
|
||||
], {
|
||||
header: localize('deployCluster.DeploymentJSON', "Deployment JSON files"),
|
||||
collapsible: true
|
||||
});
|
||||
|
||||
this.form = view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
title: '',
|
||||
component: deploymentJsonSection
|
||||
}
|
||||
]);
|
||||
return view.initializeModel(this.form!.withLayout({ width: '100%' }).component());
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
this.targetDeploymentProfile = this.wizard.model.createTargetProfile();
|
||||
this.formItems.forEach(item => {
|
||||
this.form!.removeFormItem(item);
|
||||
});
|
||||
this.formItems = [];
|
||||
|
||||
const deploymentTargetSectionInfo: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
labelWidth: '150px',
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.DeploymentTarget', "Deployment target"),
|
||||
rows: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.Kubeconfig', "Kube config"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterContext', "Cluster context"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterContext_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const clusterSectionInfo: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
labelWidth: '150px',
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.ClusterSettings', "Cluster settings"),
|
||||
rows: [
|
||||
{
|
||||
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DeploymentProfile', "Deployment profile"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterName', "Cluster name"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ControllerUsername', "Controller username"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
|
||||
defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ?
|
||||
localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") :
|
||||
localize('deployCluster.AuthenticationMode.Basic', "Basic"),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const azureSectionInfo: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
labelWidth: '150px',
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.AzureSettings', "Azure settings"),
|
||||
rows: [{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SubscriptionId', "Subscription id"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ResourceGroup', "Resource group"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.Region', "Region"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AksClusterName', "AKS cluster name"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AksName_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.VMSize', "VM size"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.VMCount', "VM count"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.VMCount_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const scaleSectionInfo: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
labelWidth: '150px',
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.ScaleSettings', "Scale settings"),
|
||||
rows: [
|
||||
{
|
||||
fields: [{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ComputeText', "Compute"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DataText', "Data"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DataPoolScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.HDFSText', "HDFS"),
|
||||
defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`,
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SparkText', "Spark"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkPoolScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const hadrSectionInfo: SectionInfo = {
|
||||
labelPosition: LabelPosition.Left,
|
||||
labelWidth: '150px',
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.HadrSection', "High availability settings"),
|
||||
rows: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SqlServerText', "SQL Server Master"),
|
||||
defaultValue: `${this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName)} ${this.wizard.model.hadrEnabled ? localize('deployCluster.WithHADR', "(Availability Groups Enabled)") : ''}`,
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.HDFSNameNodeText', "HDFS name node"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.HDFSNameNodeScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ZooKeeperText', "ZooKeeper"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ZooKeeperScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SparkHeadText', "SparkHead"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkHeadScale_VariableName),
|
||||
fontStyle: FontStyle.Italic
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const createSectionFunc = (sectionInfo: SectionInfo): azdata.FormComponent => {
|
||||
return {
|
||||
title: '',
|
||||
component: createSection({
|
||||
container: this.wizard.wizardObject,
|
||||
sectionInfo: sectionInfo,
|
||||
view: this.view,
|
||||
onNewDisposableCreated: () => { },
|
||||
onNewInputComponentCreated: () => { },
|
||||
onNewValidatorCreated: () => { }
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
if (this.wizard.deploymentType === BdcDeploymentType.ExistingAKS || this.wizard.deploymentType === BdcDeploymentType.ExistingKubeAdm) {
|
||||
const deploymentTargetSection = createSectionFunc(deploymentTargetSectionInfo);
|
||||
this.formItems.push(deploymentTargetSection);
|
||||
}
|
||||
|
||||
const clusterSection = createSectionFunc(clusterSectionInfo);
|
||||
const scaleSection = createSectionFunc(scaleSectionInfo);
|
||||
const hadrSection = createSectionFunc(hadrSectionInfo);
|
||||
const endpointSection = {
|
||||
title: '',
|
||||
component: this.createEndpointSection()
|
||||
};
|
||||
const storageSection = {
|
||||
title: '',
|
||||
component: this.createStorageSection()
|
||||
};
|
||||
if (this.wizard.model.getStringValue(VariableNames.AksName_VariableName)) {
|
||||
const azureSection = createSectionFunc(azureSectionInfo);
|
||||
this.formItems.push(azureSection);
|
||||
}
|
||||
|
||||
this.formItems.push(clusterSection, scaleSection, hadrSection, endpointSection, storageSection);
|
||||
this.form.addFormItems(this.formItems);
|
||||
}
|
||||
|
||||
private getStorageSettingValue(propertyName: string, defaultValuePropertyName: string): string | undefined {
|
||||
const value = this.wizard.model.getStringValue(propertyName);
|
||||
return (value === undefined || value === '') ? this.wizard.model.getStringValue(defaultValuePropertyName) : value;
|
||||
}
|
||||
|
||||
private createStorageSection(): azdata.GroupContainer {
|
||||
const serviceNameColumn: azdata.TableColumn = {
|
||||
value: ' ',
|
||||
width: 150
|
||||
};
|
||||
const dataStorageClassColumn: azdata.TableColumn = {
|
||||
value: localize('deployCluster.DataStorageClassName', "Storage class for data"),
|
||||
width: 180
|
||||
};
|
||||
const dataStorageSizeColumn: azdata.TableColumn = {
|
||||
value: localize('deployCluster.DataClaimSize', "Claim size for data (GB)"),
|
||||
width: 180
|
||||
};
|
||||
const logStorageClassColumn: azdata.TableColumn = {
|
||||
value: localize('deployCluster.LogStorageClassName', "Storage class for logs"),
|
||||
width: 180
|
||||
};
|
||||
const logStorageSizeColumn: azdata.TableColumn = {
|
||||
value: localize('deployCluster.LogsClaimSize', "Claim size for logs (GB)"),
|
||||
width: 180
|
||||
};
|
||||
const storageTable = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
data: [
|
||||
[
|
||||
localize('deployCluster.ControllerText', "Controller"),
|
||||
this.wizard.model.getStringValue(VariableNames.ControllerDataStorageClassName_VariableName),
|
||||
this.wizard.model.getStringValue(VariableNames.ControllerDataStorageSize_VariableName),
|
||||
this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageClassName_VariableName),
|
||||
this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageSize_VariableName)],
|
||||
[
|
||||
localize('deployCluster.HDFSText', "HDFS"),
|
||||
this.getStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.HDFSLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)
|
||||
], [
|
||||
localize('deployCluster.DataText', "Data"),
|
||||
this.getStorageSettingValue(VariableNames.DataPoolDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.DataPoolDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.DataPoolLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.DataPoolLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)
|
||||
], [
|
||||
localize('deployCluster.MasterSqlText', "SQL Server Master"),
|
||||
this.getStorageSettingValue(VariableNames.SQLServerDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.SQLServerDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.SQLServerLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName),
|
||||
this.getStorageSettingValue(VariableNames.SQLServerLogsStorageSize_VariableName, VariableNames.ControllerLogsStorageSize_VariableName)
|
||||
]
|
||||
],
|
||||
columns: [serviceNameColumn, dataStorageClassColumn, dataStorageSizeColumn, logStorageClassColumn, logStorageSizeColumn],
|
||||
width: '1000px',
|
||||
height: '140px'
|
||||
}).component();
|
||||
return createGroupContainer(this.view, [storageTable], {
|
||||
header: localize('deployCluster.StorageSettings', "Storage settings"),
|
||||
collapsible: true
|
||||
});
|
||||
}
|
||||
|
||||
private createEndpointSection(): azdata.GroupContainer {
|
||||
const endpointRows = [
|
||||
this.createEndpointRow(localize('deployCluster.ControllerText', "Controller"), VariableNames.ControllerDNSName_VariableName, VariableNames.ControllerPort_VariableName),
|
||||
this.createEndpointRow(localize('deployCluster.SqlServerText', "SQL Server Master"), VariableNames.SQLServerDNSName_VariableName, VariableNames.SQLServerPort_VariableName),
|
||||
this.createEndpointRow(localize('deployCluster.GatewayText', "Gateway"), VariableNames.GatewayDNSName_VariableName, VariableNames.GateWayPort_VariableName)
|
||||
];
|
||||
|
||||
if (this.wizard.model.hadrEnabled) {
|
||||
endpointRows.push(
|
||||
this.createEndpointRow(localize('deployCluster.ReadableSecondaryText', "Readable secondary"), VariableNames.ReadableSecondaryDNSName_VariableName, VariableNames.ReadableSecondaryPort_VariableName)
|
||||
);
|
||||
}
|
||||
return createGroupContainer(this.view, endpointRows, {
|
||||
header: localize('deployCluster.EndpointSettings', "Endpoint settings"),
|
||||
collapsible: true
|
||||
});
|
||||
}
|
||||
|
||||
private createEndpointRow(name: string, dnsVariableName: string, portVariableName: string): azdata.FlexContainer {
|
||||
const items = [];
|
||||
items.push(createLabel(this.view, { text: name, width: '150px' }));
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
items.push(createLabel(this.view, { text: this.wizard.model.getStringValue(dnsVariableName)!, width: '200px', fontStyle: FontStyle.Italic }));
|
||||
}
|
||||
items.push(createLabel(this.view, { text: this.wizard.model.getStringValue(portVariableName)!, width: '100px', fontStyle: FontStyle.Italic }));
|
||||
return createFlexContainer(this.view, items);
|
||||
}
|
||||
|
||||
private createSaveJsonButton(label: string, fileName: string, getContent: () => string): azdata.ButtonComponent {
|
||||
const button = this.view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
title: label,
|
||||
label: fileName,
|
||||
ariaLabel: label,
|
||||
width: '150px'
|
||||
}).component();
|
||||
this.wizard.registerDisposable(button.onDidClick(() => {
|
||||
vscode.window.showSaveDialog({
|
||||
defaultUri: vscode.Uri.file(join(os.homedir(), fileName)),
|
||||
filters: {
|
||||
'JSON': ['json']
|
||||
}
|
||||
}).then((path) => {
|
||||
if (path) {
|
||||
fs.promises.writeFile(path.fsPath, getContent()).then(() => {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('deployCluster.SaveJsonFileMessage', "File saved: {0}", path.fsPath),
|
||||
level: azdata.window.MessageLevel.Information
|
||||
};
|
||||
}).catch((error) => {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: error.message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
return button;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { KubeClusterContext } from '../../../services/kubeService';
|
||||
import { ClusterContext_VariableName, KubeConfigPath_VariableName } from '../constants';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const ClusterRadioButtonGroupName = 'ClusterRadioGroup';
|
||||
|
||||
export class TargetClusterContextPage extends WizardPageBase<DeployClusterWizard> {
|
||||
private existingClusterControl: azdata.FlexContainer | undefined;
|
||||
private clusterContextsLabel: azdata.TextComponent | undefined;
|
||||
private errorLoadingClustersLabel: azdata.TextComponent | undefined;
|
||||
private clusterContextList: azdata.DivContainer | undefined;
|
||||
private clusterContextLoadingComponent: azdata.LoadingComponent | undefined;
|
||||
private configFileInput: azdata.InputBoxComponent | undefined;
|
||||
private browseFileButton: azdata.ButtonComponent | undefined;
|
||||
private loadDefaultKubeConfigFile: boolean = true;
|
||||
private view: azdata.ModelView | undefined;
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.TargetClusterContextPageTitle', "Target cluster context"),
|
||||
localize('deployCluster.TargetClusterContextPageDescription', "Select the kube config file and then select a cluster context from the list"), wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
this.view = view;
|
||||
this.initExistingClusterControl();
|
||||
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.existingClusterControl!,
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
).withLayout({ width: '100%', height: '100%' });
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
if (this.loadDefaultKubeConfigFile) {
|
||||
let defaultKubeConfigPath = this.wizard.kubeService.getDefautConfigPath();
|
||||
this.loadClusterContexts(defaultKubeConfigPath);
|
||||
this.loadDefaultKubeConfigFile = false;
|
||||
}
|
||||
|
||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||
if (e.lastPage > e.newPage) {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
return true;
|
||||
}
|
||||
let clusterSelected = this.wizard.model.getStringValue(ClusterContext_VariableName) !== undefined;
|
||||
if (!clusterSelected) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('deployCluster.ClusterContextNotSelectedMessage', 'Please select a cluster context.'),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return clusterSelected;
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private initExistingClusterControl(): void {
|
||||
let self = this;
|
||||
const labelWidth = '150px';
|
||||
let configFileLabel = this.view!.modelBuilder.text().withProperties({ value: localize('deployCluster.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('deployCluster.browseText', 'Browse'), width: '100px' }).component();
|
||||
let configFileContainer = this.view!.modelBuilder.flexContainer()
|
||||
.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('deployCluster.clusterContextsLabelText', 'Cluster Contexts') }).component();
|
||||
this.clusterContextsLabel.width = labelWidth;
|
||||
this.errorLoadingClustersLabel = this.view!.modelBuilder.text().withProperties({ value: localize('deployCluster.errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
|
||||
this.clusterContextList = this.view!.modelBuilder.divContainer().component();
|
||||
this.clusterContextLoadingComponent = this.view!.modelBuilder.loadingComponent().withItem(this.clusterContextList).component();
|
||||
this.existingClusterControl = this.view!.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
||||
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.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,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(os.homedir()),
|
||||
openLabel: localize('deployCluster.selectKubeConfigFileText', 'Select'),
|
||||
filters: {
|
||||
'Config Files': ['*'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
self.clusterContextList!.clearItems();
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
|
||||
self.loadClusterContexts(fileUri.fsPath);
|
||||
}));
|
||||
}
|
||||
|
||||
private async loadClusterContexts(configPath: string): Promise<void> {
|
||||
this.clusterContextLoadingComponent!.loading = true;
|
||||
this.wizard.model.setPropertyValue(ClusterContext_VariableName, undefined);
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
let self = this;
|
||||
this.configFileInput!.value = configPath;
|
||||
|
||||
let clusterContexts: KubeClusterContext[] = [];
|
||||
try {
|
||||
clusterContexts = await this.wizard.kubeService.getClusterContexts(configPath);
|
||||
} catch (error) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('deployCluster.ConfigParseError', "Failed to load the config file"),
|
||||
description: error.message || error, level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
if (clusterContexts.length !== 0) {
|
||||
self.wizard.model.setPropertyValue(KubeConfigPath_VariableName, configPath);
|
||||
let options = clusterContexts.map(clusterContext => {
|
||||
let option = this.view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||
label: clusterContext.name,
|
||||
checked: clusterContext.isCurrentContext,
|
||||
name: ClusterRadioButtonGroupName
|
||||
}).component();
|
||||
|
||||
if (clusterContext.isCurrentContext) {
|
||||
self.wizard.model.setPropertyValue(ClusterContext_VariableName, clusterContext.name);
|
||||
self.wizard.wizardObject.message = { text: '' };
|
||||
}
|
||||
|
||||
this.wizard.registerDisposable(option.onDidClick(() => {
|
||||
self.wizard.model.setPropertyValue(ClusterContext_VariableName, clusterContext.name);
|
||||
self.wizard.wizardObject.message = { text: '' };
|
||||
}));
|
||||
return option;
|
||||
});
|
||||
self.clusterContextList!.addItems(options);
|
||||
} else {
|
||||
self.clusterContextList!.addItem(this.errorLoadingClustersLabel!);
|
||||
}
|
||||
this.clusterContextLoadingComponent!.loading = false;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -16,10 +15,10 @@ export abstract class DialogBase {
|
||||
this._dialogObject.cancelButton.onClick(() => this.onCancel());
|
||||
}
|
||||
|
||||
protected abstract initializeDialog(): void;
|
||||
protected abstract initialize(): void;
|
||||
|
||||
public open(): void {
|
||||
this.initializeDialog();
|
||||
this.initialize();
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
}
|
||||
|
||||
|
||||
43
extensions/resource-deployment/src/ui/model.ts
Normal file
43
extensions/resource-deployment/src/ui/model.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class Model {
|
||||
private propValueObject: { [s: string]: string | undefined } = {};
|
||||
|
||||
public setPropertyValue(property: string, value: string | number | boolean | undefined): void {
|
||||
if (typeof value === 'boolean') {
|
||||
this.propValueObject[property] = value ? 'true' : 'false';
|
||||
} else if (typeof value === 'number') {
|
||||
this.propValueObject[property] = value.toString();
|
||||
} else {
|
||||
this.propValueObject[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public getIntegerValue(propName: string, defaultValue: number = 0): number {
|
||||
const value = this.propValueObject[propName];
|
||||
return value === undefined ? defaultValue : Number.parseInt(value);
|
||||
}
|
||||
|
||||
public getStringValue(propName: string, defaultValue?: string): string | undefined {
|
||||
const value = this.propValueObject[propName];
|
||||
return value === undefined ? defaultValue : value;
|
||||
}
|
||||
|
||||
public getBooleanValue(propName: string, defaultValue: boolean = false): boolean {
|
||||
const value = this.propValueObject[propName];
|
||||
return value === undefined ? defaultValue : value === 'true';
|
||||
}
|
||||
|
||||
public setEnvironmentVariables(): void {
|
||||
Object.keys(this.propValueObject).filter(propertyName => propertyName.startsWith('AZDATA_NB_VAR_')).forEach(propertyName => {
|
||||
const value = this.getStringValue(propertyName);
|
||||
if (value !== undefined && value !== '') {
|
||||
process.env[propertyName] = value;
|
||||
}
|
||||
process.env[propertyName] = value === undefined ? '' : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
437
extensions/resource-deployment/src/ui/modelViewUtils.ts
Normal file
437
extensions/resource-deployment/src/ui/modelViewUtils.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DialogInfo, FieldType, FieldInfo, SectionInfo, LabelPosition } from '../interfaces';
|
||||
import { Model } from './model';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export type Validator = () => { valid: boolean, message: string };
|
||||
export type InputComponents = { [s: string]: azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent; };
|
||||
|
||||
export function getInputBoxComponent(name: string, inputComponents: InputComponents): azdata.InputBoxComponent {
|
||||
return <azdata.InputBoxComponent>inputComponents[name];
|
||||
}
|
||||
|
||||
export function getDropdownComponent(name: string, inputComponents: InputComponents): azdata.DropDownComponent {
|
||||
return <azdata.DropDownComponent>inputComponents[name];
|
||||
}
|
||||
|
||||
export function getCheckboxComponent(name: string, inputComponents: InputComponents): azdata.CheckBoxComponent {
|
||||
return <azdata.CheckBoxComponent>inputComponents[name];
|
||||
}
|
||||
|
||||
export const DefaultInputComponentWidth = '400px';
|
||||
export const DefaultLabelComponentWidth = '200px';
|
||||
|
||||
export interface DialogContext extends CreateContext {
|
||||
dialogInfo: DialogInfo;
|
||||
container: azdata.window.Dialog;
|
||||
}
|
||||
|
||||
export interface WizardPageContext extends CreateContext {
|
||||
sections: SectionInfo[];
|
||||
page: azdata.window.WizardPage;
|
||||
container: azdata.window.Wizard;
|
||||
}
|
||||
|
||||
|
||||
export interface SectionContext extends CreateContext {
|
||||
sectionInfo: SectionInfo;
|
||||
view: azdata.ModelView;
|
||||
}
|
||||
|
||||
interface FieldContext extends CreateContext {
|
||||
fieldInfo: FieldInfo;
|
||||
components: azdata.Component[];
|
||||
view: azdata.ModelView;
|
||||
}
|
||||
|
||||
interface CreateContext {
|
||||
container: azdata.window.Dialog | azdata.window.Wizard;
|
||||
onNewValidatorCreated: (validator: Validator) => void;
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable) => void;
|
||||
onNewInputComponentCreated: (name: string, component: azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent) => void;
|
||||
}
|
||||
|
||||
export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValue?: string, ariaLabel: string, required?: boolean, placeHolder?: string, width?: string }): azdata.InputBoxComponent {
|
||||
return view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: inputInfo.defaultValue,
|
||||
ariaLabel: inputInfo.ariaLabel,
|
||||
inputType: 'text',
|
||||
required: inputInfo.required,
|
||||
placeHolder: inputInfo.placeHolder,
|
||||
width: inputInfo.width
|
||||
}).component();
|
||||
}
|
||||
|
||||
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, fontStyle?: string }): azdata.TextComponent {
|
||||
const text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: info.text,
|
||||
description: info.description,
|
||||
requiredIndicator: info.required,
|
||||
CSSStyles: { 'font-style': info.fontStyle || 'normal' }
|
||||
}).component();
|
||||
text.width = info.width;
|
||||
return text;
|
||||
}
|
||||
|
||||
export function createNumberInput(view: azdata.ModelView, info: { defaultValue?: string, ariaLabel?: string, min?: number, max?: number, required?: boolean, width?: string, placeHolder?: string }): azdata.InputBoxComponent {
|
||||
return view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: info.defaultValue,
|
||||
ariaLabel: info.ariaLabel,
|
||||
inputType: 'number',
|
||||
min: info.min,
|
||||
max: info.max,
|
||||
required: info.required,
|
||||
width: info.width,
|
||||
placeHolder: info.placeHolder
|
||||
}).component();
|
||||
}
|
||||
|
||||
export function createCheckbox(view: azdata.ModelView, info: { initialValue: boolean, label: string }): azdata.CheckBoxComponent {
|
||||
return view.modelBuilder.checkBox().withProperties<azdata.CheckBoxProperties>({
|
||||
checked: info.initialValue,
|
||||
label: info.label
|
||||
}).component();
|
||||
}
|
||||
|
||||
export function createDropdown(view: azdata.ModelView, info: { defaultValue?: string | azdata.CategoryValue, values?: string[] | azdata.CategoryValue[], width?: string }): azdata.DropDownComponent {
|
||||
return view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
|
||||
values: info.values,
|
||||
value: info.defaultValue,
|
||||
width: info.width
|
||||
}).component();
|
||||
}
|
||||
|
||||
export function initializeDialog(dialogContext: DialogContext): void {
|
||||
const tabs: azdata.window.DialogTab[] = [];
|
||||
dialogContext.dialogInfo.tabs.forEach(tabInfo => {
|
||||
const tab = azdata.window.createTab(tabInfo.title);
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const sections = tabInfo.sections.map(sectionInfo => {
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || tabInfo.inputWidth || DefaultInputComponentWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || tabInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
return createSection({
|
||||
sectionInfo: sectionInfo,
|
||||
view: view,
|
||||
onNewDisposableCreated: dialogContext.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: dialogContext.onNewInputComponentCreated,
|
||||
onNewValidatorCreated: dialogContext.onNewValidatorCreated,
|
||||
container: dialogContext.container
|
||||
});
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
sections.map(section => {
|
||||
return { title: '', component: section };
|
||||
}),
|
||||
{
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
tabs.push(tab);
|
||||
});
|
||||
dialogContext.container.content = tabs;
|
||||
}
|
||||
|
||||
export function initializeWizardPage(context: WizardPageContext): void {
|
||||
context.page.registerContent((view: azdata.ModelView) => {
|
||||
const sections = context.sections.map(sectionInfo => {
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || DefaultInputComponentWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
return createSection({
|
||||
view: view,
|
||||
container: context.container,
|
||||
onNewDisposableCreated: context.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: context.onNewInputComponentCreated,
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
sectionInfo: sectionInfo
|
||||
});
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
sections.map(section => { return { title: '', component: section }; }),
|
||||
{
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
export function createSection(context: SectionContext): azdata.GroupContainer {
|
||||
const components: azdata.Component[] = [];
|
||||
context.sectionInfo.inputWidth = context.sectionInfo.inputWidth || DefaultInputComponentWidth;
|
||||
context.sectionInfo.labelWidth = context.sectionInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
if (context.sectionInfo.fields) {
|
||||
processFields(context.sectionInfo.fields, components, context);
|
||||
} else if (context.sectionInfo.rows) {
|
||||
context.sectionInfo.rows.forEach(rowInfo => {
|
||||
const rowItems: azdata.Component[] = [];
|
||||
processFields(rowInfo.fields, rowItems, context, context.sectionInfo.spaceBetweenFields || '50px');
|
||||
const row = createFlexContainer(context.view, rowItems);
|
||||
components.push(row);
|
||||
});
|
||||
}
|
||||
|
||||
return createGroupContainer(context.view, components, {
|
||||
header: context.sectionInfo.title,
|
||||
collapsible: context.sectionInfo.collapsible === undefined ? true : context.sectionInfo.collapsible,
|
||||
collapsed: context.sectionInfo.collapsed === undefined ? false : context.sectionInfo.collapsed
|
||||
});
|
||||
}
|
||||
|
||||
function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component[], context: SectionContext, spaceBetweenFields?: string): void {
|
||||
for (let i = 0; i < fieldInfoArray.length; i++) {
|
||||
const fieldInfo = fieldInfoArray[i];
|
||||
fieldInfo.labelWidth = fieldInfo.labelWidth || context.sectionInfo.labelWidth;
|
||||
fieldInfo.inputWidth = fieldInfo.inputWidth || context.sectionInfo.inputWidth;
|
||||
fieldInfo.labelPosition = fieldInfo.labelPosition === undefined ? context.sectionInfo.labelPosition : fieldInfo.labelPosition;
|
||||
processField({
|
||||
view: context.view,
|
||||
onNewDisposableCreated: context.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: context.onNewInputComponentCreated,
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
fieldInfo: fieldInfo,
|
||||
container: context.container,
|
||||
components: components
|
||||
});
|
||||
if (spaceBetweenFields && i < fieldInfoArray.length - 1) {
|
||||
components.push(context.view.modelBuilder.divContainer().withLayout({ width: spaceBetweenFields }).component());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createFlexContainer(view: azdata.ModelView, items: azdata.Component[], rowLayout: boolean = true): azdata.FlexContainer {
|
||||
const flexFlow = rowLayout ? 'row' : 'column';
|
||||
const alignItems = rowLayout ? 'center' : '';
|
||||
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px' } } : {};
|
||||
return view.modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout({ flexFlow: flexFlow, alignItems: alignItems }).component();
|
||||
}
|
||||
|
||||
export function createGroupContainer(view: azdata.ModelView, items: azdata.Component[], layout: azdata.GroupLayout): azdata.GroupContainer {
|
||||
return view.modelBuilder.groupContainer().withItems(items).withLayout(layout).component();
|
||||
}
|
||||
|
||||
function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata.Component[], label: azdata.Component, input: azdata.Component, labelPosition?: LabelPosition) {
|
||||
if (labelPosition && labelPosition === LabelPosition.Left) {
|
||||
const row = createFlexContainer(view, [label, input]);
|
||||
components.push(row);
|
||||
} else {
|
||||
components.push(label, input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function processField(context: FieldContext): void {
|
||||
switch (context.fieldInfo.type) {
|
||||
case FieldType.Options:
|
||||
processOptionsTypeField(context);
|
||||
break;
|
||||
case FieldType.DateTimeText:
|
||||
processDateTimeTextField(context);
|
||||
break;
|
||||
case FieldType.Number:
|
||||
processNumberField(context);
|
||||
break;
|
||||
case FieldType.SQLPassword:
|
||||
case FieldType.Password:
|
||||
processPasswordField(context);
|
||||
break;
|
||||
case FieldType.Text:
|
||||
processTextField(context);
|
||||
break;
|
||||
case FieldType.ReadonlyText:
|
||||
processReadonlyTextField(context);
|
||||
break;
|
||||
case FieldType.Checkbox:
|
||||
processCheckboxField(context);
|
||||
break;
|
||||
default:
|
||||
throw new Error(localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", context.fieldInfo.type));
|
||||
}
|
||||
}
|
||||
|
||||
function processOptionsTypeField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth });
|
||||
const dropdown = createDropdown(context.view, {
|
||||
values: context.fieldInfo.options,
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
width: context.fieldInfo.inputWidth
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, dropdown);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition);
|
||||
}
|
||||
|
||||
function processDateTimeTextField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth });
|
||||
const defaultValue = context.fieldInfo.defaultValue + new Date().toISOString().slice(0, 19).replace(/[^0-9]/g, ''); // Take the date time information and only leaving the numbers
|
||||
const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: defaultValue,
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
inputType: 'text',
|
||||
required: !context.fieldInfo.useCustomValidator && context.fieldInfo.required,
|
||||
placeHolder: context.fieldInfo.placeHolder
|
||||
}).component();
|
||||
input.width = context.fieldInfo.inputWidth;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
}
|
||||
|
||||
function processNumberField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth });
|
||||
const input = createNumberInput(context.view, {
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
min: context.fieldInfo.min,
|
||||
max: context.fieldInfo.max,
|
||||
required: !context.fieldInfo.useCustomValidator && context.fieldInfo.required,
|
||||
width: context.fieldInfo.inputWidth,
|
||||
placeHolder: context.fieldInfo.placeHolder
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
}
|
||||
|
||||
function processTextField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth });
|
||||
const input = createTextInput(context.view, {
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
required: !context.fieldInfo.useCustomValidator && context.fieldInfo.required,
|
||||
placeHolder: context.fieldInfo.placeHolder,
|
||||
width: context.fieldInfo.inputWidth
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
}
|
||||
|
||||
function processPasswordField(context: FieldContext): void {
|
||||
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth });
|
||||
const passwordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
inputType: 'password',
|
||||
required: !context.fieldInfo.useCustomValidator && context.fieldInfo.required,
|
||||
placeHolder: context.fieldInfo.placeHolder,
|
||||
width: context.fieldInfo.inputWidth
|
||||
}).component();
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, passwordInput);
|
||||
addLabelInputPairToContainer(context.view, context.components, passwordLabel, passwordInput, context.fieldInfo.labelPosition);
|
||||
|
||||
if (context.fieldInfo.type === FieldType.SQLPassword) {
|
||||
const invalidPasswordMessage = getInvalidSQLPasswordMessage(context.fieldInfo.label);
|
||||
context.onNewDisposableCreated(passwordInput.onTextChanged(() => {
|
||||
if (context.fieldInfo.type === FieldType.SQLPassword && isValidSQLPassword(passwordInput.value!, context.fieldInfo.userName)) {
|
||||
removeValidationMessage(context.container, invalidPasswordMessage);
|
||||
}
|
||||
}));
|
||||
|
||||
context.onNewValidatorCreated((): { valid: boolean, message: string } => {
|
||||
return { valid: isValidSQLPassword(passwordInput.value!, context.fieldInfo.userName), message: invalidPasswordMessage };
|
||||
});
|
||||
}
|
||||
|
||||
if (context.fieldInfo.confirmationRequired) {
|
||||
const passwordNotMatchMessage = getPasswordMismatchMessage(context.fieldInfo.label);
|
||||
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth });
|
||||
const confirmPasswordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: context.fieldInfo.confirmationLabel,
|
||||
inputType: 'password',
|
||||
required: !context.fieldInfo.useCustomValidator,
|
||||
width: context.fieldInfo.inputWidth
|
||||
}).component();
|
||||
|
||||
addLabelInputPairToContainer(context.view, context.components, confirmPasswordLabel, confirmPasswordInput, context.fieldInfo.labelPosition);
|
||||
context.onNewValidatorCreated((): { valid: boolean, message: string } => {
|
||||
const passwordMatches = passwordInput.value === confirmPasswordInput.value;
|
||||
return { valid: passwordMatches, message: passwordNotMatchMessage };
|
||||
});
|
||||
|
||||
const updatePasswordMismatchMessage = () => {
|
||||
if (passwordInput.value === confirmPasswordInput.value) {
|
||||
removeValidationMessage(context.container, passwordNotMatchMessage);
|
||||
}
|
||||
};
|
||||
|
||||
context.onNewDisposableCreated(passwordInput.onTextChanged(() => {
|
||||
updatePasswordMismatchMessage();
|
||||
}));
|
||||
context.onNewDisposableCreated(confirmPasswordInput.onTextChanged(() => {
|
||||
updatePasswordMismatchMessage();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function processReadonlyTextField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth });
|
||||
const text = createLabel(context.view, { text: context.fieldInfo.defaultValue!, description: '', required: false, width: context.fieldInfo.inputWidth, fontStyle: context.fieldInfo.fontStyle });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo.labelPosition);
|
||||
}
|
||||
|
||||
function processCheckboxField(context: FieldContext): void {
|
||||
const checkbox = createCheckbox(context.view, { initialValue: context.fieldInfo.defaultValue! === 'true', label: context.fieldInfo.label });
|
||||
context.components.push(checkbox);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, checkbox);
|
||||
}
|
||||
|
||||
export function isValidSQLPassword(password: string, userName: string = 'sa'): boolean {
|
||||
// Validate SQL Server password
|
||||
const containsUserName = password && userName !== undefined && password.toUpperCase().includes(userName.toUpperCase());
|
||||
// Instead of using one RegEx, I am seperating it to make it more readable.
|
||||
const hasUpperCase = /[A-Z]/.test(password) ? 1 : 0;
|
||||
const hasLowerCase = /[a-z]/.test(password) ? 1 : 0;
|
||||
const hasNumbers = /\d/.test(password) ? 1 : 0;
|
||||
const hasNonalphas = /\W/.test(password) ? 1 : 0;
|
||||
return !containsUserName && password.length >= 8 && password.length <= 128 && (hasUpperCase + hasLowerCase + hasNumbers + hasNonalphas >= 3);
|
||||
}
|
||||
|
||||
export function removeValidationMessage(container: azdata.window.Dialog | azdata.window.Wizard, message: string): void {
|
||||
if (container.message && container.message.text.includes(message)) {
|
||||
const messageWithLineBreak = message + '\n';
|
||||
const searchText = container.message.text.includes(messageWithLineBreak) ? messageWithLineBreak : message;
|
||||
container.message = { text: container.message.text.replace(searchText, '') };
|
||||
}
|
||||
}
|
||||
|
||||
export function getInvalidSQLPasswordMessage(fieldName: string): string {
|
||||
return localize('invalidSQLPassword', "{0} doesn't meet the password complexity requirement. For more information: https://docs.microsoft.com/sql/relational-databases/security/password-policy", fieldName);
|
||||
}
|
||||
|
||||
export function getPasswordMismatchMessage(fieldName: string): string {
|
||||
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
||||
}
|
||||
|
||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
||||
Object.keys(inputComponents).forEach(key => {
|
||||
let value;
|
||||
const input = inputComponents[key];
|
||||
if ('checked' in input) {
|
||||
value = input.checked ? 'true' : 'false';
|
||||
} else {
|
||||
const inputValue = input.value;
|
||||
if (typeof inputValue === 'string' || typeof inputValue === 'undefined') {
|
||||
value = inputValue;
|
||||
} else {
|
||||
value = inputValue.name;
|
||||
}
|
||||
}
|
||||
|
||||
model.setPropertyValue(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {
|
||||
return input.value === undefined || input.value === '';
|
||||
}
|
||||
|
||||
export const MissingRequiredInformationErrorMessage = localize('deployCluster.MissingRequiredInfoError', "Please fill out the required fields marked with red asterisks.");
|
||||
@@ -2,20 +2,22 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { INotebookService } from '../services/notebookService';
|
||||
import { DialogFieldInfo, FieldType, DialogInfo } from '../interfaces';
|
||||
import { DialogInfo } from '../interfaces';
|
||||
import { Validator, initializeDialog, InputComponents, setModelValues } from './modelViewUtils';
|
||||
import { Model } from './model';
|
||||
import { EOL } from 'os';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class NotebookInputDialog extends DialogBase {
|
||||
|
||||
private variables: { [s: string]: string | undefined; } = {};
|
||||
private validators: (() => { valid: boolean, message: string })[] = [];
|
||||
private inputComponents: InputComponents = {};
|
||||
|
||||
constructor(private notebookService: INotebookService,
|
||||
private dialogInfo: DialogInfo) {
|
||||
@@ -24,192 +26,46 @@ export class NotebookInputDialog extends DialogBase {
|
||||
this._dialogObject.okButton.onClick(() => this.onComplete());
|
||||
}
|
||||
|
||||
protected initializeDialog() {
|
||||
const tabs: azdata.window.DialogTab[] = [];
|
||||
this.dialogInfo.tabs.forEach(tabInfo => {
|
||||
const tab = azdata.window.createTab(tabInfo.title);
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const sections: azdata.FormComponentGroup[] = [];
|
||||
tabInfo.sections.forEach(sectionInfo => {
|
||||
const fields: azdata.FormComponent[] = [];
|
||||
sectionInfo.fields.forEach(fieldInfo => {
|
||||
this.addField(view, fields, fieldInfo);
|
||||
});
|
||||
sections.push({ title: sectionInfo.title, components: fields });
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
sections,
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
const self = this;
|
||||
this._dialogObject.registerCloseValidator(() => {
|
||||
const messages: string[] = [];
|
||||
self.validators.forEach(validator => {
|
||||
const result = validator();
|
||||
if (!result.valid) {
|
||||
messages.push(result.message);
|
||||
}
|
||||
});
|
||||
if (messages.length > 0) {
|
||||
self._dialogObject.message = { level: azdata.window.MessageLevel.Error, text: messages.join('\n') };
|
||||
} else {
|
||||
self._dialogObject.message = { text: '' };
|
||||
}
|
||||
return messages.length === 0;
|
||||
});
|
||||
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
tabs.push(tab);
|
||||
});
|
||||
this._dialogObject.content = tabs;
|
||||
}
|
||||
|
||||
private addField(view: azdata.ModelView, fields: azdata.FormComponent[], fieldInfo: DialogFieldInfo): void {
|
||||
this.variables[fieldInfo.variableName] = fieldInfo.defaultValue;
|
||||
let component: { component: azdata.Component, title: string }[] | azdata.Component | undefined = undefined;
|
||||
switch (fieldInfo.type) {
|
||||
case FieldType.Options:
|
||||
component = this.createOptionsTypeField(view, fieldInfo);
|
||||
break;
|
||||
case FieldType.DateTimeText:
|
||||
component = this.createDateTimeTextField(view, fieldInfo);
|
||||
break;
|
||||
case FieldType.Number:
|
||||
component = this.createNumberField(view, fieldInfo);
|
||||
break;
|
||||
case FieldType.SQLPassword:
|
||||
case FieldType.Password:
|
||||
component = this.createPasswordField(view, fieldInfo);
|
||||
break;
|
||||
case FieldType.Text:
|
||||
component = this.createTextField(view, fieldInfo);
|
||||
break;
|
||||
default:
|
||||
throw new Error(localize('deploymentDialog.UnknownFieldTypeError', "Unknown field type: \"{0}\"", fieldInfo.type));
|
||||
}
|
||||
|
||||
if (component) {
|
||||
if (Array.isArray(component)) {
|
||||
fields.push(...component);
|
||||
} else {
|
||||
fields.push({ title: fieldInfo.label, component: component });
|
||||
protected initialize() {
|
||||
const self = this;
|
||||
const validators: Validator[] = [];
|
||||
initializeDialog({
|
||||
dialogInfo: this.dialogInfo,
|
||||
container: this._dialogObject,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
this._toDispose.push(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: azdata.DropDownComponent | azdata.InputBoxComponent | azdata.CheckBoxComponent): void => {
|
||||
this.inputComponents[name] = component;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
validators.push(validator);
|
||||
}
|
||||
} else {
|
||||
throw new Error(localize('deploymentDialog.addFieldError', "Failed to add field: \"{0}\"", fieldInfo.label));
|
||||
}
|
||||
}
|
||||
|
||||
private createOptionsTypeField(view: azdata.ModelView, fieldInfo: DialogFieldInfo): azdata.DropDownComponent {
|
||||
const dropdown = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ values: fieldInfo.options, value: fieldInfo.defaultValue }).component();
|
||||
this._toDispose.push(dropdown.onValueChanged(() => { this.variables[fieldInfo.variableName] = <string>dropdown.value; }));
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
private createDateTimeTextField(view: azdata.ModelView, fieldInfo: DialogFieldInfo): azdata.InputBoxComponent {
|
||||
const defaultValue = fieldInfo.defaultValue + new Date().toISOString().slice(0, 19).replace(/[^0-9]/g, '');
|
||||
const input = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: defaultValue, ariaLabel: fieldInfo.label, inputType: 'text', required: fieldInfo.required, placeHolder: fieldInfo.placeHolder
|
||||
}).component();
|
||||
this.variables[fieldInfo.variableName] = defaultValue;
|
||||
this._toDispose.push(input.onTextChanged(() => { this.variables[fieldInfo.variableName] = input.value; }));
|
||||
return input;
|
||||
|
||||
}
|
||||
|
||||
private createNumberField(view: azdata.ModelView, fieldInfo: DialogFieldInfo): azdata.InputBoxComponent {
|
||||
const input = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: fieldInfo.defaultValue, ariaLabel: fieldInfo.label, inputType: 'number', min: fieldInfo.min, max: fieldInfo.max, required: fieldInfo.required
|
||||
}).component();
|
||||
this._toDispose.push(input.onTextChanged(() => { this.variables[fieldInfo.variableName] = input.value; }));
|
||||
return input;
|
||||
}
|
||||
|
||||
private createTextField(view: azdata.ModelView, fieldInfo: DialogFieldInfo): azdata.InputBoxComponent {
|
||||
const input = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: fieldInfo.defaultValue, ariaLabel: fieldInfo.label, inputType: 'text', min: fieldInfo.min, max: fieldInfo.max, required: fieldInfo.required, placeHolder: fieldInfo.placeHolder
|
||||
}).component();
|
||||
this._toDispose.push(input.onTextChanged(() => { this.variables[fieldInfo.variableName] = input.value; }));
|
||||
return input;
|
||||
}
|
||||
|
||||
private createPasswordField(view: azdata.ModelView, fieldInfo: DialogFieldInfo): { title: string, component: azdata.Component }[] {
|
||||
const components: { title: string, component: azdata.Component }[] = [];
|
||||
const passwordInput = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: fieldInfo.label, inputType: 'password', required: fieldInfo.required, placeHolder: fieldInfo.placeHolder
|
||||
}).component();
|
||||
this._toDispose.push(passwordInput.onTextChanged(() => { this.variables[fieldInfo.variableName] = passwordInput.value; }));
|
||||
components.push({ title: fieldInfo.label, component: passwordInput });
|
||||
|
||||
if (fieldInfo.type === FieldType.SQLPassword) {
|
||||
const invalidPasswordMessage = localize('invalidSQLPassword', "{0} doesn't meet the password complexity requirement. For more information: https://docs.microsoft.com/sql/relational-databases/security/password-policy", fieldInfo.label);
|
||||
this._toDispose.push(passwordInput.onTextChanged(() => {
|
||||
if (fieldInfo.type === FieldType.SQLPassword && this.isValidSQLPassword(fieldInfo, passwordInput)) {
|
||||
this.removeValidationMessage(invalidPasswordMessage);
|
||||
});
|
||||
this._dialogObject.registerCloseValidator(() => {
|
||||
const messages: string[] = [];
|
||||
validators.forEach(validator => {
|
||||
const result = validator();
|
||||
if (!result.valid) {
|
||||
messages.push(result.message);
|
||||
}
|
||||
}));
|
||||
|
||||
this.validators.push((): { valid: boolean, message: string } => {
|
||||
return { valid: this.isValidSQLPassword(fieldInfo, passwordInput), message: invalidPasswordMessage };
|
||||
});
|
||||
}
|
||||
|
||||
if (fieldInfo.confirmationRequired) {
|
||||
const passwordNotMatchMessage = localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldInfo.label);
|
||||
|
||||
const confirmPasswordInput = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ ariaLabel: fieldInfo.confirmationLabel, inputType: 'password', required: true }).component();
|
||||
components.push({ title: fieldInfo.confirmationLabel, component: confirmPasswordInput });
|
||||
|
||||
this.validators.push((): { valid: boolean, message: string } => {
|
||||
const passwordMatches = passwordInput.value === confirmPasswordInput.value;
|
||||
return { valid: passwordMatches, message: passwordNotMatchMessage };
|
||||
});
|
||||
|
||||
const updatePasswordMismatchMessage = () => {
|
||||
if (passwordInput.value === confirmPasswordInput.value) {
|
||||
this.removeValidationMessage(passwordNotMatchMessage);
|
||||
}
|
||||
};
|
||||
|
||||
this._toDispose.push(passwordInput.onTextChanged(() => {
|
||||
updatePasswordMismatchMessage();
|
||||
}));
|
||||
this._toDispose.push(confirmPasswordInput.onTextChanged(() => {
|
||||
updatePasswordMismatchMessage();
|
||||
}));
|
||||
}
|
||||
return components;
|
||||
if (messages.length > 0) {
|
||||
self._dialogObject.message = { level: azdata.window.MessageLevel.Error, text: messages.join(EOL) };
|
||||
} else {
|
||||
self._dialogObject.message = { text: '' };
|
||||
}
|
||||
return messages.length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
private onComplete(): void {
|
||||
Object.keys(this.variables).forEach(key => {
|
||||
process.env[key] = this.variables[key];
|
||||
const model: Model = new Model();
|
||||
setModelValues(this.inputComponents, model);
|
||||
model.setEnvironmentVariables();
|
||||
this.notebookService.launchNotebook(this.dialogInfo.notebook).then(() => { }, (error) => {
|
||||
vscode.window.showErrorMessage(error);
|
||||
});
|
||||
this.notebookService.launchNotebook(this.dialogInfo.notebook);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private isValidSQLPassword(field: DialogFieldInfo, component: azdata.InputBoxComponent): boolean {
|
||||
const password = component.value!;
|
||||
// Validate SQL Server password
|
||||
const containsUserName = password && field.userName && password.toUpperCase().includes(field.userName.toUpperCase());
|
||||
// Instead of using one RegEx, I am seperating it to make it more readable.
|
||||
const hasUpperCase = /[A-Z]/.test(password) ? 1 : 0;
|
||||
const hasLowerCase = /[a-z]/.test(password) ? 1 : 0;
|
||||
const hasNumbers = /\d/.test(password) ? 1 : 0;
|
||||
const hasNonalphas = /\W/.test(password) ? 1 : 0;
|
||||
return !containsUserName && password.length >= 8 && password.length <= 128 && (hasUpperCase + hasLowerCase + hasNumbers + hasNonalphas >= 3);
|
||||
}
|
||||
|
||||
private removeValidationMessage(message: string): void {
|
||||
if (this._dialogObject.message && this._dialogObject.message.text.includes(message)) {
|
||||
const messageWithLineBreak = message + '\n';
|
||||
const searchText = this._dialogObject.message.text.includes(messageWithLineBreak) ? messageWithLineBreak : message;
|
||||
this._dialogObject.message = { text: this._dialogObject.message.text.replace(searchText, '') };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { ResourceType, DeploymentProvider } from '../interfaces';
|
||||
import { ResourceType, DeploymentProvider, AgreementInfo } from '../interfaces';
|
||||
import { IResourceTypeService } from '../services/resourceTypeService';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { EOL } from 'os';
|
||||
import { createFlexContainer } from './modelViewUtils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ResourceTypePickerDialog extends DialogBase {
|
||||
private toolRefreshTimestamp: number = 0;
|
||||
private _selectedResourceType: ResourceType;
|
||||
private _resourceTypeCards: azdata.CardComponent[] = [];
|
||||
private _view!: azdata.ModelView;
|
||||
@@ -23,6 +25,9 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
private _toolsTable!: azdata.TableComponent;
|
||||
private _cardResourceTypeMap: Map<string, azdata.CardComponent> = new Map();
|
||||
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
|
||||
private _toolsLoadingComponent!: azdata.LoadingComponent;
|
||||
private _agreementContainer!: azdata.DivContainer;
|
||||
private _agreementCheckboxChecked: boolean = false;
|
||||
|
||||
constructor(private extensionContext: vscode.ExtensionContext,
|
||||
private toolsService: IToolsService,
|
||||
@@ -34,8 +39,18 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
this._dialogObject.okButton.onClick(() => this.onComplete());
|
||||
}
|
||||
|
||||
initializeDialog() {
|
||||
initialize() {
|
||||
let tab = azdata.window.createTab('');
|
||||
this._dialogObject.registerCloseValidator(() => {
|
||||
const isValid = this._selectedResourceType && (this._selectedResourceType.agreement === undefined || this._agreementCheckboxChecked);
|
||||
if (!isValid) {
|
||||
this._dialogObject.message = {
|
||||
text: localize('deploymentDialog.AcceptAgreements', "You must agree to the license agreements in order to proceed."),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const tableWidth = 1126;
|
||||
this._view = view;
|
||||
@@ -43,25 +58,33 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
|
||||
this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component();
|
||||
this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
|
||||
this._agreementContainer = view.modelBuilder.divContainer().component();
|
||||
const toolColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolNameColumnHeader', 'Tool'),
|
||||
width: 150
|
||||
};
|
||||
const descriptionColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolDescriptionColumnHeader', 'Description'),
|
||||
width: 850
|
||||
width: 650
|
||||
};
|
||||
const installStatusColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolStatusColumnHeader', 'Installed'),
|
||||
width: 100
|
||||
};
|
||||
const versionColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolVersionColumnHeader', 'Version'),
|
||||
width: 100
|
||||
};
|
||||
|
||||
this._toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
data: [],
|
||||
columns: [toolColumn, descriptionColumn],
|
||||
columns: [toolColumn, descriptionColumn, installStatusColumn, versionColumn],
|
||||
width: tableWidth
|
||||
}).component();
|
||||
|
||||
const toolsTableWrapper = view.modelBuilder.divContainer().withLayout({ width: tableWidth }).component();
|
||||
toolsTableWrapper.addItem(this._toolsTable, { CSSStyles: { 'border-left': '1px solid silver', 'border-top': '1px solid silver' } });
|
||||
|
||||
this._toolsLoadingComponent = view.modelBuilder.loadingComponent().withItem(toolsTableWrapper).component();
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
@@ -71,10 +94,14 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
component: this._resourceDescriptionLabel,
|
||||
title: ''
|
||||
}, {
|
||||
component: this._agreementContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this._optionsContainer,
|
||||
title: localize('deploymentDialog.OptionsTitle', 'Options')
|
||||
}, {
|
||||
component: toolsTableWrapper,
|
||||
component: this._toolsLoadingComponent,
|
||||
title: localize('deploymentDialog.RequiredToolsTitle', 'Required tools')
|
||||
}
|
||||
],
|
||||
@@ -128,6 +155,12 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
}
|
||||
|
||||
this._resourceDescriptionLabel.value = resourceType.description;
|
||||
this._agreementCheckboxChecked = false;
|
||||
this._agreementContainer.clearItems();
|
||||
if (resourceType.agreement) {
|
||||
this._agreementContainer.addItem(this.createAgreementCheckbox(resourceType.agreement));
|
||||
}
|
||||
|
||||
this._optionsContainer.clearItems();
|
||||
this._optionDropDownMap.clear();
|
||||
resourceType.options.forEach(option => {
|
||||
@@ -151,19 +184,67 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
}
|
||||
|
||||
private updateTools(): void {
|
||||
const tools = this.getCurrentProvider().requiredTools;
|
||||
this.toolRefreshTimestamp = new Date().getTime();
|
||||
const currentRefreshTimestamp = this.toolRefreshTimestamp;
|
||||
const toolRequirements = this.getCurrentProvider().requiredTools;
|
||||
const headerRowHeight = 28;
|
||||
this._toolsTable.height = 25 * Math.max(tools.length, 1) + headerRowHeight;
|
||||
if (tools.length === 0) {
|
||||
this._toolsTable.height = 25 * Math.max(toolRequirements.length, 1) + headerRowHeight;
|
||||
if (toolRequirements.length === 0) {
|
||||
this._dialogObject.okButton.enabled = true;
|
||||
this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']];
|
||||
} else {
|
||||
this._toolsTable.data = tools.map(toolRef => {
|
||||
const tool = this.toolsService.getToolByName(toolRef.name)!;
|
||||
return [tool.displayName, tool.description];
|
||||
const tools = toolRequirements.map(toolReq => {
|
||||
return this.toolsService.getToolByName(toolReq.name)!;
|
||||
});
|
||||
this._toolsLoadingComponent.loading = true;
|
||||
this._dialogObject.okButton.enabled = false;
|
||||
this._dialogObject.message = {
|
||||
text: ''
|
||||
};
|
||||
|
||||
Promise.all(tools.map(tool => tool.loadInformation())).then(() => {
|
||||
// If the local timestamp does not match the class level timestamp, it means user has changed options, ignore the results
|
||||
if (this.toolRefreshTimestamp !== currentRefreshTimestamp) {
|
||||
return;
|
||||
}
|
||||
const messages: string[] = [];
|
||||
this._toolsTable.data = toolRequirements.map(toolRef => {
|
||||
const tool = this.toolsService.getToolByName(toolRef.name)!;
|
||||
if (!tool.isInstalled) {
|
||||
messages.push(localize('deploymentDialog.ToolInformation', "{0}: {1}", tool.displayName, tool.homePage));
|
||||
if (tool.statusDescription !== undefined) {
|
||||
console.warn(localize('deploymentDialog.DetailToolStatusDescription', "Additional status information for tool: {0}. {1}", tool.name, tool.statusDescription));
|
||||
}
|
||||
}
|
||||
return [tool.displayName, tool.description, tool.isInstalled ? localize('deploymentDialog.YesText', "Yes") : localize('deploymentDialog.NoText', "No"), tool.version ? tool.version.version : ''];
|
||||
});
|
||||
this._dialogObject.okButton.enabled = messages.length === 0;
|
||||
if (messages.length !== 0) {
|
||||
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in the debug console."));
|
||||
this._dialogObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: localize('deploymentDialog.ToolCheckFailed', "Some required tools are not installed or do not meet the minimum version requirement."),
|
||||
description: messages.join(EOL)
|
||||
};
|
||||
}
|
||||
this._toolsLoadingComponent.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private createAgreementCheckbox(agreementInfo: AgreementInfo): azdata.FlexContainer {
|
||||
const checkbox = this._view.modelBuilder.checkBox().component();
|
||||
checkbox.checked = false;
|
||||
this._toDispose.push(checkbox.onChanged(() => {
|
||||
this._agreementCheckboxChecked = checkbox.checked;
|
||||
}));
|
||||
const text = this._view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: agreementInfo.template,
|
||||
links: agreementInfo.links
|
||||
}).component();
|
||||
return createFlexContainer(this._view, [checkbox, text]);
|
||||
}
|
||||
|
||||
private getCurrentProvider(): DeploymentProvider {
|
||||
const options: { option: string, value: string }[] = [];
|
||||
|
||||
|
||||
90
extensions/resource-deployment/src/ui/wizardBase.ts
Normal file
90
extensions/resource-deployment/src/ui/wizardBase.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { WizardPageBase } from './wizardPageBase';
|
||||
import { Model } from './model';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class WizardBase<T, M extends Model> {
|
||||
private customButtons: azdata.window.Button[] = [];
|
||||
private pages: WizardPageBase<T>[] = [];
|
||||
|
||||
public wizardObject: azdata.window.Wizard;
|
||||
public toDispose: vscode.Disposable[] = [];
|
||||
public get model(): M {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
constructor(private title: string, private _model: M) {
|
||||
this.wizardObject = azdata.window.createWizard(title);
|
||||
}
|
||||
|
||||
public open(): Thenable<void> {
|
||||
this.initialize();
|
||||
this.wizardObject.customButtons = this.customButtons;
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected abstract initialize(): void;
|
||||
protected abstract onOk(): void;
|
||||
protected abstract onCancel(): void;
|
||||
|
||||
public addButton(button: azdata.window.Button) {
|
||||
this.customButtons.push(button);
|
||||
}
|
||||
|
||||
protected setPages(pages: WizardPageBase<T>[]) {
|
||||
this.wizardObject!.pages = pages.map(p => p.pageObject);
|
||||
this.pages = pages;
|
||||
this.pages.forEach((page) => {
|
||||
page.initialize();
|
||||
});
|
||||
}
|
||||
|
||||
private dispose() {
|
||||
let errorOccured = false;
|
||||
this.toDispose.forEach((disposable: vscode.Disposable) => {
|
||||
try {
|
||||
disposable.dispose();
|
||||
}
|
||||
catch (error) {
|
||||
errorOccured = true;
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
if (errorOccured) {
|
||||
vscode.window.showErrorMessage(localize('resourceDeployment.DisposableError', "Error occured while closing the wizard: {0}, open 'Debugger Console' for more information."), this.title);
|
||||
}
|
||||
}
|
||||
|
||||
public registerDisposable(disposable: vscode.Disposable): void {
|
||||
this.toDispose.push(disposable);
|
||||
}
|
||||
}
|
||||
35
extensions/resource-deployment/src/ui/wizardPageBase.ts
Normal file
35
extensions/resource-deployment/src/ui/wizardPageBase.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { Validator } from './modelViewUtils';
|
||||
|
||||
export abstract class WizardPageBase<T> {
|
||||
private _page: azdata.window.WizardPage;
|
||||
private _validators: Validator[] = [];
|
||||
|
||||
constructor(title: string, description: string, private _wizard: T) {
|
||||
this._page = azdata.window.createWizardPage(title);
|
||||
this._page.description = description;
|
||||
}
|
||||
|
||||
public get pageObject(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public get wizard(): T {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public onEnter(): void { }
|
||||
|
||||
public onLeave(): void { }
|
||||
|
||||
public abstract initialize(): void;
|
||||
|
||||
protected get validators(): Validator[] {
|
||||
return this._validators;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user