mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Refactor BDC dashboard to use ModelView Dashboard (#10327)
* WIP refactor * More changes * More changes * Remove unused styles * Uncomment code
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="32" height="32">
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 94 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="32" height="32">
|
||||||
|
<path fill="#FF0000" d="M960 256q115 0 221 30t198 84 169 130 130 168 84 199 30 221q0 115-30 221t-84 198-130 169-168 130-199 84-221 30q-115 0-221-30t-198-84-169-130-130-168-84-199-30-221q0-115 30-221t84-198 130-169 168-130 199-84 221-30z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 337 B |
@@ -26,6 +26,8 @@ export class IconPathHelper {
|
|||||||
public static status_ok: IconPath;
|
public static status_ok: IconPath;
|
||||||
public static status_warning: IconPath;
|
public static status_warning: IconPath;
|
||||||
public static notebook: IconPath;
|
public static notebook: IconPath;
|
||||||
|
public static status_circle_red: IconPath;
|
||||||
|
public static status_circle_blank: IconPath;
|
||||||
|
|
||||||
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
||||||
IconPathHelper.extensionContext = extensionContext;
|
IconPathHelper.extensionContext = extensionContext;
|
||||||
@@ -53,6 +55,14 @@ export class IconPathHelper {
|
|||||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/notebook.svg'),
|
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/notebook.svg'),
|
||||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/notebook_inverse.svg')
|
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/notebook_inverse.svg')
|
||||||
};
|
};
|
||||||
|
IconPathHelper.status_circle_red = {
|
||||||
|
light: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_red.svg'),
|
||||||
|
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_red.svg')
|
||||||
|
};
|
||||||
|
IconPathHelper.status_circle_blank = {
|
||||||
|
light: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_blank.svg'),
|
||||||
|
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_blank.svg')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,13 +70,6 @@ export namespace cssStyles {
|
|||||||
export const title = { 'font-size': '14px', 'font-weight': '600' };
|
export const title = { 'font-size': '14px', 'font-weight': '600' };
|
||||||
export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' };
|
export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' };
|
||||||
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
|
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
|
||||||
export const overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' };
|
|
||||||
export const nonSelectableText = { ...cssStyles.text, 'user-select': 'none' };
|
|
||||||
export const tabHeaderText = { 'margin-block-start': '2px', 'margin-block-end': '0px', 'user-select': 'none' };
|
|
||||||
export const selectedResourceHeaderTab = { 'font-weight': 'bold', 'color': '' };
|
|
||||||
export const unselectedResourceHeaderTab = { 'font-weight': '', 'color': '#0078d4' };
|
|
||||||
export const selectedTabDiv = { 'border-bottom': '2px solid #000' };
|
|
||||||
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 const errorText = { ...text, 'color': 'red' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,176 +4,62 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
||||||
import { BdcDashboardModel, getTroubleshootNotebookUrl, BdcErrorEvent } from './bdcDashboardModel';
|
|
||||||
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, showErrorMessage } from '../utils';
|
import { getServiceNameDisplayText, showErrorMessage, getHealthStatusDotIcon } from '../utils';
|
||||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
import { InitializingComponent } from './intializingComponent';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
const navWidth = '200px';
|
export class BdcDashboard extends InitializingComponent {
|
||||||
|
|
||||||
const selectedTabCss = { 'font-weight': 'bold' };
|
private dashboard: azdata.window.ModelViewDashboard;
|
||||||
const unselectedTabCss = { 'font-weight': '' };
|
|
||||||
|
|
||||||
type NavTab = { serviceName: string, div: azdata.DivContainer, dot: azdata.TextComponent, text: azdata.TextComponent };
|
|
||||||
|
|
||||||
export class BdcDashboard extends BdcDashboardPage {
|
|
||||||
|
|
||||||
private dashboard: azdata.workspace.ModelViewEditor;
|
|
||||||
|
|
||||||
private modelView: azdata.ModelView;
|
private modelView: azdata.ModelView;
|
||||||
private mainAreaContainer: azdata.FlexContainer;
|
|
||||||
private navContainer: azdata.FlexContainer;
|
|
||||||
private overviewPage: BdcDashboardOverviewPage;
|
|
||||||
|
|
||||||
private currentTab: NavTab;
|
private createdServicePages: Map<string, azdata.DashboardTab> = new Map<string, azdata.DashboardTab>();
|
||||||
private currentPageContainer: azdata.FlexContainer;
|
private overviewTab: azdata.DashboardTab;
|
||||||
|
|
||||||
private refreshButton: azdata.ButtonComponent;
|
|
||||||
|
|
||||||
private serviceTabPageMapping = new Map<string, { navTab: NavTab, servicePage: BdcServiceStatusPage }>();
|
|
||||||
|
|
||||||
constructor(private title: string, private model: BdcDashboardModel) {
|
constructor(private title: string, private model: BdcDashboardModel) {
|
||||||
super();
|
super();
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
this.model.onBdcError(errorEvent => this.eventuallyRunOnInitialized(() => this.handleError(errorEvent)));
|
model.onBdcError(errorEvent => this.eventuallyRunOnInitialized(() => this.handleError(errorEvent)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public showDashboard(): void {
|
public async showDashboard(): Promise<void> {
|
||||||
this.createDashboard();
|
await this.createDashboard();
|
||||||
this.dashboard.openEditor();
|
await this.dashboard.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private createDashboard(): void {
|
private async createDashboard(): Promise<void> {
|
||||||
this.dashboard = azdata.workspace.createModelViewEditor(this.title, { retainContextWhenHidden: true, supportsSave: false });
|
this.dashboard = azdata.window.createModelViewDashboard(this.title, { alwaysShowTabs: true });
|
||||||
this.dashboard.registerContent(async (modelView: azdata.ModelView) => {
|
this.dashboard.registerTabs(async (modelView: azdata.ModelView) => {
|
||||||
this.modelView = modelView;
|
this.modelView = modelView;
|
||||||
const rootContainer = modelView.modelBuilder.flexContainer().withLayout(
|
|
||||||
{
|
|
||||||
flexFlow: 'column',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
// ###########
|
const overviewPage = new BdcDashboardOverviewPage(this.model, modelView);
|
||||||
// # TOOLBAR #
|
this.overviewTab = {
|
||||||
// ###########
|
title: loc.bdcOverview,
|
||||||
|
id: 'overview-tab',
|
||||||
// Refresh button
|
content: overviewPage.container,
|
||||||
this.refreshButton = modelView.modelBuilder.button()
|
toolbar: overviewPage.toolbarContainer
|
||||||
.withProperties<azdata.ButtonProperties>({
|
};
|
||||||
label: loc.refresh,
|
return [
|
||||||
iconPath: IconPathHelper.refresh
|
this.overviewTab
|
||||||
}).component();
|
];
|
||||||
|
|
||||||
this.refreshButton.onDidClick(async () => {
|
|
||||||
this.overviewPage.onRefreshStarted();
|
|
||||||
await this.doRefresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
const openTroubleshootNotebookButton = modelView.modelBuilder.button()
|
|
||||||
.withProperties<azdata.ButtonProperties>({
|
|
||||||
label: loc.troubleshoot,
|
|
||||||
iconPath: IconPathHelper.notebook
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
openTroubleshootNotebookButton.onDidClick(() => {
|
|
||||||
vscode.commands.executeCommand('books.sqlserver2019', getTroubleshootNotebookUrl(this.currentTab.serviceName));
|
|
||||||
});
|
|
||||||
|
|
||||||
const toolbarContainer = modelView.modelBuilder.toolbarContainer()
|
|
||||||
.withToolbarItems(
|
|
||||||
[
|
|
||||||
{ component: this.refreshButton },
|
|
||||||
{ component: openTroubleshootNotebookButton }
|
|
||||||
]
|
|
||||||
).component();
|
|
||||||
|
|
||||||
rootContainer.addItem(toolbarContainer, { flex: '0 0 auto' });
|
|
||||||
|
|
||||||
// #############
|
|
||||||
// # MAIN AREA #
|
|
||||||
// #############
|
|
||||||
|
|
||||||
this.mainAreaContainer = modelView.modelBuilder.flexContainer().withLayout(
|
|
||||||
{
|
|
||||||
flexFlow: 'row',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
rootContainer.addItem(this.mainAreaContainer, { flex: '0 0 100%' });
|
|
||||||
|
|
||||||
// #################
|
|
||||||
// # NAV CONTAINER #
|
|
||||||
// #################
|
|
||||||
|
|
||||||
this.navContainer = modelView.modelBuilder.flexContainer().withLayout(
|
|
||||||
{
|
|
||||||
flexFlow: 'column',
|
|
||||||
width: navWidth,
|
|
||||||
height: '100%'
|
|
||||||
}
|
|
||||||
).withProperties({
|
|
||||||
ariaRole: 'tablist'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.mainAreaContainer.addItem(this.navContainer, { flex: `0 0 ${navWidth}`, CSSStyles: { 'padding': '0 20px 0 20px', 'border-right': 'solid 1px #ccc' } });
|
|
||||||
|
|
||||||
// Overview nav item - this will be the initial page
|
|
||||||
const overviewNavItemDiv = modelView.modelBuilder
|
|
||||||
.divContainer()
|
|
||||||
.withLayout({ width: navWidth, height: '30px' })
|
|
||||||
.withProperties<azdata.DivContainerProperties>({
|
|
||||||
clickable: true,
|
|
||||||
ariaRole: 'tab',
|
|
||||||
ariaSelected: true
|
|
||||||
}).component();
|
|
||||||
const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: loc.bdcOverview }).component();
|
|
||||||
overviewNavItemText.updateCssStyles(selectedTabCss);
|
|
||||||
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
|
|
||||||
this.overviewPage = new BdcDashboardOverviewPage(this, this.model);
|
|
||||||
const overviewContainer: azdata.FlexContainer = this.overviewPage.create(modelView);
|
|
||||||
this.currentPageContainer = overviewContainer;
|
|
||||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
|
||||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
|
||||||
|
|
||||||
overviewNavItemDiv.onDidClick(() => {
|
|
||||||
if (this.currentTab) {
|
|
||||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
|
||||||
this.currentTab.div.ariaSelected = false;
|
|
||||||
}
|
|
||||||
this.mainAreaContainer.removeItem(this.currentPageContainer);
|
|
||||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
|
||||||
this.currentPageContainer = overviewContainer;
|
|
||||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
|
||||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
|
||||||
this.currentTab.div.ariaSelected = true;
|
|
||||||
});
|
|
||||||
this.navContainer.addItem(overviewNavItemDiv, { flex: '0 0 auto' });
|
|
||||||
|
|
||||||
const clusterDetailsHeader = modelView.modelBuilder.text().withProperties({ value: loc.clusterDetails, CSSStyles: { 'margin-block-end': '0px' } }).component();
|
|
||||||
this.navContainer.addItem(clusterDetailsHeader, { CSSStyles: { 'user-select': 'none', 'font-weight': 'bold', 'border-bottom': 'solid 1px #ccc', 'margin-bottom': '10px' } });
|
|
||||||
|
|
||||||
await modelView.initializeModel(rootContainer);
|
|
||||||
|
|
||||||
this.initialized = true;
|
|
||||||
|
|
||||||
// Now that we've created the UI load data from the model in case it already had data
|
|
||||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
|
||||||
});
|
});
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
// Now that we've created the UI load data from the model in case it already had data
|
||||||
|
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
||||||
if (!bdcStatus) {
|
if (!bdcStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.updateServiceNavTabs(bdcStatus.services);
|
this.updateServicePages(bdcStatus.services);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleError(errorEvent: BdcErrorEvent): void {
|
private handleError(errorEvent: BdcErrorEvent): void {
|
||||||
@@ -187,77 +73,34 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doRefresh(): Promise<void> {
|
|
||||||
try {
|
|
||||||
this.refreshButton.enabled = false;
|
|
||||||
await this.model.refresh();
|
|
||||||
} finally {
|
|
||||||
this.refreshButton.enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the current navigation tab to the one corresponding to the specified service
|
* Update the service tab pages, creating any new ones as necessary
|
||||||
* @param serviceName The name of the service to switch to the tab of
|
|
||||||
*/
|
*/
|
||||||
public switchToServiceTab(serviceName: string): void {
|
private updateServicePages(services?: ServiceStatusModel[]): void {
|
||||||
const tabPageMapping = this.serviceTabPageMapping.get(serviceName);
|
|
||||||
if (!tabPageMapping) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.currentTab) {
|
|
||||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
|
||||||
this.currentTab.div.ariaSelected = false;
|
|
||||||
}
|
|
||||||
this.mainAreaContainer.removeItem(this.currentPageContainer);
|
|
||||||
this.mainAreaContainer.addItem(tabPageMapping.servicePage.container, { CSSStyles: { 'margin': '0 20px 0 20px' } });
|
|
||||||
this.currentPageContainer = tabPageMapping.servicePage.container;
|
|
||||||
this.currentTab = tabPageMapping.navTab;
|
|
||||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
|
||||||
this.currentTab.div.ariaSelected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to update the navigation tabs for the services when we get a status update
|
|
||||||
*/
|
|
||||||
private updateServiceNavTabs(services?: ServiceStatusModel[]): void {
|
|
||||||
if (services) {
|
if (services) {
|
||||||
// Add a nav item for each service
|
// Create a service page for each new service. We currently don't support services being removed.
|
||||||
services.forEach(s => {
|
services.forEach(s => {
|
||||||
const existingTabPage = this.serviceTabPageMapping.get(s.serviceName);
|
const existingPage = this.createdServicePages.get(s.serviceName);
|
||||||
if (existingTabPage) {
|
if (existingPage) {
|
||||||
// We've already created the tab and page for this service, just update the tab health status dot
|
existingPage.icon = getHealthStatusDotIcon(s.healthStatus);
|
||||||
existingTabPage.navTab.dot.value = getHealthStatusDot(s.healthStatus);
|
|
||||||
} else {
|
} else {
|
||||||
// New service - create the page and tab
|
|
||||||
const navItem = createServiceNavTab(this.modelView.modelBuilder, s);
|
|
||||||
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView);
|
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView);
|
||||||
this.serviceTabPageMapping.set(s.serviceName, { navTab: navItem, servicePage: serviceStatusPage });
|
const newTab = <azdata.Tab>{
|
||||||
navItem.div.onDidClick(() => {
|
title: getServiceNameDisplayText(s.serviceName),
|
||||||
this.switchToServiceTab(s.serviceName);
|
id: s.serviceName,
|
||||||
});
|
icon: getHealthStatusDotIcon(s.healthStatus),
|
||||||
this.navContainer.addItem(navItem.div, { flex: '0 0 auto' });
|
content: serviceStatusPage.container,
|
||||||
|
toolbar: serviceStatusPage.toolbarContainer
|
||||||
|
};
|
||||||
|
this.createdServicePages.set(s.serviceName, newTab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.dashboard.updateTabs([
|
||||||
|
this.overviewTab,
|
||||||
|
{
|
||||||
|
title: loc.clusterDetails,
|
||||||
|
tabs: Array.from(this.createdServicePages.values())
|
||||||
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceStatus: ServiceStatusModel): NavTab {
|
|
||||||
const div = modelBuilder.divContainer()
|
|
||||||
.withLayout({
|
|
||||||
width: navWidth,
|
|
||||||
height: '30px',
|
|
||||||
})
|
|
||||||
.withProperties<azdata.DivContainerProperties>({
|
|
||||||
clickable: true,
|
|
||||||
ariaRole: 'tab'
|
|
||||||
}).component();
|
|
||||||
const innerContainer = modelBuilder.flexContainer().withLayout({ width: navWidth, height: '30px', flexFlow: 'row' }).component();
|
|
||||||
const dot = modelBuilder.text().withProperties({ value: getHealthStatusDot(serviceStatus.healthStatus), CSSStyles: { 'color': 'red', 'font-size': '40px', 'width': '20px', ...cssStyles.nonSelectableText } }).component();
|
|
||||||
innerContainer.addItem(dot, { flex: '0 0 auto' });
|
|
||||||
const text = modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { ...cssStyles.tabHeaderText } }).component();
|
|
||||||
innerContainer.addItem(text, { flex: '0 0 auto' });
|
|
||||||
div.addItem(innerContainer);
|
|
||||||
return { serviceName: serviceStatus.serviceName, div: div, dot: dot, text: text };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ export class BdcDashboardModel {
|
|||||||
constructor(private _options: BdcDashboardOptions, private _treeDataProvider: ControllerTreeDataProvider) {
|
constructor(private _options: BdcDashboardOptions, private _treeDataProvider: ControllerTreeDataProvider) {
|
||||||
try {
|
try {
|
||||||
this._clusterController = new ClusterController(_options.url, _options.auth, _options.username, _options.password);
|
this._clusterController = new ClusterController(_options.url, _options.auth, _options.username, _options.password);
|
||||||
// tslint:disable-next-line:no-floating-promises
|
this.refresh().catch(e => console.log(`Unexpected error refreshing BdcModel ${e instanceof Error ? e.message : e}`));
|
||||||
this.refresh();
|
|
||||||
} catch {
|
} catch {
|
||||||
this.promptReconnect().then(async () => {
|
this.promptReconnect().then(async () => {
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
|
|||||||
@@ -9,26 +9,17 @@ import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
|||||||
import { IconPathHelper, cssStyles } from '../constants';
|
import { IconPathHelper, cssStyles } from '../constants';
|
||||||
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint, getBdcStatusErrorMessage } from '../utils';
|
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint, getBdcStatusErrorMessage } from '../utils';
|
||||||
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||||
import { BdcDashboard } from './bdcDashboard';
|
|
||||||
import { createViewDetailsButton } from './commonControls';
|
import { createViewDetailsButton } from './commonControls';
|
||||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
import { BdcDashboardPage } from './bdcDashboardPage';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
const clusterStateLabelColumnWidth = 100;
|
|
||||||
const clusterStateValueColumnWidth = 225;
|
|
||||||
const healthStatusColumnWidth = 125;
|
|
||||||
|
|
||||||
const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
|
const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
|
||||||
|
|
||||||
export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
||||||
|
private rootContainer: azdata.FlexContainer;
|
||||||
private modelBuilder: azdata.ModelBuilder;
|
|
||||||
|
|
||||||
private lastUpdatedLabel: azdata.TextComponent;
|
private lastUpdatedLabel: azdata.TextComponent;
|
||||||
private propertiesContainer: azdata.DivContainer;
|
private propertiesContainerLoadingComponent: azdata.LoadingComponent;
|
||||||
private clusterStateLoadingComponent: azdata.LoadingComponent;
|
|
||||||
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
|
|
||||||
|
|
||||||
private serviceStatusTable: azdata.DeclarativeTableComponent;
|
private serviceStatusTable: azdata.DeclarativeTableComponent;
|
||||||
private endpointsTable: azdata.DeclarativeTableComponent;
|
private endpointsTable: azdata.DeclarativeTableComponent;
|
||||||
@@ -40,16 +31,23 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
private endpointsErrorMessage: azdata.TextComponent;
|
private endpointsErrorMessage: azdata.TextComponent;
|
||||||
private serviceStatusErrorMessage: azdata.TextComponent;
|
private serviceStatusErrorMessage: azdata.TextComponent;
|
||||||
|
|
||||||
constructor(private dashboard: BdcDashboard, private model: BdcDashboardModel) {
|
constructor(model: BdcDashboardModel, modelView: azdata.ModelView) {
|
||||||
super();
|
super(model, modelView);
|
||||||
this.model.onDidUpdateEndpoints(endpoints => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdate(endpoints)));
|
this.model.onDidUpdateEndpoints(endpoints => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdate(endpoints)));
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
this.model.onBdcError(error => this.eventuallyRunOnInitialized(() => this.handleBdcError(error)));
|
this.model.onBdcError(error => this.eventuallyRunOnInitialized(() => this.handleBdcError(error)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public create(view: azdata.ModelView): azdata.FlexContainer {
|
public get container(): azdata.FlexContainer {
|
||||||
this.modelBuilder = view.modelBuilder;
|
// Lazily create the container only when needed
|
||||||
const rootContainer = view.modelBuilder.flexContainer().withLayout(
|
if (!this.rootContainer) {
|
||||||
|
this.rootContainer = this.createContainer();
|
||||||
|
}
|
||||||
|
return this.rootContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createContainer(): azdata.FlexContainer {
|
||||||
|
const rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
||||||
{
|
{
|
||||||
flexFlow: 'column',
|
flexFlow: 'column',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -60,51 +58,24 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
// # PROPERTIES #
|
// # PROPERTIES #
|
||||||
// ##############
|
// ##############
|
||||||
|
|
||||||
const propertiesLabel = view.modelBuilder.text()
|
const propertiesLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({ value: loc.clusterProperties, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } })
|
.withProperties<azdata.TextComponentProperties>({ value: loc.clusterProperties, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } })
|
||||||
.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();
|
const propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
|
||||||
rootContainer.addItem(this.propertiesErrorMessage, { flex: '0 0 auto' });
|
|
||||||
|
|
||||||
this.propertiesContainer = view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
this.propertiesContainerLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(propertiesContainer).component();
|
||||||
|
rootContainer.addItem(this.propertiesContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px' } });
|
||||||
// Row 1
|
|
||||||
const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component();
|
|
||||||
|
|
||||||
// Cluster State
|
|
||||||
const clusterStateLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.clusterState }).component();
|
|
||||||
const clusterStateValue = view.modelBuilder.text().component();
|
|
||||||
this.clusterStateLoadingComponent = view.modelBuilder.loadingComponent()
|
|
||||||
.withItem(clusterStateValue)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({ loadingCompletedText: loc.loadingClusterStateCompleted })
|
|
||||||
.component();
|
|
||||||
row1.addItem(clusterStateLabel, { CSSStyles: { 'width': `${clusterStateLabelColumnWidth}px`, 'min-width': `${clusterStateLabelColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } });
|
|
||||||
row1.addItem(this.clusterStateLoadingComponent, { CSSStyles: { 'width': `${clusterStateValueColumnWidth}px`, 'min-width': `${clusterStateValueColumnWidth}px` } });
|
|
||||||
|
|
||||||
// Health Status
|
|
||||||
const healthStatusLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.healthStatusWithColon }).component();
|
|
||||||
const healthStatusValue = view.modelBuilder.text().component();
|
|
||||||
this.clusterHealthStatusLoadingComponent = view.modelBuilder.loadingComponent()
|
|
||||||
.withItem(healthStatusValue)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({ loadingCompletedText: loc.loadingHealthStatusCompleted })
|
|
||||||
.component();
|
|
||||||
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` } });
|
|
||||||
|
|
||||||
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 #
|
||||||
// ############
|
// ############
|
||||||
|
|
||||||
const overviewHeaderContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
const overviewHeaderContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
||||||
rootContainer.addItem(overviewHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
rootContainer.addItem(overviewHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
||||||
|
|
||||||
const overviewLabel = view.modelBuilder.text()
|
const overviewLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
value: loc.clusterOverview,
|
value: loc.clusterOverview,
|
||||||
CSSStyles: { ...cssStyles.text }
|
CSSStyles: { ...cssStyles.text }
|
||||||
@@ -113,7 +84,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
|
|
||||||
overviewHeaderContainer.addItem(overviewLabel, { CSSStyles: { ...cssStyles.title } });
|
overviewHeaderContainer.addItem(overviewLabel, { CSSStyles: { ...cssStyles.title } });
|
||||||
|
|
||||||
this.lastUpdatedLabel = view.modelBuilder.text()
|
this.lastUpdatedLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: loc.lastUpdated(),
|
value: loc.lastUpdated(),
|
||||||
CSSStyles: { ...cssStyles.lastUpdatedText }
|
CSSStyles: { ...cssStyles.lastUpdatedText }
|
||||||
@@ -121,9 +92,9 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
|
|
||||||
overviewHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
overviewHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
||||||
|
|
||||||
const overviewContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
const overviewContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||||
|
|
||||||
this.serviceStatusTable = view.modelBuilder.declarativeTable()
|
this.serviceStatusTable = this.modelView.modelBuilder.declarativeTable()
|
||||||
.withProperties<azdata.DeclarativeTableProperties>(
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
{
|
{
|
||||||
columns: [
|
columns: [
|
||||||
@@ -214,19 +185,19 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this.serviceStatusDisplayContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
this.serviceStatusDisplayContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusTable);
|
this.serviceStatusDisplayContainer.addItem(this.serviceStatusTable);
|
||||||
|
|
||||||
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||||
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||||
// component once it's finished loading the content
|
// component once it's finished loading the content
|
||||||
this.serviceStatusLoadingComponent = view.modelBuilder.loadingComponent()
|
this.serviceStatusLoadingComponent = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
this.serviceStatusDisplayContainer.addItem(this.serviceStatusLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||||
|
|
||||||
this.serviceStatusErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
this.serviceStatusErrorMessage = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||||
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
||||||
|
|
||||||
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
||||||
@@ -237,16 +208,16 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
// # SERVICE ENDPOINTS #
|
// # SERVICE ENDPOINTS #
|
||||||
// #####################
|
// #####################
|
||||||
|
|
||||||
const endpointsLabel = view.modelBuilder.text()
|
const endpointsLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
.withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
||||||
.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();
|
this.endpointsErrorMessage = this.modelView.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 = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||||
|
|
||||||
this.endpointsTable = view.modelBuilder.declarativeTable()
|
this.endpointsTable = this.modelView.modelBuilder.declarativeTable()
|
||||||
.withProperties<azdata.DeclarativeTableProperties>(
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
{
|
{
|
||||||
columns: [
|
columns: [
|
||||||
@@ -305,13 +276,13 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
ariaLabel: loc.serviceEndpoints
|
ariaLabel: loc.serviceEndpoints
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.endpointsDisplayContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
this.endpointsDisplayContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
this.endpointsDisplayContainer.addItem(this.endpointsTable);
|
this.endpointsDisplayContainer.addItem(this.endpointsTable);
|
||||||
|
|
||||||
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||||
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||||
// component once it's finished loading the content
|
// component once it's finished loading the content
|
||||||
this.endpointsLoadingComponent = view.modelBuilder.loadingComponent()
|
this.endpointsLoadingComponent = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||||
.component();
|
.component();
|
||||||
this.endpointsDisplayContainer.addItem(this.endpointsLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
this.endpointsDisplayContainer.addItem(this.endpointsLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||||
@@ -335,7 +306,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
this.endpointsErrorMessage.display = 'none';
|
this.endpointsErrorMessage.display = 'none';
|
||||||
|
|
||||||
this.serviceStatusDisplayContainer.display = undefined;
|
this.serviceStatusDisplayContainer.display = undefined;
|
||||||
this.propertiesContainer.display = undefined;
|
this.propertiesContainerLoadingComponent.display = undefined;
|
||||||
this.endpointsDisplayContainer.display = undefined;
|
this.endpointsDisplayContainer.display = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,31 +316,33 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
}
|
}
|
||||||
this.lastUpdatedLabel.value = loc.lastUpdated(this.model.bdcStatusLastUpdated);
|
this.lastUpdatedLabel.value = loc.lastUpdated(this.model.bdcStatusLastUpdated);
|
||||||
|
|
||||||
this.clusterStateLoadingComponent.loading = false;
|
this.propertiesContainerLoadingComponent.loading = false;
|
||||||
this.clusterHealthStatusLoadingComponent.loading = false;
|
|
||||||
(<azdata.TextComponent>this.clusterStateLoadingComponent.component).value = getStateDisplayText(bdcStatus.state);
|
(<azdata.PropertiesContainerComponentProperties>this.propertiesContainerLoadingComponent.component).propertyItems = [
|
||||||
(<azdata.TextComponent>this.clusterHealthStatusLoadingComponent.component).value = getHealthStatusDisplayText(bdcStatus.healthStatus);
|
{ displayName: loc.clusterState, value: getStateDisplayText(bdcStatus.state) },
|
||||||
|
{ displayName: loc.healthStatus, value: getHealthStatusDisplayText(bdcStatus.healthStatus) }
|
||||||
|
];
|
||||||
|
|
||||||
if (bdcStatus.services) {
|
if (bdcStatus.services) {
|
||||||
this.serviceStatusTable.data = bdcStatus.services.map(serviceStatus => {
|
this.serviceStatusTable.data = bdcStatus.services.map(serviceStatus => {
|
||||||
const statusIconCell = this.modelBuilder.text()
|
const statusIconCell = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
value: getHealthStatusIcon(serviceStatus.healthStatus),
|
value: getHealthStatusIcon(serviceStatus.healthStatus),
|
||||||
ariaRole: 'img',
|
ariaRole: 'img',
|
||||||
title: getHealthStatusDisplayText(serviceStatus.healthStatus),
|
title: getHealthStatusDisplayText(serviceStatus.healthStatus),
|
||||||
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
||||||
}).component();
|
}).component();
|
||||||
const nameCell = this.modelBuilder.hyperlink()
|
const nameCell = this.modelView.modelBuilder.hyperlink()
|
||||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: getServiceNameDisplayText(serviceStatus.serviceName),
|
label: getServiceNameDisplayText(serviceStatus.serviceName),
|
||||||
url: '',
|
url: '',
|
||||||
CSSStyles: { ...cssStyles.text }
|
CSSStyles: { ...cssStyles.text }
|
||||||
}).component();
|
}).component();
|
||||||
nameCell.onDidClick(() => {
|
nameCell.onDidClick(() => {
|
||||||
this.dashboard.switchToServiceTab(serviceStatus.serviceName);
|
//this.dashboard.switchToServiceTab(serviceStatus.serviceName); TODO: Enable direct link to tab page
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewDetailsButton = serviceStatus.healthStatus !== 'healthy' && serviceStatus.details && serviceStatus.details.length > 0 ? createViewDetailsButton(this.modelBuilder, serviceStatus.details) : undefined;
|
const viewDetailsButton = serviceStatus.healthStatus !== 'healthy' && serviceStatus.details && serviceStatus.details.length > 0 ? createViewDetailsButton(this.modelView.modelBuilder, serviceStatus.details) : undefined;
|
||||||
return [
|
return [
|
||||||
statusIconCell,
|
statusIconCell,
|
||||||
nameCell,
|
nameCell,
|
||||||
@@ -397,7 +370,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
endpoints.unshift(...sqlServerMasterEndpoints);
|
endpoints.unshift(...sqlServerMasterEndpoints);
|
||||||
|
|
||||||
this.endpointsTable.data = endpoints.map(e => {
|
this.endpointsTable.data = endpoints.map(e => {
|
||||||
const copyValueCell = this.modelBuilder.button().withProperties<azdata.ButtonProperties>({ title: loc.copy }).component();
|
const copyValueCell = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({ title: loc.copy }).component();
|
||||||
copyValueCell.iconPath = IconPathHelper.copy;
|
copyValueCell.iconPath = IconPathHelper.copy;
|
||||||
copyValueCell.onDidClick(() => {
|
copyValueCell.onDidClick(() => {
|
||||||
vscode.env.clipboard.writeText(e.endpoint);
|
vscode.env.clipboard.writeText(e.endpoint);
|
||||||
@@ -406,7 +379,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
copyValueCell.iconHeight = '14px';
|
copyValueCell.iconHeight = '14px';
|
||||||
copyValueCell.iconWidth = '14px';
|
copyValueCell.iconWidth = '14px';
|
||||||
return [getEndpointDisplayText(e.name, e.description),
|
return [getEndpointDisplayText(e.name, e.description),
|
||||||
createEndpointComponent(this.modelBuilder, e, this.model, hyperlinkedEndpoints.some(he => he === e.name)),
|
createEndpointComponent(this.modelView.modelBuilder, e, this.model, hyperlinkedEndpoints.some(he => he === e.name)),
|
||||||
copyValueCell];
|
copyValueCell];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -426,7 +399,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
|
|
||||||
private showBdcStatusError(errorMessage: string): void {
|
private showBdcStatusError(errorMessage: string): void {
|
||||||
this.serviceStatusDisplayContainer.display = 'none';
|
this.serviceStatusDisplayContainer.display = 'none';
|
||||||
this.propertiesContainer.display = 'none';
|
this.propertiesContainerLoadingComponent.display = 'none';
|
||||||
this.serviceStatusErrorMessage.value = errorMessage;
|
this.serviceStatusErrorMessage.value = errorMessage;
|
||||||
this.serviceStatusErrorMessage.display = undefined;
|
this.serviceStatusErrorMessage.display = undefined;
|
||||||
this.propertiesErrorMessage.value = errorMessage;
|
this.propertiesErrorMessage.value = errorMessage;
|
||||||
|
|||||||
@@ -3,37 +3,67 @@
|
|||||||
* 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.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Deferred } from '../../common/promise';
|
import { IconPathHelper } from '../constants';
|
||||||
|
import { BdcDashboardModel, getTroubleshootNotebookUrl } from './bdcDashboardModel';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { InitializingComponent } from './intializingComponent';
|
||||||
|
|
||||||
export abstract class BdcDashboardPage {
|
export abstract class BdcDashboardPage extends InitializingComponent {
|
||||||
|
|
||||||
private _initialized: boolean = false;
|
private _toolbarContainer: azdata.ToolbarContainer;
|
||||||
|
private _refreshButton: azdata.ButtonComponent;
|
||||||
|
|
||||||
private onInitializedPromise: Deferred<void> = new Deferred();
|
constructor(protected model: BdcDashboardModel, protected modelView: azdata.ModelView, protected serviceName?: string) {
|
||||||
|
super();
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
protected get initialized(): boolean {
|
|
||||||
return this._initialized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected set initialized(value: boolean) {
|
public get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
if (!this._initialized && value) {
|
// Lazily create the container only when needed
|
||||||
this._initialized = true;
|
if (!this._toolbarContainer) {
|
||||||
this.onInitializedPromise.resolve();
|
this._toolbarContainer = this.createToolbarContainer();
|
||||||
}
|
}
|
||||||
|
return this._toolbarContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected createToolbarContainer(): azdata.ToolbarContainer {
|
||||||
* Runs the specified action when the component is initialized. If already initialized just runs
|
// Refresh button
|
||||||
* the action immediately.
|
this._refreshButton = this.modelView.modelBuilder.button()
|
||||||
* @param action The action to be ran when the page is initialized
|
.withProperties<azdata.ButtonProperties>({
|
||||||
*/
|
label: loc.refresh,
|
||||||
protected eventuallyRunOnInitialized(action: () => void): void {
|
iconPath: IconPathHelper.refresh
|
||||||
if (!this._initialized) {
|
}).component();
|
||||||
this.onInitializedPromise.promise.then(() => action()).catch(error => console.error(`Unexpected error running onInitialized action for BDC Page : ${error}`));
|
|
||||||
} else {
|
this._refreshButton.onDidClick(async () => {
|
||||||
action();
|
await this.doRefresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
const openTroubleshootNotebookButton = this.modelView.modelBuilder.button()
|
||||||
|
.withProperties<azdata.ButtonProperties>({
|
||||||
|
label: loc.troubleshoot,
|
||||||
|
iconPath: IconPathHelper.notebook
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
openTroubleshootNotebookButton.onDidClick(() => {
|
||||||
|
vscode.commands.executeCommand('books.sqlserver2019', getTroubleshootNotebookUrl(this.serviceName));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.modelView.modelBuilder.toolbarContainer()
|
||||||
|
.withToolbarItems(
|
||||||
|
[
|
||||||
|
{ component: this._refreshButton },
|
||||||
|
{ component: openTroubleshootNotebookButton }
|
||||||
|
]
|
||||||
|
).component();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doRefresh(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this._refreshButton.enabled = false;
|
||||||
|
await this.model.refresh();
|
||||||
|
} finally {
|
||||||
|
this._refreshButton.enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
|||||||
private metricsAndLogsRowsTable: azdata.DeclarativeTableComponent;
|
private metricsAndLogsRowsTable: azdata.DeclarativeTableComponent;
|
||||||
private lastUpdatedLabel: azdata.TextComponent;
|
private lastUpdatedLabel: azdata.TextComponent;
|
||||||
|
|
||||||
constructor(private model: BdcDashboardModel, private modelView: azdata.ModelView, private serviceName: string, private resourceName: string) {
|
constructor(model: BdcDashboardModel, modelView: azdata.ModelView, serviceName: string, private resourceName: string) {
|
||||||
super();
|
super(model, modelView, serviceName);
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,57 +7,35 @@ import * as azdata from 'azdata';
|
|||||||
import { BdcStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
import { BdcStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
||||||
import { BdcDashboardResourceStatusPage } from './bdcDashboardResourceStatusPage';
|
import { BdcDashboardResourceStatusPage } from './bdcDashboardResourceStatusPage';
|
||||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||||
import { getHealthStatusDot } from '../utils';
|
|
||||||
import { cssStyles } from '../constants';
|
|
||||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
import { BdcDashboardPage } from './bdcDashboardPage';
|
||||||
|
import { getHealthStatusDotIcon } from '../utils';
|
||||||
type ServiceTab = { div: azdata.DivContainer, dot: azdata.TextComponent, text: azdata.TextComponent };
|
|
||||||
|
|
||||||
export class BdcServiceStatusPage extends BdcDashboardPage {
|
export class BdcServiceStatusPage extends BdcDashboardPage {
|
||||||
|
|
||||||
private currentTab: { tab: ServiceTab, index: number };
|
private createdResourceTabs: Map<string, azdata.Tab> = new Map<string, azdata.Tab>();
|
||||||
private currentTabPage: BdcDashboardResourceStatusPage;
|
private tabbedPanel: azdata.TabbedPanelComponent;
|
||||||
private rootContainer: azdata.FlexContainer;
|
|
||||||
private resourceHeader: azdata.FlexContainer;
|
|
||||||
|
|
||||||
private createdTabs: Map<string, ServiceTab> = new Map<string, ServiceTab>();
|
constructor(serviceName: string, model: BdcDashboardModel, modelView: azdata.ModelView) {
|
||||||
|
super(model, modelView, serviceName);
|
||||||
constructor(private serviceName: string, private model: BdcDashboardModel, private modelView: azdata.ModelView) {
|
|
||||||
super();
|
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public get container(): azdata.FlexContainer {
|
public get container(): azdata.TabbedPanelComponent {
|
||||||
// Lazily create the container only when needed
|
// Lazily create the container only when needed
|
||||||
if (!this.rootContainer) {
|
if (!this.tabbedPanel) {
|
||||||
this.createPage();
|
this.createPage();
|
||||||
}
|
}
|
||||||
return this.rootContainer;
|
return this.tabbedPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPage(): void {
|
private createPage(): void {
|
||||||
this.rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
this.tabbedPanel = this.modelView.modelBuilder.tabbedPanel()
|
||||||
{
|
.withLayout({ showIcon: true, alwaysShowTabs: true }).component();
|
||||||
flexFlow: 'column',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.resourceHeader = this.modelView.modelBuilder.flexContainer().withLayout(
|
// Initialize our set of tab pages
|
||||||
{
|
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
||||||
flexFlow: 'row',
|
|
||||||
width: '100%',
|
|
||||||
height: '25px'
|
|
||||||
}
|
|
||||||
).withProperties({
|
|
||||||
ariaRole: 'tablist'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.rootContainer.addItem(this.resourceHeader, { CSSStyles: { 'padding-top': '15px' } });
|
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void {
|
private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void {
|
||||||
@@ -66,90 +44,29 @@ export class BdcServiceStatusPage extends BdcDashboardPage {
|
|||||||
}
|
}
|
||||||
const service = bdcStatus.services.find(s => s.serviceName === this.serviceName);
|
const service = bdcStatus.services.find(s => s.serviceName === this.serviceName);
|
||||||
if (service && service.resources) {
|
if (service && service.resources) {
|
||||||
this.createResourceNavTabs(service.resources);
|
this.updateResourcePages(service.resources);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private changeSelectedTabPage(newPage: BdcDashboardResourceStatusPage): void {
|
|
||||||
if (this.currentTabPage) {
|
|
||||||
this.rootContainer.removeItem(this.currentTabPage.container);
|
|
||||||
}
|
|
||||||
this.rootContainer.addItem(newPage.container);
|
|
||||||
this.currentTabPage = newPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to create the navigation tabs for the resources
|
* Update the resource tab pages, creating any new ones as necessary
|
||||||
*/
|
*/
|
||||||
private createResourceNavTabs(resources: ResourceStatusModel[]) {
|
private updateResourcePages(resources: ResourceStatusModel[]): void {
|
||||||
let tabIndex = this.createdTabs.size;
|
|
||||||
resources.forEach(resource => {
|
resources.forEach(resource => {
|
||||||
const existingTab: ServiceTab = this.createdTabs.get(resource.resourceName);
|
const existingTab = this.createdResourceTabs.get(resource.resourceName);
|
||||||
if (existingTab) {
|
if (existingTab) {
|
||||||
// We already created this tab so just update the status
|
existingTab.icon = getHealthStatusDotIcon(resource.healthStatus);
|
||||||
existingTab.dot.value = getHealthStatusDot(resource.healthStatus);
|
|
||||||
} else {
|
} else {
|
||||||
// New tab - create and add to the end of the container
|
|
||||||
const currentIndex = tabIndex++;
|
|
||||||
const resourceHeaderTab = createResourceHeaderTab(this.modelView.modelBuilder, resource);
|
|
||||||
this.createdTabs.set(resource.resourceName, resourceHeaderTab);
|
|
||||||
const resourceStatusPage = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName);
|
const resourceStatusPage = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName);
|
||||||
resourceHeaderTab.div.onDidClick(() => {
|
const newTab: azdata.Tab = {
|
||||||
// Don't need to do anything if this is already the currently selected tab
|
title: resource.resourceName,
|
||||||
if (this.currentTab.index === currentIndex) {
|
id: resource.resourceName,
|
||||||
return;
|
content: resourceStatusPage.container,
|
||||||
}
|
icon: getHealthStatusDotIcon(resource.healthStatus)
|
||||||
if (this.currentTab) {
|
};
|
||||||
this.currentTab.tab.text.updateCssStyles(cssStyles.unselectedResourceHeaderTab);
|
this.createdResourceTabs.set(resource.resourceName, newTab);
|
||||||
this.currentTab.tab.div.ariaSelected = false;
|
|
||||||
this.resourceHeader.removeItem(this.currentTab.tab.div);
|
|
||||||
this.resourceHeader.insertItem(this.currentTab.tab.div, this.currentTab.index, { flex: '0 0 auto', CSSStyles: cssStyles.unselectedTabDiv });
|
|
||||||
}
|
|
||||||
this.changeSelectedTabPage(resourceStatusPage);
|
|
||||||
this.currentTab = { tab: resourceHeaderTab, index: currentIndex };
|
|
||||||
this.currentTab.tab.text.updateCssStyles(cssStyles.selectedResourceHeaderTab);
|
|
||||||
this.currentTab.tab.div.ariaSelected = true;
|
|
||||||
this.resourceHeader.removeItem(this.currentTab.tab.div);
|
|
||||||
this.resourceHeader.insertItem(this.currentTab.tab.div, this.currentTab.index, { flex: '0 0 auto', CSSStyles: cssStyles.selectedTabDiv });
|
|
||||||
});
|
|
||||||
// Set initial page
|
|
||||||
if (!this.currentTabPage) {
|
|
||||||
this.changeSelectedTabPage(resourceStatusPage);
|
|
||||||
this.currentTab = { tab: resourceHeaderTab, index: currentIndex };
|
|
||||||
this.currentTab.tab.text.updateCssStyles(cssStyles.selectedResourceHeaderTab);
|
|
||||||
this.currentTab.tab.div.ariaSelected = true;
|
|
||||||
this.resourceHeader.addItem(resourceHeaderTab.div, { flex: '0 0 auto', CSSStyles: cssStyles.selectedTabDiv });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resourceHeaderTab.text.updateCssStyles(cssStyles.unselectedResourceHeaderTab);
|
|
||||||
this.resourceHeader.addItem(resourceHeaderTab.div, { flex: '0 0 auto', CSSStyles: cssStyles.unselectedTabDiv });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.tabbedPanel.updateTabs(Array.from(this.createdResourceTabs.values()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a single resource header tab
|
|
||||||
* @param modelBuilder The ModelBuilder used to construct the object
|
|
||||||
* @param resourceStatus The status of the resource we're creating
|
|
||||||
*/
|
|
||||||
function createResourceHeaderTab(modelBuilder: azdata.ModelBuilder, resourceStatus: ResourceStatusModel): ServiceTab {
|
|
||||||
const resourceHeaderTab = modelBuilder
|
|
||||||
.divContainer()
|
|
||||||
.withLayout({
|
|
||||||
width: '100px',
|
|
||||||
height: '25px'
|
|
||||||
})
|
|
||||||
.withProperties({
|
|
||||||
clickable: true,
|
|
||||||
ariaRole: 'tab'
|
|
||||||
}).component();
|
|
||||||
const innerContainer = modelBuilder.flexContainer().withLayout({ width: '100px', height: '25px', flexFlow: 'row' }).component();
|
|
||||||
const statusDot = modelBuilder.text().withProperties({ value: getHealthStatusDot(resourceStatus.healthStatus), CSSStyles: { 'color': 'red', 'font-size': '40px', 'width': '20px', 'text-align': 'right', ...cssStyles.nonSelectableText } }).component();
|
|
||||||
innerContainer.addItem(statusDot, { flex: '0 0 auto' });
|
|
||||||
const resourceHeaderLabel = modelBuilder.text().withProperties({ value: resourceStatus.resourceName, CSSStyles: { 'text-align': 'left', ...cssStyles.tabHeaderText } }).component();
|
|
||||||
innerContainer.addItem(resourceHeaderLabel);
|
|
||||||
resourceHeaderTab.addItem(innerContainer);
|
|
||||||
return { div: resourceHeaderTab, text: resourceHeaderLabel, dot: statusDot };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Deferred } from '../../common/promise';
|
||||||
|
|
||||||
|
export abstract class InitializingComponent {
|
||||||
|
|
||||||
|
private _initialized: boolean = false;
|
||||||
|
|
||||||
|
private _onInitializedPromise: Deferred<void> = new Deferred();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
protected get initialized(): boolean {
|
||||||
|
return this._initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected set initialized(value: boolean) {
|
||||||
|
if (!this._initialized && value) {
|
||||||
|
this._initialized = true;
|
||||||
|
this._onInitializedPromise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the specified action when the component is initialized. If already initialized just runs
|
||||||
|
* the action immediately.
|
||||||
|
* @param action The action to be ran when the page is initialized
|
||||||
|
*/
|
||||||
|
protected eventuallyRunOnInitialized(action: () => void): void {
|
||||||
|
if (!this._initialized) {
|
||||||
|
this._onInitializedPromise.promise.then(() => action()).catch(error => console.error(`Unexpected error running onInitialized action for BDC Page : ${error}`));
|
||||||
|
} else {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@ export const clusterDetails = localize('bdc.dashboard.clusterDetails', "Cluster
|
|||||||
export const clusterOverview = localize('bdc.dashboard.clusterOverview', "Cluster Overview");
|
export const clusterOverview = localize('bdc.dashboard.clusterOverview', "Cluster Overview");
|
||||||
export const serviceEndpoints = localize('bdc.dashboard.serviceEndpoints', "Service Endpoints");
|
export const serviceEndpoints = localize('bdc.dashboard.serviceEndpoints', "Service Endpoints");
|
||||||
export const clusterProperties = localize('bdc.dashboard.clusterProperties', "Cluster Properties");
|
export const clusterProperties = localize('bdc.dashboard.clusterProperties', "Cluster Properties");
|
||||||
export const clusterState = localize('bdc.dashboard.clusterState', "Cluster State :");
|
export const clusterState = localize('bdc.dashboard.clusterState', "Cluster State");
|
||||||
export const healthStatusWithColon = localize('bdc.dashboard.healthStatusWithColon', "Health Status :");
|
|
||||||
export const serviceName = localize('bdc.dashboard.serviceName', "Service Name");
|
export const serviceName = localize('bdc.dashboard.serviceName', "Service Name");
|
||||||
export const service = localize('bdc.dashboard.service', "Service");
|
export const service = localize('bdc.dashboard.service', "Service");
|
||||||
export const endpoint = localize('bdc.dashboard.endpoint', "Endpoint");
|
export const endpoint = localize('bdc.dashboard.endpoint', "Endpoint");
|
||||||
|
|||||||
@@ -210,17 +210,17 @@ export function getHealthStatusIcon(healthStatus?: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the status dot string which will be a • for all non-healthy states
|
* Returns the status dot icon which will be a • for all non-healthy states
|
||||||
* @param healthStatus The status to check
|
* @param healthStatus The status to check
|
||||||
*/
|
*/
|
||||||
export function getHealthStatusDot(healthStatus?: string): string {
|
export function getHealthStatusDotIcon(healthStatus?: string): constants.IconPath {
|
||||||
healthStatus = healthStatus || '';
|
healthStatus = healthStatus || '';
|
||||||
switch (healthStatus.toLowerCase()) {
|
switch (healthStatus.toLowerCase()) {
|
||||||
case 'healthy':
|
case 'healthy':
|
||||||
return '';
|
return constants.IconPathHelper.status_circle_blank;
|
||||||
default:
|
default:
|
||||||
// Display status dot for all non-healthy status'
|
// Display status dot for all non-healthy status'
|
||||||
return '•';
|
return constants.IconPathHelper.status_circle_red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function registerCommands(context: vscode.ExtensionContext, treeDataProvider: Co
|
|||||||
await treeDataProvider.saveControllers();
|
await treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
const dashboard: BdcDashboard = new BdcDashboard(title, new BdcDashboardModel(info, treeDataProvider));
|
const dashboard: BdcDashboard = new BdcDashboard(title, new BdcDashboardModel(info, treeDataProvider));
|
||||||
dashboard.showDashboard();
|
await dashboard.showDashboard();
|
||||||
});
|
});
|
||||||
|
|
||||||
vscode.commands.registerCommand(commands.MountHdfsCommand, e => mountHdfs(e).catch(error => {
|
vscode.commands.registerCommand(commands.MountHdfsCommand, e => mountHdfs(e).catch(error => {
|
||||||
|
|||||||
Reference in New Issue
Block a user