diff --git a/extensions/big-data-cluster/resources/dark/notebook_inverse.svg b/extensions/big-data-cluster/resources/dark/notebook_inverse.svg
new file mode 100644
index 0000000000..841199cf11
--- /dev/null
+++ b/extensions/big-data-cluster/resources/dark/notebook_inverse.svg
@@ -0,0 +1,4 @@
+
diff --git a/extensions/big-data-cluster/resources/dark/status_ok_dark.svg b/extensions/big-data-cluster/resources/dark/status_ok_dark.svg
new file mode 100644
index 0000000000..776e1fd909
--- /dev/null
+++ b/extensions/big-data-cluster/resources/dark/status_ok_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/big-data-cluster/resources/dark/status_warning_dark.svg b/extensions/big-data-cluster/resources/dark/status_warning_dark.svg
new file mode 100644
index 0000000000..a267963e58
--- /dev/null
+++ b/extensions/big-data-cluster/resources/dark/status_warning_dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/big-data-cluster/resources/light/notebook.svg b/extensions/big-data-cluster/resources/light/notebook.svg
new file mode 100644
index 0000000000..2711d10b2a
--- /dev/null
+++ b/extensions/big-data-cluster/resources/light/notebook.svg
@@ -0,0 +1,7 @@
+
diff --git a/extensions/big-data-cluster/resources/light/status_ok_light.svg b/extensions/big-data-cluster/resources/light/status_ok_light.svg
new file mode 100644
index 0000000000..776e1fd909
--- /dev/null
+++ b/extensions/big-data-cluster/resources/light/status_ok_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/big-data-cluster/resources/light/status_warning_light.svg b/extensions/big-data-cluster/resources/light/status_warning_light.svg
new file mode 100644
index 0000000000..f2e2aa741e
--- /dev/null
+++ b/extensions/big-data-cluster/resources/light/status_warning_light.svg
@@ -0,0 +1,4 @@
+
diff --git a/extensions/big-data-cluster/src/bigDataCluster/constants.ts b/extensions/big-data-cluster/src/bigDataCluster/constants.ts
index 3f3e39c09e..7cab730ced 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/constants.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/constants.ts
@@ -17,36 +17,56 @@ export enum BdcItemType {
loadingController = 'bigDataClusters.itemType.loadingControllerNode'
}
-export class IconPath {
+export interface IconPath {
+ dark: string;
+ light: string;
+}
+
+export class IconPathHelper {
private static extensionContext: vscode.ExtensionContext;
- public static controllerNode: { dark: string, light: string };
- public static folderNode: { dark: string, light: string };
- public static sqlMasterNode: { dark: string, light: string };
- public static copy: { dark: string, light: string };
- public static refresh: { dark: string, light: string };
+ public static controllerNode: IconPath;
+ public static folderNode: IconPath;
+ public static sqlMasterNode: IconPath;
+ public static copy: IconPath;
+ public static refresh: IconPath;
+ public static status_ok: IconPath;
+ public static status_warning: IconPath;
+ public static notebook: IconPath;
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
- IconPath.extensionContext = extensionContext;
- IconPath.controllerNode = {
- dark: IconPath.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
- light: IconPath.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
+ IconPathHelper.extensionContext = extensionContext;
+ IconPathHelper.controllerNode = {
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
};
- IconPath.folderNode = {
- dark: IconPath.extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
- light: IconPath.extensionContext.asAbsolutePath('resources/light/folder.svg')
+ IconPathHelper.folderNode = {
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/folder.svg')
};
- IconPath.sqlMasterNode = {
- dark: IconPath.extensionContext.asAbsolutePath('resources/dark/sql_bigdata_cluster_inverse.svg'),
- light: IconPath.extensionContext.asAbsolutePath('resources/light/sql_bigdata_cluster.svg')
+ IconPathHelper.sqlMasterNode = {
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/sql_bigdata_cluster_inverse.svg'),
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/sql_bigdata_cluster.svg')
};
- IconPath.copy = {
- light: IconPath.extensionContext.asAbsolutePath('resources/light/copy.svg'),
- dark: IconPath.extensionContext.asAbsolutePath('resources/dark/copy_inverse.svg')
+ IconPathHelper.copy = {
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/copy.svg'),
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/copy_inverse.svg')
};
- IconPath.refresh = {
- light: IconPath.extensionContext.asAbsolutePath('resources/light/refresh.svg'),
- dark: IconPath.extensionContext.asAbsolutePath('resources/dark/refresh_inverse.svg')
+ IconPathHelper.refresh = {
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/refresh.svg'),
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/refresh_inverse.svg')
+ };
+ IconPathHelper.status_ok = {
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/status_ok_light.svg'),
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/status_ok_dark.svg')
+ };
+ IconPathHelper.status_warning = {
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/status_warning_light.svg'),
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/status_warning_dark.svg')
+ };
+ IconPathHelper.notebook = {
+ light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/notebook.svg'),
+ dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/notebook_inverse.svg')
};
}
}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts b/extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
index 9e6714d594..15b8321152 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
@@ -10,7 +10,6 @@
import localVarRequest = require('request');
import http = require('http');
-import Promise = require('bluebird');
let defaultBasePath = 'https://localhost';
diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts
index 4825f33508..cb6ec5e297 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts
@@ -6,12 +6,14 @@
'use strict';
import * as azdata from 'azdata';
+import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { BdcDashboardModel } from './bdcDashboardModel';
-import { IconPath } from '../constants';
+import { IconPathHelper } from '../constants';
import { BdcServiceStatusPage } from './bdcServiceStatusPage';
import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage';
import { EndpointModel, BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated';
+import { getHealthStatusDot, getServiceNameDisplayText } from '../utils';
const localize = nls.loadMessageBundle();
@@ -59,13 +61,30 @@ export class BdcDashboard {
const refreshButton = modelView.modelBuilder.button()
.withProperties({
label: localize('bdc.dashboard.refreshButton', "Refresh"),
- iconPath: IconPath.refresh,
- height: '50'
+ iconPath: IconPathHelper.refresh,
+ height: '50px'
}).component();
refreshButton.onDidClick(() => this.model.refresh());
- const toolbarContainer = modelView.modelBuilder.toolbarContainer().withToolbarItems([{ component: refreshButton }]).component();
+ const openTroubleshootNotebookButton = modelView.modelBuilder.button()
+ .withProperties({
+ label: localize('bdc.dashboard.troubleshootButton', "Troubleshoot"),
+ iconPath: IconPathHelper.notebook,
+ height: '50px'
+ }).component();
+
+ openTroubleshootNotebookButton.onDidClick(() => {
+ vscode.commands.executeCommand('mssqlCluster.task.openNotebook');
+ });
+
+ const toolbarContainer = modelView.modelBuilder.toolbarContainer()
+ .withToolbarItems(
+ [
+ { component: refreshButton },
+ { component: openTroubleshootNotebookButton }
+ ]
+ ).component();
rootContainer.addItem(toolbarContainer, { flex: '0 0 auto' });
@@ -143,7 +162,7 @@ export class BdcDashboard {
if (this.initialized && !this.serviceTabsCreated && services) {
// Add a nav item for each service
services.forEach(s => {
- const navItem = createServiceNavTab(this.modelView.modelBuilder, getFriendlyServiceName(s.serviceName));
+ const navItem = createServiceNavTab(this.modelView.modelBuilder, s);
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView).container;
navItem.onDidClick(() => {
this.mainAreaContainer.removeItem(this.currentPage);
@@ -157,28 +176,11 @@ export class BdcDashboard {
}
}
-function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceName: string): azdata.DivContainer {
- const navItem = modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).component();
- navItem.addItem(modelBuilder.text().withProperties({ value: serviceName }).component(), { CSSStyles: { 'user-select': 'text' } });
- return navItem;
-}
-
-function getFriendlyServiceName(serviceName: string): string {
- serviceName = serviceName || '';
- switch (serviceName.toLowerCase()) {
- case 'sql':
- return localize('bdc.dashboard.sql', "SQL Server");
- case 'hdfs':
- return localize('bdc.dashboard.hdfs', "HDFS");
- case 'spark':
- return localize('bdc.dashboard.spark', "Spark");
- case 'control':
- return localize('bdc.dashboard.control', "Control");
- case 'gateway':
- return localize('bdc.dashboard.gateway', "Gateway");
- case 'app':
- return localize('bdc.dashboard.app', "App");
- default:
- return serviceName;
- }
+function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceStatus: ServiceStatusModel): azdata.DivContainer {
+ const div = modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).component();
+ const innerContainer = modelBuilder.flexContainer().withLayout({ width: navWidth, height: '30px', flexFlow: 'row' }).component();
+ innerContainer.addItem(modelBuilder.text().withProperties({ value: getHealthStatusDot(serviceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none', 'color': 'red', 'font-size': '40px', 'width': '20px' } }).component(), { flex: '0 0 auto' });
+ innerContainer.addItem(modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component(), { flex: '0 0 auto' });
+ div.addItem(innerContainer);
+ return div;
}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardModel.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardModel.ts
index b83f02663a..058941517c 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardModel.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardModel.ts
@@ -14,6 +14,8 @@ export class BdcDashboardModel {
private _bdcStatus: BdcStatusModel;
private _endpoints: EndpointModel[] = [];
+ private _bdcStatusLastUpdated: Date;
+ private _endpointsLastUpdated: Date;
private readonly _onDidUpdateEndpoints = new vscode.EventEmitter();
private readonly _onDidUpdateBdcStatus = new vscode.EventEmitter();
public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event;
@@ -31,40 +33,26 @@ export class BdcDashboardModel {
return this._endpoints || [];
}
+ public get bdcStatusLastUpdated(): Date {
+ return this._bdcStatusLastUpdated;
+ }
+
+ public get endpointsLastUpdated(): Date {
+ return this._endpointsLastUpdated;
+ }
+
public async refresh(): Promise {
await Promise.all([
getBdcStatus(this.url, this.username, this.password, true).then(response => {
this._bdcStatus = response.bdcStatus;
+ this._bdcStatusLastUpdated = new Date();
this._onDidUpdateBdcStatus.fire(this.bdcStatus);
}),
getEndPoints(this.url, this.username, this.password, true).then(response => {
this._endpoints = response.endPoints || [];
+ this._endpointsLastUpdated = new Date();
this._onDidUpdateEndpoints.fire(this.serviceEndpoints);
})
]).catch(error => showErrorMessage(error));
}
}
-
-export enum Endpoint {
- gateway = 'gateway',
- sparkHistory = 'spark-history',
- yarnUi = 'yarn-ui',
- appProxy = 'app-proxy',
- mgmtproxy = 'mgmtproxy',
- managementProxy = 'management-proxy',
- logsui = 'logsui',
- metricsui = 'metricsui',
- controller = 'controller',
- sqlServerMaster = 'sql-server-master',
- webhdfs = 'webhdfs',
- livy = 'livy'
-}
-
-export enum Service {
- sql = 'sql',
- hdfs = 'hdfs',
- spark = 'spark',
- control = 'control',
- gateway = 'gateway',
- app = 'app'
-}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts
index 2bd6217e3c..6619e61539 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts
@@ -7,51 +7,35 @@
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
-import { BdcDashboardModel, Endpoint, Service } from './bdcDashboardModel';
-import { IconPath } from '../constants';
+import { BdcDashboardModel } from './bdcDashboardModel';
+import { IconPathHelper } from '../constants';
+import { getStateDisplayText, getHealthStatusDisplayText, getHealthStatusIcon, getEndpointDisplayText, getServiceNameDisplayText, Endpoint } from '../utils';
import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
const localize = nls.loadMessageBundle();
-interface IServiceStatusRow {
- stateLoadingComponent: azdata.LoadingComponent;
- healthStatusLoadingComponent: azdata.LoadingComponent;
-}
-
-interface IServiceEndpointRow {
- endpointLoadingComponent: azdata.LoadingComponent;
- isHyperlink: boolean;
-}
-
-const navWidth = '175px';
+const overviewIconColumnWidth = '50px';
const overviewServiceNameCellWidth = '100px';
const overviewStateCellWidth = '75px';
-const overviewHealthStatusCellWidth = '75px';
+const overviewHealthStatusCellWidth = '100px';
const serviceEndpointRowServiceNameCellWidth = '125px';
const serviceEndpointRowEndpointCellWidth = '350px';
+const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
+
export class BdcDashboardOverviewPage {
private initialized: boolean = false;
+ private modelBuilder: azdata.ModelBuilder;
+ private lastUpdatedLabel: azdata.TextComponent;
private clusterStateLoadingComponent: azdata.LoadingComponent;
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
- private sqlServerStatusRow: IServiceStatusRow;
- private hdfsStatusRow: IServiceStatusRow;
- private sparkStatusRow: IServiceStatusRow;
- private controlStatusRow: IServiceStatusRow;
- private gatewayStatusRow: IServiceStatusRow;
- private appStatusRow: IServiceStatusRow;
+ private serviceStatusRowContainer: azdata.FlexContainer;
- private sqlServerEndpointRow: IServiceEndpointRow;
- private controllerEndpointRow: IServiceEndpointRow;
- private hdfsSparkGatewayEndpointRow: IServiceEndpointRow;
- private sparkHistoryEndpointRow: IServiceEndpointRow;
- private yarnHistoryEndpointRow: IServiceEndpointRow;
- private grafanaDashboardEndpointRow: IServiceEndpointRow;
- private kibanaDashboardEndpointRow: IServiceEndpointRow;
+ private endpointsRowContainer: azdata.FlexContainer;
constructor(private model: BdcDashboardModel) {
this.model.onDidUpdateEndpoints(endpoints => this.handleEndpointsUpdate(endpoints));
@@ -59,6 +43,7 @@ export class BdcDashboardOverviewPage {
}
public create(view: azdata.ModelView): azdata.FlexContainer {
+ this.modelBuilder = view.modelBuilder;
const rootContainer = view.modelBuilder.flexContainer().withLayout(
{
flexFlow: 'column',
@@ -109,30 +94,51 @@ export class BdcDashboardOverviewPage {
// # OVERVIEW #
// ############
+ const overviewHeaderContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
+ rootContainer.addItem(overviewHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
+
const overviewLabel = view.modelBuilder.text()
- .withProperties({ value: localize('bdc.dashboard.overviewHeader', "Cluster Overview"), CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
+ .withProperties({
+ value: localize('bdc.dashboard.overviewHeader', "Cluster Overview"),
+ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
+ })
.component();
- rootContainer.addItem(overviewLabel, { CSSStyles: { 'font-size': '20px', 'font-weight': 'bold', 'padding-left': '10px' } });
+
+ overviewHeaderContainer.addItem(overviewLabel, { CSSStyles: { 'font-size': '20px', 'font-weight': 'bold' } });
+
+ this.lastUpdatedLabel = view.modelBuilder.text()
+ .withProperties({
+ value: localize('bdc.dashboard.lastUpdated', "Last Updated : {0}", '-'),
+ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'color': 'lightgray' }
+ }).component();
+
+ overviewHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
const overviewContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%', alignItems: 'left' }).component();
// Service Status header row
const serviceStatusHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
+ const serviceStatusIconHeader = view.modelBuilder.text().component();
+ serviceStatusHeaderRow.addItem(serviceStatusIconHeader, { CSSStyles: { 'width': overviewIconColumnWidth, 'min-width': overviewIconColumnWidth } });
const nameCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.serviceNameHeader', "Service Name") }).component();
serviceStatusHeaderRow.addItem(nameCell, { CSSStyles: { 'width': overviewServiceNameCellWidth, 'min-width': overviewServiceNameCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
- const stateCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.stateHeader', "State") }).component();
- serviceStatusHeaderRow.addItem(stateCell, { CSSStyles: { 'width': overviewStateCellWidth, 'min-width': overviewStateCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
- const healthStatusCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status") }).component();
- serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': overviewHealthStatusCellWidth, 'min-width': overviewHealthStatusCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const stateCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.stateHeader', "State"), CSSStyles: { 'text-align': 'center', 'font-weight': 'bold' } }).component();
+ serviceStatusHeaderRow.addItem(stateCell, { CSSStyles: { 'width': overviewStateCellWidth, 'min-width': overviewStateCellWidth, 'user-select': 'text' } });
+ const healthStatusCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status"), CSSStyles: { 'text-align': 'center', 'font-weight': 'bold' } }).component();
+ serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': overviewHealthStatusCellWidth, 'min-width': overviewHealthStatusCellWidth, 'user-select': 'text' } });
overviewContainer.addItem(serviceStatusHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
- this.sqlServerStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.sqlServerLabel', "SQL Server"));
- this.hdfsStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.hdfsLabel', "HDFS"));
- this.sparkStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.sparkLabel', "Spark"));
- this.controlStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.controlLabel', "Control"));
- this.gatewayStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.gatewayLabel', "Gateway"));
- this.appStatusRow = createServiceStatusRow(view.modelBuilder, overviewContainer, localize('bdc.dashboard.appLabel', "App"));
+ // Service Status row container
+ 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
+ // messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component
+ // and replace it with the rows directly
+ const serviceStatusRowContainerLoadingComponent = view.modelBuilder.loadingComponent()
+ .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
+ .component();
+ this.serviceStatusRowContainer.addItem(serviceStatusRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
+ overviewContainer.addItem(this.serviceStatusRowContainer);
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
// #####################
@@ -154,13 +160,16 @@ export class BdcDashboardOverviewPage {
endpointsHeaderRow.addItem(endpointsEndpointHeaderCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
endpointsContainer.addItem(endpointsHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
- this.sqlServerEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('sql-server'), false);
- this.controllerEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('controller'), false);
- this.hdfsSparkGatewayEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('gateway'), false);
- this.sparkHistoryEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('spark-history'), true);
- this.yarnHistoryEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('yarn-history'), true);
- this.grafanaDashboardEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('grafana'), true);
- this.kibanaDashboardEndpointRow = createServiceEndpointRow(view.modelBuilder, endpointsContainer, getFriendlyEndpointNames('kibana'), true);
+ 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
+ // messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component
+ // and replace it with the rows directly
+ const endpointRowContainerLoadingComponent = view.modelBuilder.loadingComponent()
+ .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
+ .component();
+ this.endpointsRowContainer.addItem(endpointRowContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
+
+ endpointsContainer.addItem(this.endpointsRowContainer);
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
@@ -177,31 +186,22 @@ export class BdcDashboardOverviewPage {
if (!this.initialized || !bdcStatus) {
return;
}
+ this.lastUpdatedLabel.value =
+ localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
+ this.model.bdcStatusLastUpdated ?
+ `${this.model.bdcStatusLastUpdated.toLocaleDateString()} ${this.model.bdcStatusLastUpdated.toLocaleTimeString()}`
+ : '-');
this.clusterStateLoadingComponent.loading = false;
this.clusterHealthStatusLoadingComponent.loading = false;
- this.clusterStateLoadingComponent.component.updateProperty('value', bdcStatus.state);
- this.clusterHealthStatusLoadingComponent.component.updateProperty('value', bdcStatus.healthStatus);
+ (this.clusterStateLoadingComponent.component).value = getStateDisplayText(bdcStatus.state);
+ (this.clusterHealthStatusLoadingComponent.component).value = getHealthStatusDisplayText(bdcStatus.healthStatus);
if (bdcStatus.services) {
- // Service Status
- const sqlServerServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.sql);
- updateServiceStatusRow(this.sqlServerStatusRow, sqlServerServiceStatus);
-
- const hdfsServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.hdfs);
- updateServiceStatusRow(this.hdfsStatusRow, hdfsServiceStatus);
-
- const sparkServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.spark);
- updateServiceStatusRow(this.sparkStatusRow, sparkServiceStatus);
-
- const controlServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.control);
- updateServiceStatusRow(this.controlStatusRow, controlServiceStatus);
-
- const gatewayServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.gateway);
- updateServiceStatusRow(this.gatewayStatusRow, gatewayServiceStatus);
-
- const appServiceStatus = bdcStatus.services.find(s => s.serviceName === Service.app);
- updateServiceStatusRow(this.appStatusRow, appServiceStatus);
+ this.serviceStatusRowContainer.clearItems();
+ bdcStatus.services.forEach((s, i) => {
+ createServiceStatusRow(this.modelBuilder, this.serviceStatusRowContainer, s, i === bdcStatus.services.length - 1);
+ });
}
}
@@ -210,105 +210,45 @@ export class BdcDashboardOverviewPage {
return;
}
- // Service Endpoints
- const sqlServerEndpoint = endpoints.find(e => e.name === Endpoint.sqlServerMaster);
- updateServiceEndpointRow(this.sqlServerEndpointRow, sqlServerEndpoint);
-
- const controllerEndpoint = endpoints.find(e => e.name === Endpoint.controller);
- updateServiceEndpointRow(this.controllerEndpointRow, controllerEndpoint);
-
- const gatewayEndpoint = endpoints.find(e => e.name === Endpoint.gateway);
- updateServiceEndpointRow(this.hdfsSparkGatewayEndpointRow, gatewayEndpoint);
-
- const yarnHistoryEndpoint = endpoints.find(e => e.name === Endpoint.yarnUi);
- updateServiceEndpointRow(this.yarnHistoryEndpointRow, yarnHistoryEndpoint);
-
- const sparkHistoryEndpoint = endpoints.find(e => e.name === Endpoint.sparkHistory);
- updateServiceEndpointRow(this.sparkHistoryEndpointRow, sparkHistoryEndpoint);
-
- const grafanaDashboardEndpoint = endpoints.find(e => e.name === Endpoint.metricsui);
- updateServiceEndpointRow(this.grafanaDashboardEndpointRow, grafanaDashboardEndpoint);
-
- const kibanaDashboardEndpoint = endpoints.find(e => e.name === Endpoint.logsui);
- updateServiceEndpointRow(this.kibanaDashboardEndpointRow, kibanaDashboardEndpoint);
+ this.endpointsRowContainer.clearItems();
+ endpoints.forEach((e, i) => {
+ createServiceEndpointRow(this.modelBuilder, this.endpointsRowContainer, e, hyperlinkedEndpoints.some(he => he === e.name), i === endpoints.length - 1);
+ });
}
}
-function updateServiceStatusRow(serviceStatusRow: IServiceStatusRow, serviceStatus: ServiceStatusModel) {
- if (serviceStatus) {
- serviceStatusRow.stateLoadingComponent.loading = false;
- serviceStatusRow.healthStatusLoadingComponent.loading = false;
- serviceStatusRow.stateLoadingComponent.component.updateProperty('value', serviceStatus.state);
- serviceStatusRow.healthStatusLoadingComponent.component.updateProperty('value', serviceStatus.healthStatus);
- }
- else {
- serviceStatusRow.stateLoadingComponent.loading = true;
- serviceStatusRow.healthStatusLoadingComponent.loading = true;
- }
-}
-
-function updateServiceEndpointRow(serviceEndpointRow: IServiceEndpointRow, endpoint: EndpointModel) {
- if (endpoint) {
- serviceEndpointRow.endpointLoadingComponent.loading = false;
- if (serviceEndpointRow.isHyperlink) {
- serviceEndpointRow.endpointLoadingComponent.component.updateProperties({ label: endpoint.endpoint, url: endpoint.endpoint });
- }
- else {
- serviceEndpointRow.endpointLoadingComponent.component.updateProperty('value', endpoint.endpoint);
- }
- }
- else {
- serviceEndpointRow.endpointLoadingComponent.loading = true;
- }
-}
-
-function createServiceStatusRow(modelBuilder: azdata.ModelBuilder, container: azdata.FlexContainer, name: string): IServiceStatusRow {
+function createServiceStatusRow(modelBuilder: azdata.ModelBuilder, container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void {
const serviceStatusRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
- const nameCell = modelBuilder.text().withProperties({ value: name, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
- serviceStatusRow.addItem(nameCell, { CSSStyles: { 'width': '100px', 'min-width': '100px', 'user-select': 'text', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
- const stateCell = modelBuilder.text().withProperties({ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
- const stateLoadingComponent = modelBuilder.loadingComponent()
- .withItem(stateCell)
- .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
- .component();
- serviceStatusRow.addItem(stateLoadingComponent, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
- const healthStatusCell = modelBuilder.text().withProperties({ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
- const healthStatusLoadingComponent = modelBuilder.loadingComponent()
- .withItem(healthStatusCell)
- .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
- .component();
- serviceStatusRow.addItem(healthStatusLoadingComponent, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
+ const statusIconCell = modelBuilder.text().withProperties({ value: getHealthStatusIcon(serviceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component();
+ serviceStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': overviewIconColumnWidth, 'min-width': overviewIconColumnWidth } });
+ const nameCell = modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
+ serviceStatusRow.addItem(nameCell, { CSSStyles: { 'width': overviewServiceNameCellWidth, 'min-width': overviewServiceNameCellWidth, 'user-select': 'text', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
+ const stateCell = modelBuilder.text().withProperties({ value: getStateDisplayText(serviceStatus.state), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'text-align': 'center' } }).component();
+ serviceStatusRow.addItem(stateCell, { CSSStyles: { 'width': overviewStateCellWidth, 'min-width': overviewStateCellWidth } });
+ const healthStatusCell = modelBuilder.text().withProperties({ value: getHealthStatusDisplayText(serviceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'text-align': 'center' } }).component();
+ serviceStatusRow.addItem(healthStatusCell, { CSSStyles: { 'width': overviewHealthStatusCellWidth, 'min-width': overviewHealthStatusCellWidth } });
- container.addItem(serviceStatusRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
-
- return { stateLoadingComponent: stateLoadingComponent, healthStatusLoadingComponent: healthStatusLoadingComponent };
+ container.addItem(serviceStatusRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'border-bottom': isLastRow ? 'solid 1px #ccc' : '', 'box-sizing': 'border-box', 'user-select': 'text' } });
}
-function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container: azdata.FlexContainer, name: string, isHyperlink: boolean): IServiceEndpointRow {
- const endPointRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
- const nameCell = modelBuilder.text().withProperties({ value: name, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
- endPointRow.addItem(nameCell, { CSSStyles: { 'width': serviceEndpointRowServiceNameCellWidth, 'min-width': serviceEndpointRowServiceNameCellWidth, 'user-select': 'text' } });
- let retRow: IServiceEndpointRow;
+function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container: azdata.FlexContainer, endpoint: EndpointModel, isHyperlink: boolean, isLastRow: boolean): void {
+ const endPointRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '40px' }).component();
+ const nameCell = modelBuilder.text().withProperties({ value: getEndpointDisplayText(endpoint.name, endpoint.description), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
+ endPointRow.addItem(nameCell, { CSSStyles: { 'width': serviceEndpointRowServiceNameCellWidth, 'min-width': serviceEndpointRowServiceNameCellWidth, 'user-select': 'text', 'text-align': 'center' } });
if (isHyperlink) {
- const endpointCell = modelBuilder.hyperlink().withProperties({ CSSStyles: { 'height': '15px' } }).component();
- const endpointLoadingComponent = modelBuilder.loadingComponent()
- .withItem(endpointCell)
- .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
+ const endpointCell = modelBuilder.hyperlink()
+ .withProperties({ label: endpoint.endpoint, url: endpoint.endpoint, CSSStyles: { 'height': '15px' } })
.component();
- retRow = { endpointLoadingComponent: endpointLoadingComponent, isHyperlink: true };
- endPointRow.addItem(endpointLoadingComponent, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'color': '#0078d4', 'text-decoration': 'underline', 'overflow': 'hidden' } });
+ endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'color': '#0078d4', 'text-decoration': 'underline', 'overflow': 'hidden' } });
}
else {
- const endpointCell = modelBuilder.text().withProperties({ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
- const endpointLoadingComponent = modelBuilder.loadingComponent()
- .withItem(endpointCell)
- .withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
+ const endpointCell = modelBuilder.text()
+ .withProperties({ value: endpoint.endpoint, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } })
.component();
- retRow = { endpointLoadingComponent: endpointLoadingComponent, isHyperlink: false };
- endPointRow.addItem(endpointLoadingComponent, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden' } });
+ endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden' } });
}
const copyValueCell = modelBuilder.button().component();
- copyValueCell.iconPath = IconPath.copy;
+ copyValueCell.iconPath = IconPathHelper.copy;
copyValueCell.onDidClick(() => {
// vscode.env.clipboard.writeText(hyperlink);
});
@@ -317,34 +257,5 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
copyValueCell.iconWidth = '14px';
endPointRow.addItem(copyValueCell, { CSSStyles: { 'width': '50px', 'min-width': '50px', 'padding-left': '10px', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
- container.addItem(endPointRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
-
- return retRow;
-}
-
-function getFriendlyEndpointNames(name: string): string {
- switch (name) {
- case 'app-proxy':
- return localize('bdc.dashboard.appproxy', "Application Proxy");
- case 'controller':
- return localize('bdc.dashboard.controller', "Controller");
- case 'gateway':
- return localize('bdc.dashboard.gateway', "HDFS/Spark Gateway");
- case 'management-proxy':
- return localize('bdc.dashboard.managementproxy', "Management Proxy");
- case 'mgmtproxy':
- return localize('bdc.dashboard.mgmtproxy', "Management Proxy");
- case 'sql-server':
- return localize('bdc.dashboard.sqlServerEndpoint', "SQL Server Master Instance");
- case 'grafana':
- return localize('bdc.dashboard.grafana', "Metrics Dashboard");
- case 'kibana':
- return localize('bdc.dashboard.kibana', "Log Search Dashboard");
- case 'yarn-history':
- localize('bdc.dashboard.yarnHistory', "Spark Resource Management");
- case 'spark-history':
- localize('sparkHistory', "Spark Job Monitoring");
- default:
- return name;
- }
+ container.addItem(endPointRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'border-bottom': isLastRow ? 'solid 1px #ccc' : '', 'box-sizing': 'border-box', 'user-select': 'text' } });
}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts
index 75da85a71b..55f19307e5 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts
@@ -8,6 +8,7 @@ import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { BdcDashboardModel } from './bdcDashboardModel';
import { BdcStatusModel, InstanceStatusModel } from '../controller/apiGenerated';
+import { getHealthStatusDisplayText, getHealthStatusIcon, getStateDisplayText } from '../utils';
const localize = nls.loadMessageBundle();
@@ -22,9 +23,14 @@ export interface IInstanceStatus {
healthStatus: string;
}
-const metricsAndLogsInstanceNameCellWidth = '100px';
-const metricsAndLogsMetricsCellWidth = '75px';
-const metricsAndLogsLogsCellWidth = '75px';
+const healthAndStatusIconColumnWidth = '50px';
+const healthAndStatusInstanceNameColumnWidth = '100px';
+const healthAndStatusStateColumnWidth = '75px';
+const healthAndStatusHealthColumnWidth = '75px';
+
+const metricsAndLogsInstanceNameColumnWidth = '100px';
+const metricsAndLogsMetricsColumnWidth = '75px';
+const metricsAndLogsLogsColumnWidth = '75px';
export class BdcDashboardResourceStatusPage {
@@ -32,7 +38,7 @@ export class BdcDashboardResourceStatusPage {
private rootContainer: azdata.FlexContainer;
private instanceHealthStatusRowsContainer: azdata.FlexContainer;
private metricsAndLogsRowsContainer: azdata.FlexContainer;
-
+ private lastUpdatedLabel: azdata.TextComponent;
private initialized: boolean = false;
constructor(private model: BdcDashboardModel, private modelView: azdata.ModelView, private serviceName: string, private resourceName: string) {
@@ -57,20 +63,41 @@ export class BdcDashboardResourceStatusPage {
// # INSTANCE HEALTH AND STATUS #
// ##############################
- // Instance Health Label label
- const propertiesLabel = view.modelBuilder.text()
- .withProperties({ value: localize('bdc.dashboard.healthStatusDetailsHeader', "Health Status Details"), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } })
+ const healthStatusHeaderContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
+ rootContainer.addItem(healthStatusHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
+
+ // Header label
+ const healthStatusHeaderLabel = view.modelBuilder.text()
+ .withProperties({
+ value: localize('bdc.dashboard.healthStatusDetailsHeader', "Health Status Details"),
+ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' }
+ })
.component();
- rootContainer.addItem(propertiesLabel, { CSSStyles: { 'margin-top': '15px', 'font-size': '20px', 'font-weight': 'bold', 'padding-left': '10px' } });
+
+ healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { 'font-size': '20px', 'font-weight': 'bold' } });
+
+ // Last updated label
+ this.lastUpdatedLabel = view.modelBuilder.text()
+ .withProperties({
+ value: localize('bdc.dashboard.lastUpdated', "Last Updated : {0}", '-'),
+ CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'color': 'lightgray' }
+ }).component();
+
+ healthStatusHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
+
+
+ healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { 'font-size': '20px', 'font-weight': 'bold' } });
// Header row
const instanceHealthStatusHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
- const instanceHealthAndStatusNameHeaderRow = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.metricsAndLogsHeader', "Metrics and Logs") }).component();
- instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusNameHeaderRow, { CSSStyles: { 'width': metricsAndLogsInstanceNameCellWidth, 'min-width': metricsAndLogsInstanceNameCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
- const instanceHealthAndStatusStateRow = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.stateHeader', "State") }).component();
- instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusStateRow, { CSSStyles: { 'width': metricsAndLogsMetricsCellWidth, 'min-width': metricsAndLogsMetricsCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
- const instanceHealthAndStatusHealthStatusRow = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status") }).component();
- instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusHealthStatusRow, { CSSStyles: { 'width': metricsAndLogsLogsCellWidth, 'min-width': metricsAndLogsLogsCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const instanceHealthAndStatusIconHeader = view.modelBuilder.text().component();
+ instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusIconHeader, { CSSStyles: { 'width': healthAndStatusIconColumnWidth, 'min-width': healthAndStatusIconColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const instanceHealthAndStatusNameHeader = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.instanceHeader', "Instance") }).component();
+ instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusNameHeader, { CSSStyles: { 'width': healthAndStatusInstanceNameColumnWidth, 'min-width': healthAndStatusInstanceNameColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const instanceHealthAndStatusState = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.stateHeader', "State") }).component();
+ instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusState, { CSSStyles: { 'width': healthAndStatusStateColumnWidth, 'min-width': healthAndStatusStateColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const instanceHealthAndStatusHealthStatus = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status") }).component();
+ instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusHealthStatus, { CSSStyles: { 'width': healthAndStatusHealthColumnWidth, 'min-width': healthAndStatusHealthColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
rootContainer.addItem(instanceHealthStatusHeaderRow, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
this.instanceHealthStatusRowsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
@@ -88,12 +115,12 @@ export class BdcDashboardResourceStatusPage {
// Header row
const metricsAndLogsHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
- const nameCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.metricsAndLogsHeader', "Metrics and Logs") }).component();
- metricsAndLogsHeaderRow.addItem(nameCell, { CSSStyles: { 'width': metricsAndLogsInstanceNameCellWidth, 'min-width': metricsAndLogsInstanceNameCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ const nameCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.instanceHeader', "Instance") }).component();
+ metricsAndLogsHeaderRow.addItem(nameCell, { CSSStyles: { 'width': metricsAndLogsInstanceNameColumnWidth, 'min-width': metricsAndLogsInstanceNameColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
const metricsCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.metricsHeader', "Metrics") }).component();
- metricsAndLogsHeaderRow.addItem(metricsCell, { CSSStyles: { 'width': metricsAndLogsMetricsCellWidth, 'min-width': metricsAndLogsMetricsCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ metricsAndLogsHeaderRow.addItem(metricsCell, { CSSStyles: { 'width': metricsAndLogsMetricsColumnWidth, 'min-width': metricsAndLogsMetricsColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
const healthStatusCell = view.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.logsHeader', "Logs") }).component();
- metricsAndLogsHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': metricsAndLogsLogsCellWidth, 'min-width': metricsAndLogsLogsCellWidth, 'font-weight': 'bold', 'user-select': 'text' } });
+ metricsAndLogsHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': metricsAndLogsLogsColumnWidth, 'min-width': metricsAndLogsLogsColumnWidth, 'font-weight': 'bold', 'user-select': 'text' } });
rootContainer.addItem(metricsAndLogsHeaderRow, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
this.metricsAndLogsRowsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
@@ -113,6 +140,12 @@ export class BdcDashboardResourceStatusPage {
return;
}
+ this.lastUpdatedLabel.value =
+ localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
+ this.model.bdcStatusLastUpdated ?
+ `${this.model.bdcStatusLastUpdated.toLocaleDateString()} ${this.model.bdcStatusLastUpdated.toLocaleTimeString()}`
+ : '-');
+
this.instanceHealthStatusRowsContainer.clearItems();
this.metricsAndLogsRowsContainer.clearItems();
@@ -134,12 +167,18 @@ export class BdcDashboardResourceStatusPage {
*/
function createInstanceHealthStatusRow(modelBuilder: azdata.ModelBuilder, instanceStatus: InstanceStatusModel): azdata.FlexContainer {
const instanceHealthStatusRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
+ const statusIconCell = modelBuilder.text()
+ .withProperties({
+ value: getHealthStatusIcon(instanceStatus.healthStatus),
+ CSSStyles: { 'user-select': 'none' }
+ }).component();
+ instanceHealthStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': healthAndStatusIconColumnWidth, 'min-width': healthAndStatusIconColumnWidth } });
const nameCell = modelBuilder.text().withProperties({ value: instanceStatus.instanceName, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
- instanceHealthStatusRow.addItem(nameCell, { CSSStyles: { 'width': '100px', 'min-width': '100px', 'user-select': 'text', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
- const stateCell = modelBuilder.text().withProperties({ value: instanceStatus.state, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
- instanceHealthStatusRow.addItem(stateCell, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
- const healthStatusCell = modelBuilder.text().withProperties({ value: instanceStatus.healthStatus, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
- instanceHealthStatusRow.addItem(healthStatusCell, { CSSStyles: { 'width': '75px', 'min-width': '75px' } });
+ instanceHealthStatusRow.addItem(nameCell, { CSSStyles: { 'width': healthAndStatusInstanceNameColumnWidth, 'min-width': healthAndStatusInstanceNameColumnWidth, 'user-select': 'text', 'margin-block-start': '0px', 'margin-block-end': '0px' } });
+ const stateCell = modelBuilder.text().withProperties({ value: getStateDisplayText(instanceStatus.state), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
+ instanceHealthStatusRow.addItem(stateCell, { CSSStyles: { 'width': healthAndStatusStateColumnWidth, 'min-width': healthAndStatusStateColumnWidth } });
+ const healthStatusCell = modelBuilder.text().withProperties({ value: getHealthStatusDisplayText(instanceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } }).component();
+ instanceHealthStatusRow.addItem(healthStatusCell, { CSSStyles: { 'width': healthAndStatusHealthColumnWidth, 'min-width': healthAndStatusHealthColumnWidth } });
return instanceHealthStatusRow;
}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts
index 34da9b5157..5494431283 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts
@@ -8,6 +8,7 @@ import * as azdata from 'azdata';
import { BdcStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
import { BdcDashboardResourceStatusPage } from './bdcDashboardResourceStatusPage';
import { BdcDashboardModel } from './bdcDashboardModel';
+import { getHealthStatusDot } from '../utils';
export class BdcServiceStatusPage {
@@ -75,7 +76,7 @@ export class BdcServiceStatusPage {
private createResourceNavTabs(resources: ResourceStatusModel[]) {
if (this.initialized && !this.resourceTabsCreated) {
resources.forEach(resource => {
- const resourceHeaderTab = createResourceHeaderTab(this.modelView, resource.resourceName);
+ const resourceHeaderTab = createResourceHeaderTab(this.modelView.modelBuilder, resource);
const resourceStatusPage: azdata.FlexContainer = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName).container;
resourceHeaderTab.onDidClick(() => {
this.changeSelectedTabPage(resourceStatusPage);
@@ -92,12 +93,15 @@ export class BdcServiceStatusPage {
/**
* Creates a single resource header tab
- * @param view TheModelView used to construct the object
+ * @param modelBuilder The ModelBuilder used to construct the object
* @param title The text to display in the tab
*/
-function createResourceHeaderTab(view: azdata.ModelView, title: string): azdata.DivContainer {
- const resourceHeaderTab = view.modelBuilder.divContainer().withLayout({ width: '100px', height: '25px' }).withProperties({ CSSStyles: { 'text-align': 'center' } }).component();
- const resourceHeaderLabel = view.modelBuilder.text().withProperties({ value: title, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' } }).component();
- resourceHeaderTab.addItem(resourceHeaderLabel);
+function createResourceHeaderTab(modelBuilder: azdata.ModelBuilder, resourceStatus: ResourceStatusModel): azdata.DivContainer {
+ const resourceHeaderTab = modelBuilder.divContainer().withLayout({ width: '100px', height: '25px' }).component();
+ const innerContainer = modelBuilder.flexContainer().withLayout({ width: '100px', height: '25px', flexFlow: 'row' }).component();
+ innerContainer.addItem(modelBuilder.text().withProperties({ value: getHealthStatusDot(resourceStatus.healthStatus), CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'none', 'color': 'red', 'font-size': '40px', 'width': '20px', 'text-align': 'right' } }).component(), { flex: '0 0 auto' });
+ const resourceHeaderLabel = modelBuilder.text().withProperties({ value: resourceStatus.resourceName, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'text-align': 'left' } }).component();
+ innerContainer.addItem(resourceHeaderLabel);
+ resourceHeaderTab.addItem(innerContainer);
return resourceHeaderTab;
}
diff --git a/extensions/big-data-cluster/src/bigDataCluster/tree/controllerTreeNode.ts b/extensions/big-data-cluster/src/bigDataCluster/tree/controllerTreeNode.ts
index 1f617324b6..f00515d380 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/tree/controllerTreeNode.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/tree/controllerTreeNode.ts
@@ -10,7 +10,7 @@ import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { TreeNode } from './treeNode';
-import { IconPath, BdcItemType } from '../constants';
+import { IconPathHelper, BdcItemType, IconPath } from '../constants';
import { getEndPoints } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
import { EndpointModel } from '../controller/apiGenerated';
@@ -25,7 +25,7 @@ export abstract class ControllerTreeNode extends TreeNode {
private _treeChangeHandler: IControllerTreeChangeHandler,
private _description?: string,
private _nodeType?: string,
- private _iconPath?: { dark: string, light: string }
+ private _iconPath?: IconPath
) {
super(label, parent);
this._description = this._description || this.label;
@@ -84,11 +84,11 @@ export abstract class ControllerTreeNode extends TreeNode {
this._nodeType = nodeType;
}
- public set iconPath(iconPath: { dark: string, light: string }) {
+ public set iconPath(iconPath: IconPath) {
this._iconPath = iconPath;
}
- public get iconPath(): { dark: string, light: string } {
+ public get iconPath(): IconPath {
return this._iconPath;
}
@@ -175,7 +175,7 @@ export class ControllerNode extends ControllerTreeNode {
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
- super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPath.controllerNode);
+ super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPathHelper.controllerNode);
this.label = label;
this.description = description;
@@ -297,7 +297,7 @@ export class FolderNode extends ControllerTreeNode {
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler
) {
- super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPath.folderNode);
+ super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPathHelper.folderNode);
}
}
@@ -313,7 +313,7 @@ export class SqlMasterNode extends ControllerTreeNode {
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
- super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPath.sqlMasterNode);
+ super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPathHelper.sqlMasterNode);
this._username = 'sa';
this.label = label;
this.description = description;
diff --git a/extensions/big-data-cluster/src/bigDataCluster/utils.ts b/extensions/big-data-cluster/src/bigDataCluster/utils.ts
index d9c68b8102..07f2aaa6da 100644
--- a/extensions/big-data-cluster/src/bigDataCluster/utils.ts
+++ b/extensions/big-data-cluster/src/bigDataCluster/utils.ts
@@ -6,6 +6,33 @@
'use strict';
import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+
+const localize = nls.loadMessageBundle();
+
+export enum Endpoint {
+ gateway = 'gateway',
+ sparkHistory = 'spark-history',
+ yarnUi = 'yarn-ui',
+ appProxy = 'app-proxy',
+ mgmtproxy = 'mgmtproxy',
+ managementProxy = 'management-proxy',
+ logsui = 'logsui',
+ metricsui = 'metricsui',
+ controller = 'controller',
+ sqlServerMaster = 'sql-server-master',
+ webhdfs = 'webhdfs',
+ livy = 'livy'
+}
+
+export enum Service {
+ sql = 'sql',
+ hdfs = 'hdfs',
+ spark = 'spark',
+ control = 'control',
+ gateway = 'gateway',
+ app = 'app'
+}
export function generateGuid(): string {
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
@@ -41,3 +68,144 @@ export function showErrorMessage(error: any, prefixText?: string): void {
vscode.window.showErrorMessage(text);
}
}
+
+/**
+ * Gets the localized text to display for a corresponding state
+ * @param state The state to get the display text for
+ */
+export function getStateDisplayText(state?: string): string {
+ state = state || '';
+ switch (state.toLowerCase()) {
+ case 'creating':
+ return localize('state.creating', "Creating");
+ case 'waiting':
+ return localize('state.waiting', "Waiting");
+ case 'ready':
+ return localize('state.ready', "Ready");
+ case 'deleting':
+ return localize('state.deleting', "Deleting");
+ case 'waitingfordeletion':
+ return localize('state.waitingForDeletion', "Waiting For Deletion");
+ case 'deleted':
+ return localize('state.deleted', "Deleted");
+ case 'upgrading':
+ return localize('state.upgrading', "Upgrading");
+ case 'waitingforupgrade':
+ return localize('state.waitingForUpgrade', "Waiting For Upgrade");
+ case 'error':
+ return localize('state.error', "Error");
+ case 'running':
+ return localize('state.running', "Running");
+ default:
+ return state;
+ }
+}
+
+/**
+ * Gets the localized text to display for a corresponding endpoint
+ * @param serviceName The endpoint name to get the display text for
+ * @param description The backup description to use if we don't have our own
+ */
+export function getEndpointDisplayText(endpointName?: string, description?: string): string {
+ endpointName = endpointName || '';
+ switch (endpointName.toLowerCase()) {
+ case Endpoint.appProxy:
+ return localize('endpoint.appproxy', "Application Proxy");
+ case Endpoint.controller:
+ return localize('endpoint.controller', "Controller");
+ case Endpoint.gateway:
+ return localize('endpoint.gateway', "HDFS/Spark Gateway");
+ case Endpoint.managementProxy:
+ return localize('endpoint.managementproxy', "Management Proxy");
+ case Endpoint.mgmtproxy:
+ return localize('endpoint.mgmtproxy', "Management Proxy");
+ case Endpoint.sqlServerMaster:
+ return localize('endpoint.sqlServerEndpoint', "SQL Server Master Instance");
+ case Endpoint.metricsui:
+ return localize('endpoint.grafana', "Metrics Dashboard");
+ case Endpoint.logsui:
+ return localize('endpoint.kibana', "Log Search Dashboard");
+ case Endpoint.yarnUi:
+ return localize('endpoint.yarnHistory', "Spark Resource Management");
+ case Endpoint.sparkHistory:
+ return localize('endpoint.sparkHistory', "Spark Job Monitoring");
+ case Endpoint.webhdfs:
+ return localize('endpoint.webhdfs', "HDFS File System Proxy");
+ case Endpoint.livy:
+ return localize('endpoint.livy', "Spark Proxy");
+ default:
+ // Default is to use the description if one was given, otherwise worst case just fall back to using the
+ // original service name
+ return description && description.length > 0 ? description : endpointName;
+ }
+}
+
+/**
+ * Gets the localized text to display for a corresponding service
+ * @param serviceName The service name to get the display text for
+ */
+export function getServiceNameDisplayText(serviceName?: string): string {
+ serviceName = serviceName || '';
+ switch (serviceName.toLowerCase()) {
+ case Service.sql:
+ return localize('service.sql', "SQL Server");
+ case Service.hdfs:
+ return localize('service.hdfs', "HDFS");
+ case Service.spark:
+ return localize('service.spark', "Spark");
+ case Service.control:
+ return localize('service.control', "Control");
+ case Service.gateway:
+ return localize('service.gateway', "Gateway");
+ case Service.app:
+ return localize('service.app', "App");
+ default:
+ return serviceName;
+ }
+}
+
+/**
+ * Gets the localized text to display for a corresponding health status
+ * @param healthStatus The health status to get the display text for
+ */
+export function getHealthStatusDisplayText(healthStatus?: string) {
+ healthStatus = healthStatus || '';
+ switch (healthStatus.toLowerCase()) {
+ case 'healthy':
+ return localize('bdc.healthy', "Healthy");
+ case 'unhealthy':
+ return localize('bdc.unhealthy', "Unhealthy");
+ default:
+ return healthStatus;
+ }
+}
+
+/**
+ * Returns the status icon for the corresponding health status
+ * @param healthStatus The status to check
+ */
+export function getHealthStatusIcon(healthStatus?: string): string {
+ healthStatus = healthStatus || '';
+ switch (healthStatus.toLowerCase()) {
+ case 'healthy':
+ return '✔️';
+ default:
+ // Consider all non-healthy status' as errors
+ return '⚠️';
+ }
+}
+
+/**
+ * Returns the status dot string which will be a • for all non-healthy states
+ * @param healthStatus The status to check
+ */
+export function getHealthStatusDot(healthStatus?: string): string {
+ healthStatus = healthStatus || '';
+ switch (healthStatus.toLowerCase()) {
+ case 'healthy':
+ return '';
+ default:
+ // Display status dot for all non-healthy status'
+ return '•';
+ }
+}
diff --git a/extensions/big-data-cluster/src/extension.ts b/extensions/big-data-cluster/src/extension.ts
index a02e9d2cb4..7fbaf3e491 100644
--- a/extensions/big-data-cluster/src/extension.ts
+++ b/extensions/big-data-cluster/src/extension.ts
@@ -8,7 +8,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ControllerTreeDataProvider } from './bigDataCluster/tree/controllerTreeDataProvider';
-import { IconPath } from './bigDataCluster/constants';
+import { IconPathHelper } from './bigDataCluster/constants';
import { TreeNode } from './bigDataCluster/tree/treeNode';
import { AddControllerDialogModel, AddControllerDialog } from './bigDataCluster/dialog/addControllerDialog';
import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
@@ -25,7 +25,7 @@ const ManageControllerCommand = 'bigDataClusters.command.manageController';
let throttleTimers: { [key: string]: any } = {};
export function activate(extensionContext: vscode.ExtensionContext) {
- IconPath.setExtensionContext(extensionContext);
+ IconPathHelper.setExtensionContext(extensionContext);
let treeDataProvider = new ControllerTreeDataProvider(extensionContext.globalState);
registerTreeDataProvider(treeDataProvider);
registerCommands(extensionContext, treeDataProvider);