select cluster page and status update for tool when installing (#4251)

This commit is contained in:
Alan Ren
2019-03-01 11:12:57 -08:00
committed by GitHub
parent 8e52ffa30e
commit dad807d62d
7 changed files with 122 additions and 115 deletions

View File

@@ -67,7 +67,8 @@ export interface ToolInfo {
export enum ToolInstallationStatus {
Installed,
NotInstalled,
Installing
Installing,
FailedToInstall
}
export enum ClusterType {

View File

@@ -102,9 +102,10 @@ export class CreateClusterModel implements Scriptable {
return promise;
}
public installTools(): Thenable<void> {
public installTool(tool: ToolInfo): Thenable<void> {
let promise = new Promise<void>(resolve => {
setTimeout(() => {
tool.status = ToolInstallationStatus.Installed;
this._tmp_tools_installed = true;
resolve();
}, 2000);

View File

@@ -31,7 +31,7 @@ export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateCl
let selectTargetClusterPage = new SelectExistingClusterPage(this);
let summaryPage = new SummaryPage(this);
let targetClusterTypePage = new SelectTargetClusterTypePage(this);
this.setPages([targetClusterTypePage, settingsPage, clusterProfilePage, selectTargetClusterPage, summaryPage]);
this.setPages([targetClusterTypePage, selectTargetClusterPage, clusterProfilePage, settingsPage, summaryPage]);
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
this.wizardObject.generateScriptButton.hidden = false;

View File

@@ -19,6 +19,12 @@ export class ClusterProfilePage extends WizardPageBase<CreateClusterWizard> {
wizard);
}
public onEnter() {
this.wizard.wizardObject.registerNavigationValidator(() => {
return true;
});
}
protected initialize(view: sqlops.ModelView): Thenable<void> {
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.component();

View File

@@ -9,61 +9,31 @@ import * as vscode from 'vscode';
import * as os from 'os';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import { TargetClusterType } from '../../../interfaces';
import { setActiveKubeconfig } from '../../../config/config';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const ClusterTypeRadioButtonGroupName = 'SelectClusterType';
const ClusterRadioButtonGroupName = 'cluster';
export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizard> {
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
localize('bdc-create.selectTargetClusterPageDescription', 'Select an existing Kubernetes cluster or choose a cluster type you want to deploy'),
wizard);
}
private existingClusterOption: sqlops.RadioButtonComponent;
private createAksClusterOption: sqlops.RadioButtonComponent;
private pageContainer: sqlops.DivContainer;
private existingClusterControl: sqlops.FlexContainer;
private createAksClusterControl: sqlops.FlexContainer;
private clusterContextsLabel: sqlops.TextComponent;
private errorLoadingClustersLabel: sqlops.TextComponent;
private clusterContextContainer: sqlops.DivContainer;
private cards: sqlops.CardComponent[];
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
localize('bdc-create.selectTargetClusterPageDescription', 'Select the kubeconfig file and then select a cluster context from the list'),
wizard);
}
protected initialize(view: sqlops.ModelView): Thenable<void> {
let self = this;
this.wizard.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster;
this.existingClusterOption = this.createTargetTypeRadioButton(view, localize('bdc-create.existingK8sCluster', 'Existing Kubernetes cluster'), true);
this.createAksClusterOption = this.createTargetTypeRadioButton(view, localize('bdc-create.createAksCluster', 'Create new Azure Kubernetes Service cluster'));
this.existingClusterOption.onDidClick(() => {
self.pageContainer.clearItems();
self.pageContainer.addItem(self.existingClusterControl);
self.wizard.model.targetClusterType = TargetClusterType.ExistingKubernetesCluster;
});
this.createAksClusterOption.onDidClick(() => {
self.pageContainer.clearItems();
self.pageContainer.addItem(self.createAksClusterControl);
self.wizard.model.targetClusterType = TargetClusterType.NewAksCluster;
});
let optionGroup = view.modelBuilder.divContainer().withItems([this.existingClusterOption, this.createAksClusterOption],
{ CSSStyles: { 'margin-right': '30px' } }).withLayout({ width: 'auto' }).component();
this.initExistingClusterControl(view);
this.initAksClusterControl(view);
this.pageContainer = view.modelBuilder.divContainer().withItems([this.existingClusterControl]).withLayout({ width: '100%' }).component();
let container = view.modelBuilder.flexContainer().withItems([optionGroup, this.pageContainer], { flex: '0 0 auto' }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
let formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: container,
component: this.existingClusterControl,
title: ''
}
],
@@ -76,15 +46,28 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
return view.initializeModel(form);
}
private createTargetTypeRadioButton(view: sqlops.ModelView, label: string, checked: boolean = false): sqlops.RadioButtonComponent {
return view.modelBuilder.radioButton().withProperties({ label: label, name: ClusterTypeRadioButtonGroupName, checked: checked }).component();
public onEnter() {
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
let clusterSelected = this.wizard.model.selectedCluster !== undefined;
if (!clusterSelected) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.ClusterContextNotSelectedMessage', 'Please select a cluster context.'),
level: sqlops.window.MessageLevel.Error
};
}
return clusterSelected;
});
}
private initExistingClusterControl(view: sqlops.ModelView): void {
let self = this;
let sectionDescription = view.modelBuilder.text().withProperties({ value: localize('bdc-create.existingClusterSectionDescription', 'Select the cluster context you want to install the SQL Server big data cluster') }).component();
let configFileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'KubeConfig File') }).component();
let configFileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'Kube config file path') }).component();
let configFileInput = view.modelBuilder.inputBox().withProperties({ width: '300px' }).component();
configFileInput.enabled = false;
let browseFileButton = view.modelBuilder.button().withProperties({ label: localize('bdc-browseText', 'Browse'), width: '100px' }).component();
let configFileContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', alignItems: 'baseline' })
@@ -92,7 +75,7 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
this.clusterContextsLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-clusterContextsLabelText', 'Cluster Contexts') }).component();
this.errorLoadingClustersLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
this.clusterContextContainer = view.modelBuilder.divContainer().component();
this.existingClusterControl = view.modelBuilder.divContainer().withItems([sectionDescription, configFileContainer, this.clusterContextContainer], { CSSStyles: { 'margin-top': '0px' } }).component();
this.existingClusterControl = view.modelBuilder.divContainer().withItems([configFileContainer, this.clusterContextContainer], { CSSStyles: { 'margin-top': '0px' } }).component();
browseFileButton.onDidClick(async () => {
let fileUris = await vscode.window.showOpenDialog(
@@ -119,45 +102,31 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
await setActiveKubeconfig(fileUri.fsPath);
let clusters = await self.wizard.model.loadClusters();
self.cards = [];
if (clusters.length !== 0) {
self.wizard.model.selectedCluster = clusters[0];
for (let i = 0; i < clusters.length; i++) {
let cluster = clusters[i];
let card = view.modelBuilder.card().withProperties({
selected: i === 0,
label: cluster.clusterName,
descriptions: [cluster.clusterName, cluster.userName],
cardType: sqlops.CardType.ListItem,
iconPath: {
dark: self.wizard.context.asAbsolutePath('images/cluster_inverse.svg'),
light: self.wizard.context.asAbsolutePath('images/cluster.svg')
},
let options = clusters.map(cluster => {
let option = view.modelBuilder.radioButton().withProperties<sqlops.RadioButtonProperties>({
label: cluster.contextName,
checked: cluster.active,
name: ClusterRadioButtonGroupName
}).component();
card.onCardSelectedChanged(() => {
if (card.selected) {
self.cards.forEach(c => {
if (c !== card) {
c.selected = false;
}
});
self.wizard.model.selectedCluster = cluster;
}
if (cluster.active) {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
}
option.onDidClick(() => {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
});
self.cards.push(card);
}
return option;
});
self.clusterContextContainer.addItem(self.clusterContextsLabel);
self.clusterContextContainer.addItems(self.cards);
self.clusterContextContainer.addItems(options);
} else {
self.clusterContextContainer.addItem(this.errorLoadingClustersLabel);
}
});
}
private initAksClusterControl(view: sqlops.ModelView): void {
let placeholder = view.modelBuilder.text().withProperties({ value: 'AKS cluster place holder' }).component();
this.createAksClusterControl = view.modelBuilder.divContainer().withItems([placeholder]).component();
}
}
}

View File

@@ -6,7 +6,7 @@
import * as sqlops from 'sqlops';
import { WizardPageBase } from '../../wizardPageBase';
import { TargetClusterTypeInfo, ToolInstallationStatus } from '../../../interfaces';
import { TargetClusterTypeInfo, ToolInstallationStatus, ToolInfo } from '../../../interfaces';
import * as nls from 'vscode-nls';
import { CreateClusterWizard } from '../createClusterWizard';
@@ -25,6 +25,7 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
private refreshToolsButton: sqlops.window.Button;
private isValid: boolean = false;
private isLoading: boolean = false;
private requiredTools: ToolInfo[];
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterTypePageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
@@ -32,16 +33,24 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
wizard);
this.installToolsButton = sqlops.window.createButton(InstallToolsButtonText);
this.installToolsButton.hidden = true;
this.installToolsButton.onClick(() => {
this.toolsLoadingWrapper.loading = true;
this.installToolsButton.onClick(async () => {
this.wizard.wizardObject.message = null;
this.installToolsButton.label = InstallingButtonText;
this.installToolsButton.enabled = false;
this.wizard.model.installTools().then(() => {
this.installToolsButton.label = InstallToolsButtonText;
this.installToolsButton.enabled = true;
return this.updateRequiredToolStatus();
});
this.refreshToolsButton.enabled = false;
if (this.requiredTools) {
for (let i = 0; i < this.requiredTools.length; i++) {
let tool = this.requiredTools[i];
if (tool.status === ToolInstallationStatus.NotInstalled) {
tool.status = ToolInstallationStatus.Installing;
this.updateToolStatusTable();
await this.wizard.model.installTool(tool);
}
}
}
this.installToolsButton.label = InstallToolsButtonText;
this.updateRequiredToolStatus();
});
this.wizard.addButton(this.installToolsButton);
@@ -51,31 +60,11 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
this.updateRequiredToolStatus();
});
this.wizard.addButton(this.refreshToolsButton);
this.wizard.wizardObject.registerNavigationValidator(() => {
if (this.isLoading) {
let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.');
let messageLevel = sqlops.window.MessageLevel.Information;
this.wizard.wizardObject.message = {
level: messageLevel,
text: messageText
};
return false;
}
if (!this.isValid) {
let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ?
localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type') :
localize('bdc-create.MissingToolsText', 'Please install the missing tools');
this.wizard.wizardObject.message = {
level: sqlops.window.MessageLevel.Error,
text: messageText
};
}
return this.isValid;
});
}
protected initialize(view: sqlops.ModelView): Thenable<void> {
let self = this;
self.registerNavigationValidator();
return self.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
self.cards = [];
@@ -129,6 +118,31 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
this.refreshToolsButton.hidden = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = false;
this.registerNavigationValidator();
}
private registerNavigationValidator(): void {
this.wizard.wizardObject.registerNavigationValidator(() => {
if (this.isLoading) {
let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.');
let messageLevel = sqlops.window.MessageLevel.Information;
this.wizard.wizardObject.message = {
level: messageLevel,
text: messageText
};
return false;
}
if (!this.isValid) {
let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ?
localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type.') :
localize('bdc-create.MissingToolsText', 'Please install the required tools.');
this.wizard.wizardObject.message = {
level: sqlops.window.MessageLevel.Error,
text: messageText
};
}
return this.isValid;
});
}
public onLeave(): void {
@@ -136,7 +150,6 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
this.refreshToolsButton.hidden = true;
}
private createCard(view: sqlops.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): sqlops.CardComponent {
let self = this;
let card = view.modelBuilder.card().withProperties<sqlops.CardProperties>({
@@ -165,7 +178,7 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
}
self.updateRequiredToolStatus();
} else {
if (self.cards.filter(c => { return c !== card && c.selected }).length === 0) {
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
card.selected = true;
}
}
@@ -181,29 +194,38 @@ export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWiz
this.refreshToolsButton.enabled = false;
this.installToolsButton.enabled = false;
return this.wizard.model.getRequiredToolStatus().then(tools => {
this.requiredTools = tools;
this.isLoading = false;
this.toolsLoadingWrapper.loading = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = tools.filter(tool => tool.status !== ToolInstallationStatus.Installed).length !== 0;
this.isValid = !this.installToolsButton.enabled;
this.wizard.wizardObject.message = null;
let tableData = tools.map(tool => {
return [tool.name, tool.description, this.getStatusText(tool.status)];
});
this.toolsTable.data = tableData;
this.updateToolStatusTable();
});
}
private getStatusText(status: ToolInstallationStatus): string {
switch (status) {
case ToolInstallationStatus.Installed:
return localize('bdc-create.InstalledText', 'Installed');
return '✔️ ' + localize('bdc-create.InstalledText', 'Installed');
case ToolInstallationStatus.NotInstalled:
return localize('bdc-create.NotInstalledText', 'Not Installed');
return '❌ ' + localize('bdc-create.NotInstalledText', 'Not Installed');
case ToolInstallationStatus.Installing:
return localize('bdc-create.InstallingText', 'Installing');
return '⌛ ' + localize('bdc-create.InstallingText', 'Installing...');
case ToolInstallationStatus.FailedToInstall:
return '❌ ' + localize('bdc-create.FailedToInstallText', 'Install Failed');
default:
return 'unknown status';
}
}
private updateToolStatusTable(): void {
if (this.requiredTools) {
let tableData = this.requiredTools.map(tool => {
return [tool.name, tool.description, this.getStatusText(tool.status)];
});
this.toolsTable.data = tableData;
}
}
}

View File

@@ -21,4 +21,12 @@ export class SummaryPage extends WizardPageBase<CreateClusterWizard> {
let form = formBuilder.component();
return view.initializeModel(form);
}
public onEnter(): void {
this.wizard.wizardObject.generateScriptButton.hidden = false;
}
public onLeave(): void {
this.wizard.wizardObject.generateScriptButton.hidden = true;
}
}