Merge BDC error fixes into release (#8145)

* Fix BDC remember password and reprompting connection (#7957)

* Fix remember password and reprompting connection

* comment

* Fix to remember password for session

* Fix floating promises

* Add error messages to BDC dashboard page (#8103)

* Add error messages to BDC dashboard page

* Remove testing code

* PR fixes
This commit is contained in:
Charles Gagnon
2019-10-31 13:36:14 -07:00
committed by GitHub
parent 78ec3c016b
commit fbcd3c620c
12 changed files with 286 additions and 114 deletions

View File

@@ -71,6 +71,7 @@ export namespace cssStyles {
export const selectedTabDiv = { 'border-bottom': '2px solid #000' }; export const selectedTabDiv = { 'border-bottom': '2px solid #000' };
export const unselectedTabDiv = { 'border-bottom': '1px solid #ccc' }; export const unselectedTabDiv = { 'border-bottom': '1px solid #ccc' };
export const lastUpdatedText = { ...text, 'color': '#595959' }; export const lastUpdatedText = { ...text, 'color': '#595959' };
export const errorText = { ...text, 'color': 'red' };
} }
export type AuthType = 'integrated' | 'basic'; export type AuthType = 'integrated' | 'basic';

View File

@@ -88,35 +88,51 @@ class DefaultApiWrapper extends DefaultApi {
export class ClusterController { export class ClusterController {
private authPromise: Promise<Authentication>; private _authPromise: Promise<Authentication>;
private _url: string; private _url: string;
private readonly dialog: ConnectControllerDialog; private readonly _dialog: ConnectControllerDialog;
private connectionPromise: Promise<ClusterController>; private _connectionPromise: Promise<ClusterController>;
constructor(url: string, constructor(url: string,
private authType: AuthType, private _authType: AuthType,
private username?: string, private _username?: string,
private password?: string, private _password?: string,
ignoreSslVerification?: boolean ignoreSslVerification?: boolean
) { ) {
if (!url || (authType === 'basic' && (!username || !password))) { if (!url || (_authType === 'basic' && (!_username || !_password))) {
throw new Error('Missing required inputs for Cluster controller API (URL, username, password)'); throw new Error('Missing required inputs for Cluster controller API (URL, username, password)');
} }
this._url = adjustUrl(url); this._url = adjustUrl(url);
if (this.authType === 'basic') { if (this._authType === 'basic') {
this.authPromise = Promise.resolve(new BasicAuth(username, password, !!ignoreSslVerification)); this._authPromise = Promise.resolve(new BasicAuth(_username, _password, !!ignoreSslVerification));
} else { } else {
this.authPromise = this.requestTokenUsingKerberos(ignoreSslVerification); this._authPromise = this.requestTokenUsingKerberos(ignoreSslVerification);
} }
this.dialog = new ConnectControllerDialog(new ConnectControllerModel( this._dialog = new ConnectControllerDialog(new ConnectControllerModel(
{ {
url: this._url, url: this._url,
auth: this.authType, auth: this._authType,
username: this.username, username: this._username,
password: this.password password: this._password
})); }));
} }
public get url(): string {
return this._url;
}
public get authType(): AuthType {
return this._authType;
}
public get username(): string | undefined {
return this._username;
}
public get password(): string | undefined {
return this._password;
}
private async requestTokenUsingKerberos(ignoreSslVerification?: boolean): Promise<Authentication> { private async requestTokenUsingKerberos(ignoreSslVerification?: boolean): Promise<Authentication> {
let supportsKerberos = await this.verifyKerberosSupported(ignoreSslVerification); let supportsKerberos = await this.verifyKerberosSupported(ignoreSslVerification);
if (!supportsKerberos) { if (!supportsKerberos) {
@@ -166,8 +182,8 @@ export class ClusterController {
} }
private async getEndpointsImpl(self: ClusterController): Promise<IEndPointsResponse> { private async getEndpointsImpl(self: ClusterController): Promise<IEndPointsResponse> {
let auth = await self.authPromise; let auth = await self._authPromise;
let endPointApi = new BdcApiWrapper(self.username, self.password, self._url, auth); let endPointApi = new BdcApiWrapper(self._username, self._password, self._url, auth);
let options: any = {}; let options: any = {};
let result = await endPointApi.endpointsGet(options); let result = await endPointApi.endpointsGet(options);
@@ -185,8 +201,8 @@ export class ClusterController {
} }
private async getBdcStatusImpl(self: ClusterController): Promise<IBdcStatusResponse> { private async getBdcStatusImpl(self: ClusterController): Promise<IBdcStatusResponse> {
let auth = await self.authPromise; let auth = await self._authPromise;
const bdcApi = new BdcApiWrapper(self.username, self.password, self._url, auth); const bdcApi = new BdcApiWrapper(self._username, self._password, self._url, auth);
const bdcStatus = await bdcApi.getBdcStatus('', '', /*all*/ true); const bdcStatus = await bdcApi.getBdcStatus('', '', /*all*/ true);
return { return {
@@ -206,8 +222,8 @@ export class ClusterController {
} }
private async mountHdfsImpl(self: ClusterController, mountPath: string, remoteUri: string, credentials: {}): Promise<MountResponse> { private async mountHdfsImpl(self: ClusterController, mountPath: string, remoteUri: string, credentials: {}): Promise<MountResponse> {
let auth = await self.authPromise; let auth = await self._authPromise;
const api = new DefaultApiWrapper(self.username, self.password, self._url, auth); const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
const mountStatus = await api.createMount('', '', remoteUri, mountPath, credentials); const mountStatus = await api.createMount('', '', remoteUri, mountPath, credentials);
return { return {
@@ -225,8 +241,8 @@ export class ClusterController {
} }
private async getMountStatusImpl(self: ClusterController, mountPath?: string): Promise<MountStatusResponse> { private async getMountStatusImpl(self: ClusterController, mountPath?: string): Promise<MountStatusResponse> {
const auth = await self.authPromise; const auth = await self._authPromise;
const api = new DefaultApiWrapper(self.username, self.password, self._url, auth); const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
const mountStatus = await api.listMounts('', '', mountPath); const mountStatus = await api.listMounts('', '', mountPath);
return { return {
@@ -244,8 +260,8 @@ export class ClusterController {
} }
private async refreshMountImpl(self: ClusterController, mountPath: string): Promise<MountResponse> { private async refreshMountImpl(self: ClusterController, mountPath: string): Promise<MountResponse> {
const auth = await self.authPromise; const auth = await self._authPromise;
const api = new DefaultApiWrapper(self.username, self.password, self._url, auth); const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
const mountStatus = await api.refreshMount('', '', mountPath); const mountStatus = await api.refreshMount('', '', mountPath);
return { return {
@@ -263,8 +279,8 @@ export class ClusterController {
} }
private async deleteMountImpl(mountPath: string): Promise<MountResponse> { private async deleteMountImpl(mountPath: string): Promise<MountResponse> {
let auth = await this.authPromise; let auth = await this._authPromise;
const api = new DefaultApiWrapper(this.username, this.password, this._url, auth); const api = new DefaultApiWrapper(this._username, this._password, this._url, auth);
const mountStatus = await api.deleteMount('', '', mountPath); const mountStatus = await api.deleteMount('', '', mountPath);
return { return {
@@ -291,17 +307,17 @@ export class ClusterController {
// We don't want to open multiple dialogs here if multiple calls come in the same time so check // We don't want to open multiple dialogs here if multiple calls come in the same time so check
// and see if we have are actively waiting on an open dialog to return and if so then just wait // and see if we have are actively waiting on an open dialog to return and if so then just wait
// on that promise. // on that promise.
if (!this.connectionPromise) { if (!this._connectionPromise) {
this.connectionPromise = this.dialog.showDialog(); this._connectionPromise = this._dialog.showDialog();
} }
const controller = await this.connectionPromise; const controller = await this._connectionPromise;
this.connectionPromise = undefined; this._connectionPromise = undefined;
if (controller) { if (controller) {
this.username = controller.username; this._username = controller._username;
this.password = controller.password; this._password = controller._password;
this._url = controller._url; this._url = controller._url;
this.authType = controller.authType; this._authType = controller._authType;
this.authPromise = controller.authPromise; this._authPromise = controller._authPromise;
} }
return await f(this, args); return await f(this, args);
} }
@@ -378,7 +394,7 @@ export class ControllerError extends Error {
public code?: number; public code?: number;
public reason?: string; public reason?: string;
public address?: string; public address?: string;
public statusMessage?: string;
/** /**
* *
* @param error The original error to wrap * @param error The original error to wrap
@@ -391,6 +407,7 @@ export class ControllerError extends Error {
this.code = error.response.statusCode; this.code = error.response.statusCode;
this.message += `${error.response.statusMessage ? ` - ${error.response.statusMessage}` : ''}` || ''; this.message += `${error.response.statusMessage ? ` - ${error.response.statusMessage}` : ''}` || '';
this.address = error.response.url || ''; this.address = error.response.url || '';
this.statusMessage = error.response.statusMessage;
} }
else if (error.message) { else if (error.message) {
this.message += ` - ${error.message}`; this.message += ` - ${error.message}`;

View File

@@ -6,11 +6,14 @@
'use strict'; 'use strict';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { ClusterController, ControllerError } from '../controller/clusterControllerApi'; import { ClusterController, ControllerError } from '../controller/clusterControllerApi';
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider'; import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
import { TreeNode } from '../tree/treeNode'; import { TreeNode } from '../tree/treeNode';
import { AuthType } from '../constants'; import { AuthType } from '../constants';
import { ManageControllerCommand } from '../../extension';
import { BdcDashboardOptions } from './bdcDashboardModel';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -73,7 +76,8 @@ export class AddControllerDialogModel {
if (this._canceled) { if (this._canceled) {
return; return;
} }
this.treeDataProvider.addController(url, auth, username, password, rememberPassword); this.treeDataProvider.addOrUpdateController(url, auth, username, password, rememberPassword);
vscode.commands.executeCommand(ManageControllerCommand, <BdcDashboardOptions>{ url: url, auth: auth, username: username, password: password });
await this.treeDataProvider.saveControllers(); await this.treeDataProvider.saveControllers();
} }
} catch (error) { } catch (error) {

View File

@@ -8,12 +8,13 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { BdcDashboardModel, getTroubleshootNotebookUrl } from './bdcDashboardModel'; import { BdcDashboardModel, getTroubleshootNotebookUrl, BdcErrorEvent } from './bdcDashboardModel';
import { IconPathHelper, cssStyles } from '../constants'; import { IconPathHelper, cssStyles } from '../constants';
import { BdcServiceStatusPage } from './bdcServiceStatusPage'; import { BdcServiceStatusPage } from './bdcServiceStatusPage';
import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage'; import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage';
import { BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated'; import { BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated';
import { getHealthStatusDot, getServiceNameDisplayText } from '../utils'; import { getHealthStatusDot, getServiceNameDisplayText, showErrorMessage } from '../utils';
import { HdfsDialogCancelledError } from './hdfsDialogBase';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -33,6 +34,7 @@ export class BdcDashboard {
private modelView: azdata.ModelView; private modelView: azdata.ModelView;
private mainAreaContainer: azdata.FlexContainer; private mainAreaContainer: azdata.FlexContainer;
private navContainer: azdata.FlexContainer; private navContainer: azdata.FlexContainer;
private overviewPage: BdcDashboardOverviewPage;
private currentTab: NavTab; private currentTab: NavTab;
private currentPage: azdata.FlexContainer; private currentPage: azdata.FlexContainer;
@@ -43,6 +45,7 @@ export class BdcDashboard {
constructor(private title: string, private model: BdcDashboardModel) { constructor(private title: string, private model: BdcDashboardModel) {
this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus)); this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus));
this.model.onBdcError(errorEvent => this.handleError(errorEvent));
} }
public showDashboard(): void { public showDashboard(): void {
@@ -73,6 +76,7 @@ export class BdcDashboard {
}).component(); }).component();
this.refreshButton.onDidClick(async () => { this.refreshButton.onDidClick(async () => {
this.overviewPage.onRefreshStarted();
await this.doRefresh(); await this.doRefresh();
}); });
@@ -128,18 +132,19 @@ export class BdcDashboard {
const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', 'Big data cluster overview') }).component(); const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', 'Big data cluster overview') }).component();
overviewNavItemText.updateCssStyles(selectedTabCss); overviewNavItemText.updateCssStyles(selectedTabCss);
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } }); overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
const overviewPage = new BdcDashboardOverviewPage(this, this.model).create(modelView); this.overviewPage = new BdcDashboardOverviewPage(this, this.model);
this.currentPage = overviewPage; const overviewContainer: azdata.FlexContainer = this.overviewPage.create(modelView);
this.currentPage = overviewContainer;
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText }; this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } }); this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
overviewNavItemDiv.onDidClick(() => { overviewNavItemDiv.onDidClick(() => {
if (this.currentTab) { if (this.currentTab) {
this.currentTab.text.updateCssStyles(unselectedTabCss); this.currentTab.text.updateCssStyles(unselectedTabCss);
} }
this.mainAreaContainer.removeItem(this.currentPage); this.mainAreaContainer.removeItem(this.currentPage);
this.mainAreaContainer.addItem(overviewPage, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } }); this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
this.currentPage = overviewPage; this.currentPage = overviewContainer;
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText }; this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
this.currentTab.text.updateCssStyles(selectedTabCss); this.currentTab.text.updateCssStyles(selectedTabCss);
}); });
@@ -165,6 +170,17 @@ export class BdcDashboard {
this.updateServiceNavTabs(bdcStatus.services); this.updateServiceNavTabs(bdcStatus.services);
} }
private handleError(errorEvent: BdcErrorEvent): void {
if (errorEvent.errorType !== 'general') {
return;
}
// We don't want to show an error for the connection dialog being
// canceled since that's a normal case.
if (!(errorEvent.error instanceof HdfsDialogCancelledError)) {
showErrorMessage(errorEvent.error.message);
}
}
private async doRefresh(): Promise<void> { private async doRefresh(): Promise<void> {
try { try {
this.refreshButton.enabled = false; this.refreshButton.enabled = false;

View File

@@ -7,11 +7,16 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { ClusterController } from '../controller/clusterControllerApi'; import { ClusterController } from '../controller/clusterControllerApi';
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated'; import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
import { showErrorMessage, Endpoint, Service } from '../utils'; import { Endpoint, Service } from '../utils';
import { AuthType } from '../constants'; import { AuthType } from '../constants';
import { ConnectControllerDialog, ConnectControllerModel } from './connectControllerDialog';
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
export type BdcDashboardOptions = { url: string, auth: AuthType, username: string, password: string }; export type BdcDashboardOptions = { url: string, auth: AuthType, username: string, password: string };
export type BdcErrorType = 'bdcStatus' | 'bdcEndpoints' | 'general';
export type BdcErrorEvent = { error: Error, errorType: BdcErrorType };
export class BdcDashboardModel { export class BdcDashboardModel {
private _clusterController: ClusterController; private _clusterController: ClusterController;
@@ -21,12 +26,23 @@ export class BdcDashboardModel {
private _endpointsLastUpdated: Date; private _endpointsLastUpdated: Date;
private readonly _onDidUpdateEndpoints = new vscode.EventEmitter<EndpointModel[]>(); private readonly _onDidUpdateEndpoints = new vscode.EventEmitter<EndpointModel[]>();
private readonly _onDidUpdateBdcStatus = new vscode.EventEmitter<BdcStatusModel>(); private readonly _onDidUpdateBdcStatus = new vscode.EventEmitter<BdcStatusModel>();
private readonly _onBdcError = new vscode.EventEmitter<BdcErrorEvent>();
public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event; public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event;
public onDidUpdateBdcStatus = this._onDidUpdateBdcStatus.event; public onDidUpdateBdcStatus = this._onDidUpdateBdcStatus.event;
public onBdcError = this._onBdcError.event;
constructor(private options: BdcDashboardOptions, ignoreSslVerification = true) { constructor(private _options: BdcDashboardOptions, private _treeDataProvider: ControllerTreeDataProvider, ignoreSslVerification = true) {
this._clusterController = new ClusterController(options.url, options.auth, options.username, options.password, ignoreSslVerification); try {
this.refresh(); this._clusterController = new ClusterController(_options.url, _options.auth, _options.username, _options.password, ignoreSslVerification);
// tslint:disable-next-line:no-floating-promises
this.refresh();
} catch {
this.promptReconnect().then(async () => {
await this.refresh();
}).catch(error => {
this._onBdcError.fire({ error: error, errorType: 'general' });
});
}
} }
public get bdcStatus(): BdcStatusModel | undefined { public get bdcStatus(): BdcStatusModel | undefined {
@@ -46,21 +62,28 @@ export class BdcDashboardModel {
} }
public async refresh(): Promise<void> { public async refresh(): Promise<void> {
await Promise.all([ try {
this._clusterController.getBdcStatus(true).then(response => { if (!this._clusterController) {
this._bdcStatus = response.bdcStatus; // If this succeeds without error we know we have a clusterController at this point
this._bdcStatusLastUpdated = new Date(); await this.promptReconnect();
this._onDidUpdateBdcStatus.fire(this.bdcStatus); }
}),
this._clusterController.getEndPoints(true).then(response => { await Promise.all([
this._endpoints = response.endPoints || []; this._clusterController.getBdcStatus(true).then(response => {
fixEndpoints(this._endpoints); this._bdcStatus = response.bdcStatus;
this._endpointsLastUpdated = new Date(); this._bdcStatusLastUpdated = new Date();
this._onDidUpdateEndpoints.fire(this.serviceEndpoints); this._onDidUpdateBdcStatus.fire(this.bdcStatus);
}) }).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcStatus' })),
]).catch(error => { this._clusterController.getEndPoints(true).then(response => {
showErrorMessage(error); this._endpoints = response.endPoints || [];
}); fixEndpoints(this._endpoints);
this._endpointsLastUpdated = new Date();
this._onDidUpdateEndpoints.fire(this.serviceEndpoints);
}).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcEndpoints' }))
]);
} catch (error) {
this._onBdcError.fire({ error: error, errorType: 'general' });
}
} }
/** /**
@@ -81,7 +104,7 @@ export class BdcDashboardModel {
serverName: sqlServerMasterEndpoint.endpoint, serverName: sqlServerMasterEndpoint.endpoint,
databaseName: undefined, databaseName: undefined,
userName: 'sa', userName: 'sa',
password: this.options.password, password: this._options.password,
authenticationType: '', authenticationType: '',
savePassword: true, savePassword: true,
groupFullName: undefined, groupFullName: undefined,
@@ -92,6 +115,19 @@ export class BdcDashboardModel {
options: {} options: {}
}; };
} }
/**
* Opens up a dialog prompting the user to re-enter credentials for the controller
*/
private async promptReconnect(): Promise<void> {
this._clusterController = await new ConnectControllerDialog(new ConnectControllerModel(this._options)).showDialog();
this._treeDataProvider.addOrUpdateController(
this._clusterController.url,
this._clusterController.authType,
this._clusterController.username,
this._clusterController.password,
/* Remember password */false);
}
} }
/** /**

View File

@@ -3,17 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { BdcDashboardModel } from './bdcDashboardModel'; import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
import { IconPathHelper, cssStyles } from '../constants'; import { IconPathHelper, cssStyles } from '../constants';
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint } from '../utils'; import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint, getBdcStatusErrorMessage } from '../utils';
import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated'; import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
import { BdcDashboard } from './bdcDashboard'; import { BdcDashboard } from './bdcDashboard';
import { createViewDetailsButton } from './commonControls'; import { createViewDetailsButton } from './commonControls';
import { HdfsDialogCancelledError } from './hdfsDialogBase';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -31,24 +30,29 @@ const serviceEndpointRowEndpointCellWidth = 350;
const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi]; const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
type ActionItem = (vscode.MessageItem & { execute: () => void; });
export class BdcDashboardOverviewPage { export class BdcDashboardOverviewPage {
private initialized: boolean = false; private initialized: boolean = false;
private modelBuilder: azdata.ModelBuilder; private modelBuilder: azdata.ModelBuilder;
private lastUpdatedLabel: azdata.TextComponent; private lastUpdatedLabel: azdata.TextComponent;
private propertiesContainer: azdata.DivContainer;
private clusterStateLoadingComponent: azdata.LoadingComponent; private clusterStateLoadingComponent: azdata.LoadingComponent;
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent; private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
private serviceStatusRowContainer: azdata.FlexContainer; private serviceStatusRowContainer: azdata.FlexContainer;
private endpointsRowContainer: azdata.FlexContainer; private endpointsRowContainer: azdata.FlexContainer;
private endpointsDisplayContainer: azdata.DivContainer;
private serviceStatusDisplayContainer: azdata.DivContainer;
private propertiesErrorMessage: azdata.TextComponent;
private endpointsErrorMessage: azdata.TextComponent;
private serviceStatusErrorMessage: azdata.TextComponent;
constructor(private dashboard: BdcDashboard, private model: BdcDashboardModel) { constructor(private dashboard: BdcDashboard, private model: BdcDashboardModel) {
this.model.onDidUpdateEndpoints(endpoints => this.handleEndpointsUpdate(endpoints)); this.model.onDidUpdateEndpoints(endpoints => this.handleEndpointsUpdate(endpoints));
this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus)); this.model.onDidUpdateBdcStatus(bdcStatus => this.handleBdcStatusUpdate(bdcStatus));
this.model.onBdcError(error => this.handleBdcError(error));
} }
public create(view: azdata.ModelView): azdata.FlexContainer { public create(view: azdata.ModelView): azdata.FlexContainer {
@@ -69,6 +73,11 @@ export class BdcDashboardOverviewPage {
.component(); .component();
rootContainer.addItem(propertiesLabel, { CSSStyles: { 'margin-top': '15px', 'padding-left': '10px', ...cssStyles.title } }); rootContainer.addItem(propertiesLabel, { CSSStyles: { 'margin-top': '15px', 'padding-left': '10px', ...cssStyles.title } });
this.propertiesErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
rootContainer.addItem(this.propertiesErrorMessage, { flex: '0 0 auto' });
this.propertiesContainer = view.modelBuilder.divContainer().component();
// Row 1 // Row 1
const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component(); const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component();
@@ -86,7 +95,9 @@ export class BdcDashboardOverviewPage {
row1.addItem(healthStatusLabel, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } }); row1.addItem(healthStatusLabel, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } });
row1.addItem(this.clusterHealthStatusLoadingComponent, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px` } }); row1.addItem(this.clusterHealthStatusLoadingComponent, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px` } });
rootContainer.addItem(row1, { CSSStyles: { 'padding-left': '10px', 'border-bottom': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } }); this.propertiesContainer.addItem(row1, { CSSStyles: { 'padding-left': '10px', 'border-bottom': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
rootContainer.addItem(this.propertiesContainer, { flex: '0 0 auto' });
// ############ // ############
// # OVERVIEW # // # OVERVIEW #
@@ -125,6 +136,8 @@ export class BdcDashboardOverviewPage {
serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${overviewHealthStatusCellWidthPx}px`, 'min-width': `${overviewHealthStatusCellWidthPx}px` } }); serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${overviewHealthStatusCellWidthPx}px`, 'min-width': `${overviewHealthStatusCellWidthPx}px` } });
overviewContainer.addItem(serviceStatusHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } }); overviewContainer.addItem(serviceStatusHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
this.serviceStatusDisplayContainer = view.modelBuilder.divContainer().component();
// Service Status row container // Service Status row container
this.serviceStatusRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); this.serviceStatusRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
// Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly // Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly
@@ -135,7 +148,12 @@ export class BdcDashboardOverviewPage {
.component(); .component();
this.serviceStatusRowContainer.addItem(serviceStatusRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } }); this.serviceStatusRowContainer.addItem(serviceStatusRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
overviewContainer.addItem(this.serviceStatusRowContainer); this.serviceStatusErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
overviewContainer.addItem(this.serviceStatusErrorMessage);
this.serviceStatusDisplayContainer.addItem(this.serviceStatusRowContainer);
overviewContainer.addItem(this.serviceStatusDisplayContainer);
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' }); rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
// ##################### // #####################
@@ -147,6 +165,8 @@ export class BdcDashboardOverviewPage {
.component(); .component();
rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } }); rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
this.endpointsErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component(); const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
// Service endpoints header row // Service endpoints header row
@@ -157,6 +177,7 @@ export class BdcDashboardOverviewPage {
endpointsHeaderRow.addItem(endpointsEndpointHeaderCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px`, ...cssStyles.tableHeader } }); endpointsHeaderRow.addItem(endpointsEndpointHeaderCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px`, ...cssStyles.tableHeader } });
endpointsContainer.addItem(endpointsHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } }); endpointsContainer.addItem(endpointsHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
this.endpointsDisplayContainer = view.modelBuilder.divContainer().component();
this.endpointsRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); this.endpointsRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
// Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly // Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly
// messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component // messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component
@@ -166,8 +187,9 @@ export class BdcDashboardOverviewPage {
.component(); .component();
this.endpointsRowContainer.addItem(endpointRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } }); this.endpointsRowContainer.addItem(endpointRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
endpointsContainer.addItem(this.endpointsRowContainer); this.endpointsDisplayContainer.addItem(this.endpointsRowContainer);
endpointsContainer.addItem(this.endpointsErrorMessage);
endpointsContainer.addItem(this.endpointsDisplayContainer);
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' }); rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
this.initialized = true; this.initialized = true;
@@ -179,10 +201,22 @@ export class BdcDashboardOverviewPage {
return rootContainer; return rootContainer;
} }
public onRefreshStarted(): void {
this.propertiesErrorMessage.display = 'none';
this.serviceStatusErrorMessage.display = 'none';
this.endpointsErrorMessage.display = 'none';
this.serviceStatusDisplayContainer.display = undefined;
this.propertiesContainer.display = undefined;
this.endpointsDisplayContainer.display = undefined;
}
private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void { private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void {
if (!this.initialized || !bdcStatus) { if (!this.initialized || !bdcStatus) {
return; return;
} }
this.lastUpdatedLabel.value = this.lastUpdatedLabel.value =
localize('bdc.dashboard.lastUpdated', "Last Updated : {0}", localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
this.model.bdcStatusLastUpdated ? this.model.bdcStatusLastUpdated ?
@@ -225,6 +259,44 @@ export class BdcDashboardOverviewPage {
}); });
} }
private handleBdcError(errorEvent: BdcErrorEvent): void {
if (errorEvent.errorType === 'bdcEndpoints') {
const errorMessage = localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", errorEvent.error.message);
this.showEndpointsError(errorMessage);
} else if (errorEvent.errorType === 'bdcStatus') {
this.showBdcStatusError(getBdcStatusErrorMessage(errorEvent.error));
} else {
this.handleGeneralError(errorEvent.error);
}
}
private showBdcStatusError(errorMessage: string): void {
this.serviceStatusDisplayContainer.display = 'none';
this.propertiesContainer.display = 'none';
this.serviceStatusErrorMessage.value = errorMessage;
this.serviceStatusErrorMessage.display = undefined;
this.propertiesErrorMessage.value = errorMessage;
this.propertiesErrorMessage.display = undefined;
}
private showEndpointsError(errorMessage: string): void {
this.endpointsDisplayContainer.display = 'none';
this.endpointsErrorMessage.display = undefined;
this.endpointsErrorMessage.value = errorMessage;
}
private handleGeneralError(error: Error): void {
if (error instanceof HdfsDialogCancelledError) {
const errorMessage = localize('bdc.dashboard.noConnection', "The dashboard requires a connection. Please click retry to enter your credentials.");
this.showBdcStatusError(errorMessage);
this.showEndpointsError(errorMessage);
} else {
const errorMessage = localize('bdc.dashboard.unexpectedError', "Unexpected error occurred: {0}", error.message);
this.showBdcStatusError(errorMessage);
this.showEndpointsError(errorMessage);
}
}
private createServiceStatusRow(container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void { private createServiceStatusRow(container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void {
const serviceStatusRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component(); const serviceStatusRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
const statusIconCell = this.modelBuilder.text().withProperties({ value: getHealthStatusIcon(serviceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component(); const statusIconCell = this.modelBuilder.text().withProperties({ value: getHealthStatusIcon(serviceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component();

View File

@@ -28,6 +28,12 @@ export interface HdfsDialogProperties {
password?: string; password?: string;
} }
export class HdfsDialogCancelledError extends Error {
constructor(message: string = 'Dialog cancelled') {
super(message);
}
}
export abstract class HdfsDialogModelBase<T extends HdfsDialogProperties, R> { export abstract class HdfsDialogModelBase<T extends HdfsDialogProperties, R> {
protected _canceled = false; protected _canceled = false;
private _authTypes: azdata.CategoryValue[]; private _authTypes: azdata.CategoryValue[];
@@ -87,7 +93,7 @@ export abstract class HdfsDialogModelBase<T extends HdfsDialogProperties, R> {
throw new Error(localize('mount.hdfs.loginerror1', "Login to controller failed")); throw new Error(localize('mount.hdfs.loginerror1', "Login to controller failed"));
} }
} catch (err) { } catch (err) {
throw new Error(localize('mount.hdfs.loginerror2', "Login to controller failed: {0}", err.message)); throw new Error(localize('mount.hdfs.loginerror2', "Login to controller failed: {0}", err.statusMessage || err.message));
} }
return controller; return controller;
} }
@@ -224,7 +230,7 @@ export abstract class HdfsDialogBase<T extends HdfsDialogProperties, R> {
if (this.model && this.model.onCancel) { if (this.model && this.model.onCancel) {
await this.model.onCancel(); await this.model.onCancel();
} }
this.returnPromise.reject(new Error('Dialog cancelled')); this.returnPromise.reject(new HdfsDialogCancelledError());
} }
protected async reportError(error: any): Promise<void> { protected async reportError(error: any): Promise<void> {

View File

@@ -45,7 +45,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
return this.root.getChildren(); return this.root.getChildren();
} }
this.loadSavedControllers(); await this.loadSavedControllers();
return [new LoadingControllerNode()]; return [new LoadingControllerNode()];
} }
@@ -57,7 +57,15 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
this._onDidChangeTreeData.fire(node); this._onDidChangeTreeData.fire(node);
} }
public addController( /**
* Creates or updates a node in the tree with the specified connection information
* @param url The URL for the BDC management endpoint
* @param auth The type of auth to use
* @param username The username (if basic auth)
* @param password The password (if basic auth)
* @param rememberPassword Whether to store the password in the password store when saving
*/
public addOrUpdateController(
url: string, url: string,
auth: AuthType, auth: AuthType,
username: string, username: string,
@@ -65,7 +73,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
rememberPassword: boolean rememberPassword: boolean
): void { ): void {
this.removeNonControllerNodes(); this.removeNonControllerNodes();
this.root.addControllerNode(url, auth, username, password, rememberPassword); this.root.addOrUpdateControllerNode(url, auth, username, password, rememberPassword);
this.notifyNodeChanged(); this.notifyNodeChanged();
} }
@@ -113,7 +121,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
this.root.clearChildren(); this.root.clearChildren();
let controllers: IControllerInfoSlim[] = this.memento.get('controllers'); let controllers: IControllerInfoSlim[] = this.memento.get('controllers');
if (controllers) { if (controllers) {
for (let c of controllers) { for (const c of controllers) {
let password = undefined; let password = undefined;
if (c.rememberPassword) { if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username); password = await this.getPassword(c.url, c.username);
@@ -138,18 +146,18 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
} }
public async saveControllers(): Promise<void> { public async saveControllers(): Promise<void> {
let controllers = this.root.children.map((e): IControllerInfoSlim => { const controllers = this.root.children.map((e): IControllerInfoSlim => {
let controller = e as ControllerNode; const controller = e as ControllerNode;
return { return {
url: controller.url, url: controller.url,
auth: controller.auth, auth: controller.auth,
username: controller.username, username: controller.username,
password: controller.password, password: controller.password,
rememberPassword: !!controller.rememberPassword rememberPassword: controller.rememberPassword
}; };
}); });
let controllersWithoutPassword = controllers.map((e): IControllerInfoSlim => { const controllersWithoutPassword = controllers.map((e): IControllerInfoSlim => {
return { return {
url: e.url, url: e.url,
auth: e.auth, auth: e.auth,
@@ -164,7 +172,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
showErrorMessage(error); showErrorMessage(error);
} }
for (let e of controllers) { for (const e of controllers) {
if (e.rememberPassword) { if (e.rememberPassword) {
await this.savePassword(e.url, e.username, e.password); await this.savePassword(e.url, e.username, e.password);
} else { } else {
@@ -187,7 +195,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
return result; return result;
} }
private async getPassword(url: string, username: string): Promise<string> { private async getPassword(url: string, username: string): Promise<string | undefined> {
let provider = await this.getCredentialProvider(); let provider = await this.getCredentialProvider();
let id = this.createId(url, username); let id = this.createId(url, username);
let credential = await provider.readCredential(id); let credential = await provider.readCredential(id);

View File

@@ -103,7 +103,15 @@ export class ControllerRootNode extends ControllerTreeNode {
return this.children as ControllerNode[]; return this.children as ControllerNode[];
} }
public addControllerNode( /**
* Creates or updates a node in the tree with the specified connection information
* @param url The URL for the BDC management endpoint
* @param auth The type of auth to use
* @param username The username (if basic auth)
* @param password The password (if basic auth)
* @param rememberPassword Whether to store the password in the password store when saving
*/
public addOrUpdateControllerNode(
url: string, url: string,
auth: AuthType, auth: AuthType,
username: string, username: string,
@@ -201,6 +209,10 @@ export class ControllerNode extends ControllerTreeNode {
this._password = pw; this._password = pw;
} }
public set label(label: string) {
super.label = label || this.generateLabel();
}
public get rememberPassword() { public get rememberPassword() {
return this._rememberPassword; return this._rememberPassword;
} }
@@ -209,10 +221,6 @@ export class ControllerNode extends ControllerTreeNode {
this._rememberPassword = rememberPassword; this._rememberPassword = rememberPassword;
} }
public set label(label: string) {
super.label = label || this.generateLabel();
}
private generateLabel(): string { private generateLabel(): string {
let label = `controller: ${ControllerNode.toIpAndPort(this._url)}`; let label = `controller: ${ControllerNode.toIpAndPort(this._url)}`;
if (this._auth === 'basic') { if (this._auth === 'basic') {

View File

@@ -269,3 +269,7 @@ export function getControllerEndpoint(serverInfo: azdata.ServerInfo): string | u
} }
return undefined; return undefined;
} }
export function getBdcStatusErrorMessage(error: Error): string {
return localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", error.message);
}

View File

@@ -20,13 +20,13 @@ import { getControllerEndpoint } from './bigDataCluster/utils';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const AddControllerCommand = 'bigDataClusters.command.addController'; export const AddControllerCommand = 'bigDataClusters.command.addController';
const DeleteControllerCommand = 'bigDataClusters.command.deleteController'; export const DeleteControllerCommand = 'bigDataClusters.command.deleteController';
const RefreshControllerCommand = 'bigDataClusters.command.refreshController'; export const RefreshControllerCommand = 'bigDataClusters.command.refreshController';
const ManageControllerCommand = 'bigDataClusters.command.manageController'; export const ManageControllerCommand = 'bigDataClusters.command.manageController';
const MountHdfsCommand = 'bigDataClusters.command.mount'; export const MountHdfsCommand = 'bigDataClusters.command.mount';
const RefreshMountCommand = 'bigDataClusters.command.refreshmount'; export const RefreshMountCommand = 'bigDataClusters.command.refreshmount';
const DeleteMountCommand = 'bigDataClusters.command.deletemount'; export const DeleteMountCommand = 'bigDataClusters.command.deletemount';
const endpointNotFoundError = localize('mount.error.endpointNotFound', "Controller endpoint information was not found"); const endpointNotFoundError = localize('mount.error.endpointNotFound', "Controller endpoint information was not found");
@@ -51,8 +51,8 @@ function registerCommands(context: vscode.ExtensionContext, treeDataProvider: Co
runThrottledAction(AddControllerCommand, () => addBdcController(treeDataProvider, node)); runThrottledAction(AddControllerCommand, () => addBdcController(treeDataProvider, node));
}); });
vscode.commands.registerCommand(DeleteControllerCommand, (node: TreeNode) => { vscode.commands.registerCommand(DeleteControllerCommand, async (node: TreeNode) => {
deleteBdcController(treeDataProvider, node); await deleteBdcController(treeDataProvider, node);
}); });
vscode.commands.registerCommand(RefreshControllerCommand, (node: TreeNode) => { vscode.commands.registerCommand(RefreshControllerCommand, (node: TreeNode) => {
@@ -64,7 +64,7 @@ function registerCommands(context: vscode.ExtensionContext, treeDataProvider: Co
vscode.commands.registerCommand(ManageControllerCommand, async (info: ControllerNode | BdcDashboardOptions) => { vscode.commands.registerCommand(ManageControllerCommand, async (info: ControllerNode | BdcDashboardOptions) => {
const title: string = `${localize('bdc.dashboard.title', "Big Data Cluster Dashboard -")} ${ControllerNode.toIpAndPort(info.url)}`; const title: string = `${localize('bdc.dashboard.title', "Big Data Cluster Dashboard -")} ${ControllerNode.toIpAndPort(info.url)}`;
const dashboard: BdcDashboard = new BdcDashboard(title, new BdcDashboardModel(info)); const dashboard: BdcDashboard = new BdcDashboard(title, new BdcDashboardModel(info, treeDataProvider));
dashboard.showDashboard(); dashboard.showDashboard();
}); });
@@ -80,26 +80,26 @@ function registerCommands(context: vscode.ExtensionContext, treeDataProvider: Co
} }
async function mountHdfs(explorerContext?: azdata.ObjectExplorerContext): Promise<void> { async function mountHdfs(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
let mountProps = await getMountProps(explorerContext); const mountProps = await getMountProps(explorerContext);
if (mountProps) { if (mountProps) {
let dialog = new MountHdfsDialog(new MountHdfsModel(mountProps)); const dialog = new MountHdfsDialog(new MountHdfsModel(mountProps));
dialog.showDialog(); await dialog.showDialog();
} }
} }
async function refreshMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> { async function refreshMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
let mountProps = await getMountProps(explorerContext); const mountProps = await getMountProps(explorerContext);
if (mountProps) { if (mountProps) {
let dialog = new RefreshMountDialog(new RefreshMountModel(mountProps)); const dialog = new RefreshMountDialog(new RefreshMountModel(mountProps));
dialog.showDialog(); await dialog.showDialog();
} }
} }
async function deleteMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> { async function deleteMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
let mountProps = await getMountProps(explorerContext); const mountProps = await getMountProps(explorerContext);
if (mountProps) { if (mountProps) {
let dialog = new DeleteMountDialog(new DeleteMountModel(mountProps)); const dialog = new DeleteMountDialog(new DeleteMountModel(mountProps));
dialog.showDialog(); await dialog.showDialog();
} }
} }
@@ -169,15 +169,15 @@ async function deleteBdcController(treeDataProvider: ControllerTreeDataProvider,
let result = await vscode.window.showQuickPick(Object.keys(choices), options); let result = await vscode.window.showQuickPick(Object.keys(choices), options);
let remove: boolean = !!(result && choices[result]); let remove: boolean = !!(result && choices[result]);
if (remove) { if (remove) {
deleteControllerInternal(treeDataProvider, controllerNode); await deleteControllerInternal(treeDataProvider, controllerNode);
} }
return remove; return remove;
} }
function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): void { async function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): Promise<void> {
let deleted = treeDataProvider.deleteController(controllerNode.url, controllerNode.auth, controllerNode.username); const deleted = treeDataProvider.deleteController(controllerNode.url, controllerNode.auth, controllerNode.username);
if (deleted) { if (deleted) {
treeDataProvider.saveControllers(); await treeDataProvider.saveControllers();
} }
} }

View File

@@ -23,7 +23,7 @@ class DivItem {
@Component({ @Component({
template: ` template: `
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" (click)="onClick()" (keyup)="onKey($event)"> <div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" [style.display]="display" (click)="onClick()" (keyup)="onKey($event)">
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)"> <div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore"> <model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper> </model-component-wrapper>