mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Add styling and component column type to declarative table (#8476)
* Initial wip * wip * Working implementation * Make widths a bit nicer and remove sqlops addition * Add sqlops back in * Fix timing issue with tables * Undo change to sql.bat and remove loading component when done
This commit is contained in:
@@ -58,7 +58,7 @@ export class IconPathHelper {
|
|||||||
|
|
||||||
export namespace cssStyles {
|
export namespace cssStyles {
|
||||||
export const title = { 'font-size': '14px', 'font-weight': '600' };
|
export const title = { 'font-size': '14px', 'font-weight': '600' };
|
||||||
export const tableHeader = { '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 hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
|
export const hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
|
||||||
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 overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' };
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
private overviewPage: BdcDashboardOverviewPage;
|
private overviewPage: BdcDashboardOverviewPage;
|
||||||
|
|
||||||
private currentTab: NavTab;
|
private currentTab: NavTab;
|
||||||
private currentPage: azdata.FlexContainer;
|
private currentPageContainer: azdata.FlexContainer;
|
||||||
|
|
||||||
private refreshButton: azdata.ButtonComponent;
|
private refreshButton: azdata.ButtonComponent;
|
||||||
|
|
||||||
private serviceTabPageMapping = new Map<string, { navTab: NavTab, servicePage: azdata.FlexContainer }>();
|
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();
|
||||||
@@ -141,7 +141,7 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
|
overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } });
|
||||||
this.overviewPage = new BdcDashboardOverviewPage(this, this.model);
|
this.overviewPage = new BdcDashboardOverviewPage(this, this.model);
|
||||||
const overviewContainer: azdata.FlexContainer = this.overviewPage.create(modelView);
|
const overviewContainer: azdata.FlexContainer = this.overviewPage.create(modelView);
|
||||||
this.currentPage = overviewContainer;
|
this.currentPageContainer = overviewContainer;
|
||||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||||
|
|
||||||
@@ -150,9 +150,9 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
||||||
this.currentTab.div.ariaSelected = false;
|
this.currentTab.div.ariaSelected = false;
|
||||||
}
|
}
|
||||||
this.mainAreaContainer.removeItem(this.currentPage);
|
this.mainAreaContainer.removeItem(this.currentPageContainer);
|
||||||
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||||
this.currentPage = overviewContainer;
|
this.currentPageContainer = overviewContainer;
|
||||||
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText };
|
||||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
this.currentTab.text.updateCssStyles(selectedTabCss);
|
||||||
this.currentTab.div.ariaSelected = true;
|
this.currentTab.div.ariaSelected = true;
|
||||||
@@ -211,9 +211,9 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
this.currentTab.text.updateCssStyles(unselectedTabCss);
|
||||||
this.currentTab.div.ariaSelected = false;
|
this.currentTab.div.ariaSelected = false;
|
||||||
}
|
}
|
||||||
this.mainAreaContainer.removeItem(this.currentPage);
|
this.mainAreaContainer.removeItem(this.currentPageContainer);
|
||||||
this.mainAreaContainer.addItem(tabPageMapping.servicePage, { CSSStyles: { 'margin': '0 20px 0 20px' } });
|
this.mainAreaContainer.addItem(tabPageMapping.servicePage.container, { CSSStyles: { 'margin': '0 20px 0 20px' } });
|
||||||
this.currentPage = tabPageMapping.servicePage;
|
this.currentPageContainer = tabPageMapping.servicePage.container;
|
||||||
this.currentTab = tabPageMapping.navTab;
|
this.currentTab = tabPageMapping.navTab;
|
||||||
this.currentTab.text.updateCssStyles(selectedTabCss);
|
this.currentTab.text.updateCssStyles(selectedTabCss);
|
||||||
this.currentTab.div.ariaSelected = true;
|
this.currentTab.div.ariaSelected = true;
|
||||||
@@ -233,7 +233,7 @@ export class BdcDashboard extends BdcDashboardPage {
|
|||||||
} else {
|
} else {
|
||||||
// New service - create the page and tab
|
// New service - create the page and tab
|
||||||
const navItem = createServiceNavTab(this.modelView.modelBuilder, s);
|
const navItem = createServiceNavTab(this.modelView.modelBuilder, s);
|
||||||
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView).container;
|
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView);
|
||||||
this.serviceTabPageMapping.set(s.serviceName, { navTab: navItem, servicePage: serviceStatusPage });
|
this.serviceTabPageMapping.set(s.serviceName, { navTab: navItem, servicePage: serviceStatusPage });
|
||||||
navItem.div.onDidClick(() => {
|
navItem.div.onDidClick(() => {
|
||||||
this.switchToServiceTab(s.serviceName);
|
this.switchToServiceTab(s.serviceName);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
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, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
|
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||||
import { BdcDashboard } from './bdcDashboard';
|
import { BdcDashboard } from './bdcDashboard';
|
||||||
import { createViewDetailsButton } from './commonControls';
|
import { createViewDetailsButton } from './commonControls';
|
||||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||||
@@ -21,14 +21,6 @@ const clusterStateLabelColumnWidth = 100;
|
|||||||
const clusterStateValueColumnWidth = 225;
|
const clusterStateValueColumnWidth = 225;
|
||||||
const healthStatusColumnWidth = 125;
|
const healthStatusColumnWidth = 125;
|
||||||
|
|
||||||
const overviewIconColumnWidthPx = 25;
|
|
||||||
const overviewServiceNameCellWidthPx = 175;
|
|
||||||
const overviewStateCellWidthPx = 150;
|
|
||||||
const overviewHealthStatusCellWidthPx = 100;
|
|
||||||
|
|
||||||
const serviceEndpointRowServiceNameCellWidth = 200;
|
|
||||||
const serviceEndpointRowEndpointCellWidth = 350;
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -40,11 +32,12 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
private clusterStateLoadingComponent: azdata.LoadingComponent;
|
private clusterStateLoadingComponent: azdata.LoadingComponent;
|
||||||
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
|
private clusterHealthStatusLoadingComponent: azdata.LoadingComponent;
|
||||||
|
|
||||||
private serviceStatusRowContainer: azdata.FlexContainer;
|
private serviceStatusTable: azdata.DeclarativeTableComponent;
|
||||||
|
private endpointsTable: azdata.DeclarativeTableComponent;
|
||||||
private endpointsRowContainer: azdata.FlexContainer;
|
private endpointsLoadingComponent: azdata.LoadingComponent;
|
||||||
private endpointsDisplayContainer: azdata.DivContainer;
|
private endpointsDisplayContainer: azdata.FlexContainer;
|
||||||
private serviceStatusDisplayContainer: azdata.DivContainer;
|
private serviceStatusLoadingComponent: azdata.LoadingComponent;
|
||||||
|
private serviceStatusDisplayContainer: azdata.FlexContainer;
|
||||||
private propertiesErrorMessage: azdata.TextComponent;
|
private propertiesErrorMessage: azdata.TextComponent;
|
||||||
private endpointsErrorMessage: azdata.TextComponent;
|
private endpointsErrorMessage: azdata.TextComponent;
|
||||||
private serviceStatusErrorMessage: azdata.TextComponent;
|
private serviceStatusErrorMessage: azdata.TextComponent;
|
||||||
@@ -126,33 +119,114 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
|
|
||||||
const overviewContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
const overviewContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||||
|
|
||||||
// Service Status header row
|
this.serviceStatusTable = view.modelBuilder.declarativeTable()
|
||||||
const serviceStatusHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.serviceNameHeader', "Service Name") }).component();
|
{
|
||||||
// Service name cell covers both icon + service name so width stretches both cells
|
columns: [
|
||||||
serviceStatusHeaderRow.addItem(nameCell, { CSSStyles: { 'width': `${overviewServiceNameCellWidthPx + overviewIconColumnWidthPx}px`, 'min-width': `${overviewServiceNameCellWidthPx + overviewIconColumnWidthPx}px`, ...cssStyles.tableHeader } });
|
{ // status icon
|
||||||
const stateCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.stateHeader', "State"), CSSStyles: { ...cssStyles.tableHeader } }).component();
|
displayName: '',
|
||||||
serviceStatusHeaderRow.addItem(stateCell, { CSSStyles: { 'width': `${overviewStateCellWidthPx}px`, 'min-width': `${overviewStateCellWidthPx}px` } });
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
const healthStatusCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status"), CSSStyles: { ...cssStyles.tableHeader } }).component();
|
isReadOnly: true,
|
||||||
serviceStatusHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${overviewHealthStatusCellWidthPx}px`, 'min-width': `${overviewHealthStatusCellWidthPx}px` } });
|
width: 25,
|
||||||
overviewContainer.addItem(serviceStatusHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF'
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // service
|
||||||
|
displayName: localize('bdc.dashboard.serviceNameHeader', "Service Name"),
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 175,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // state
|
||||||
|
displayName: localize('bdc.dashboard.stateHeader', "State"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 150,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // health status
|
||||||
|
displayName: localize('bdc.dashboard.healthStatusHeader', "Health Status"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
'text-align': 'left',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ // view details button
|
||||||
|
displayName: '',
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 150,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF'
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: []
|
||||||
|
})
|
||||||
|
.component();
|
||||||
|
|
||||||
this.serviceStatusDisplayContainer = view.modelBuilder.divContainer().component();
|
this.serviceStatusDisplayContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
|
this.serviceStatusDisplayContainer.addItem(this.serviceStatusTable);
|
||||||
|
|
||||||
// Service Status row container
|
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||||
this.serviceStatusRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||||
// Note we don't give the rows container as a child of the loading component since in order to align the loading component correctly
|
// component once it's finished loading the content
|
||||||
// messes up the layout for the row container that we display after loading is finished. Instead we just remove the loading component
|
this.serviceStatusLoadingComponent = view.modelBuilder.loadingComponent()
|
||||||
// and replace it with the rows directly
|
|
||||||
const serviceStatusRowContainerLoadingComponent = view.modelBuilder.loadingComponent()
|
|
||||||
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||||
.component();
|
.component();
|
||||||
this.serviceStatusRowContainer.addItem(serviceStatusRowContainerLoadingComponent, { 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 = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||||
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
||||||
|
|
||||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusRowContainer);
|
|
||||||
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
||||||
|
|
||||||
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
|
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
|
||||||
@@ -170,25 +244,77 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
|
|
||||||
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||||
|
|
||||||
// Service endpoints header row
|
this.endpointsTable = view.modelBuilder.declarativeTable()
|
||||||
const endpointsHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
const endpointsServiceNameHeaderCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.serviceHeader', "Service") }).component();
|
{
|
||||||
endpointsHeaderRow.addItem(endpointsServiceNameHeaderCell, { CSSStyles: { 'width': `${serviceEndpointRowServiceNameCellWidth}px`, 'min-width': `${serviceEndpointRowServiceNameCellWidth}px`, ...cssStyles.tableHeader } });
|
columns: [
|
||||||
const endpointsEndpointHeaderCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.endpointHeader', "Endpoint") }).component();
|
{ // service
|
||||||
endpointsHeaderRow.addItem(endpointsEndpointHeaderCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px`, ...cssStyles.tableHeader } });
|
displayName: localize('bdc.dashboard.serviceHeader', "Service"),
|
||||||
endpointsContainer.addItem(endpointsHeaderRow, { CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 200,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // endpoint
|
||||||
|
displayName: localize('bdc.dashboard.endpointHeader', "Endpoint"),
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 350,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none',
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'text-overflow': 'ellipsis'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // copy
|
||||||
|
displayName: '',
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 50,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF'
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: []
|
||||||
|
}).component();
|
||||||
|
|
||||||
this.endpointsDisplayContainer = view.modelBuilder.divContainer().component();
|
this.endpointsDisplayContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
this.endpointsRowContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
this.endpointsDisplayContainer.addItem(this.endpointsTable);
|
||||||
// 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
|
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||||
// and replace it with the rows directly
|
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||||
const endpointRowContainerLoadingComponent = view.modelBuilder.loadingComponent()
|
// component once it's finished loading the content
|
||||||
|
this.endpointsLoadingComponent = view.modelBuilder.loadingComponent()
|
||||||
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
.withProperties({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||||
.component();
|
.component();
|
||||||
this.endpointsRowContainer.addItem(endpointRowContainerLoadingComponent, { 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' } });
|
||||||
|
|
||||||
this.endpointsDisplayContainer.addItem(this.endpointsRowContainer);
|
|
||||||
endpointsContainer.addItem(this.endpointsErrorMessage);
|
endpointsContainer.addItem(this.endpointsErrorMessage);
|
||||||
endpointsContainer.addItem(this.endpointsDisplayContainer);
|
endpointsContainer.addItem(this.endpointsDisplayContainer);
|
||||||
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
|
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
|
||||||
@@ -228,16 +354,37 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
(<azdata.TextComponent>this.clusterHealthStatusLoadingComponent.component).value = getHealthStatusDisplayText(bdcStatus.healthStatus);
|
(<azdata.TextComponent>this.clusterHealthStatusLoadingComponent.component).value = getHealthStatusDisplayText(bdcStatus.healthStatus);
|
||||||
|
|
||||||
if (bdcStatus.services) {
|
if (bdcStatus.services) {
|
||||||
this.serviceStatusRowContainer.clearItems();
|
this.serviceStatusTable.data = bdcStatus.services.map(serviceStatus => {
|
||||||
bdcStatus.services.forEach((s, i) => {
|
const statusIconCell = this.modelBuilder.text()
|
||||||
this.createServiceStatusRow(this.serviceStatusRowContainer, s, i === bdcStatus.services.length - 1);
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
|
value: getHealthStatusIcon(serviceStatus.healthStatus),
|
||||||
|
ariaRole: 'img',
|
||||||
|
title: getHealthStatusDisplayText(serviceStatus.healthStatus),
|
||||||
|
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
||||||
|
}).component();
|
||||||
|
const nameCell = this.modelBuilder.hyperlink()
|
||||||
|
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
|
label: getServiceNameDisplayText(serviceStatus.serviceName),
|
||||||
|
url: '',
|
||||||
|
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
||||||
|
}).component();
|
||||||
|
nameCell.onDidClick(() => {
|
||||||
|
this.dashboard.switchToServiceTab(serviceStatus.serviceName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const viewDetailsButton = serviceStatus.healthStatus !== 'healthy' && serviceStatus.details && serviceStatus.details.length > 0 ? createViewDetailsButton(this.modelBuilder, serviceStatus.details) : undefined;
|
||||||
|
return [
|
||||||
|
statusIconCell,
|
||||||
|
nameCell,
|
||||||
|
getStateDisplayText(serviceStatus.state),
|
||||||
|
getHealthStatusDisplayText(serviceStatus.healthStatus),
|
||||||
|
viewDetailsButton];
|
||||||
|
});
|
||||||
|
this.serviceStatusDisplayContainer.removeItem(this.serviceStatusLoadingComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleEndpointsUpdate(endpoints: EndpointModel[]): void {
|
private handleEndpointsUpdate(endpoints: EndpointModel[]): void {
|
||||||
this.endpointsRowContainer.clearItems();
|
|
||||||
|
|
||||||
// Sort the endpoints. The sort method is that SQL Server Master is first - followed by all
|
// Sort the endpoints. The sort method is that SQL Server Master is first - followed by all
|
||||||
// others in alphabetical order by endpoint
|
// others in alphabetical order by endpoint
|
||||||
const sqlServerMasterEndpoints = endpoints.filter(e => e.name === Endpoint.sqlServerMaster);
|
const sqlServerMasterEndpoints = endpoints.filter(e => e.name === Endpoint.sqlServerMaster);
|
||||||
@@ -249,9 +396,21 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
});
|
});
|
||||||
endpoints.unshift(...sqlServerMasterEndpoints);
|
endpoints.unshift(...sqlServerMasterEndpoints);
|
||||||
|
|
||||||
endpoints.forEach((e, i) => {
|
this.endpointsTable.data = endpoints.map(e => {
|
||||||
createServiceEndpointRow(this.modelBuilder, this.endpointsRowContainer, e, this.model, hyperlinkedEndpoints.some(he => he === e.name), i === endpoints.length - 1);
|
const copyValueCell = this.modelBuilder.button().withProperties<azdata.ButtonProperties>({ title: localize('bdc.dashboard.copyTitle', "Copy") }).component();
|
||||||
|
copyValueCell.iconPath = IconPathHelper.copy;
|
||||||
|
copyValueCell.onDidClick(() => {
|
||||||
|
vscode.env.clipboard.writeText(e.endpoint);
|
||||||
|
vscode.window.showInformationMessage(localize('copiedEndpoint', "Endpoint '{0}' copied to clipboard", getEndpointDisplayText(e.name, e.description)));
|
||||||
});
|
});
|
||||||
|
copyValueCell.iconHeight = '14px';
|
||||||
|
copyValueCell.iconWidth = '14px';
|
||||||
|
return [getEndpointDisplayText(e.name, e.description),
|
||||||
|
createEndpointComponent(this.modelBuilder, e, this.model, hyperlinkedEndpoints.some(he => he === e.name)), //e.endpoint,
|
||||||
|
copyValueCell];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.endpointsDisplayContainer.removeItem(this.endpointsLoadingComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBdcError(errorEvent: BdcErrorEvent): void {
|
private handleBdcError(errorEvent: BdcErrorEvent): void {
|
||||||
@@ -291,54 +450,17 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
|||||||
this.showEndpointsError(errorMessage);
|
this.showEndpointsError(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createServiceStatusRow(container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void {
|
|
||||||
const serviceStatusRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
|
|
||||||
const statusIconCell = this.modelBuilder.text()
|
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: getHealthStatusIcon(serviceStatus.healthStatus),
|
|
||||||
ariaRole: 'img',
|
|
||||||
title: getHealthStatusDisplayText(serviceStatus.healthStatus),
|
|
||||||
CSSStyles: { 'user-select': 'none' }
|
|
||||||
}).component();
|
|
||||||
serviceStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': `${overviewIconColumnWidthPx}px`, 'min-width': `${overviewIconColumnWidthPx}px` } });
|
|
||||||
const nameCell = this.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
|
||||||
label: getServiceNameDisplayText(serviceStatus.serviceName),
|
|
||||||
url: '',
|
|
||||||
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
|
||||||
}).component();
|
|
||||||
nameCell.onDidClick(() => {
|
|
||||||
this.dashboard.switchToServiceTab(serviceStatus.serviceName);
|
|
||||||
});
|
|
||||||
serviceStatusRow.addItem(nameCell, { CSSStyles: { 'width': `${overviewServiceNameCellWidthPx}px`, 'min-width': `${overviewServiceNameCellWidthPx}px`, ...cssStyles.text } });
|
|
||||||
const stateText = getStateDisplayText(serviceStatus.state);
|
|
||||||
const stateCell = this.modelBuilder.text().withProperties({ value: stateText, title: stateText, CSSStyles: { ...cssStyles.overflowEllipsisText } }).component();
|
|
||||||
serviceStatusRow.addItem(stateCell, { CSSStyles: { 'width': `${overviewStateCellWidthPx}px`, 'min-width': `${overviewStateCellWidthPx}px` } });
|
|
||||||
const healthStatusText = getHealthStatusDisplayText(serviceStatus.healthStatus);
|
|
||||||
const healthStatusCell = this.modelBuilder.text().withProperties({ value: healthStatusText, title: healthStatusText, CSSStyles: { ...cssStyles.overflowEllipsisText } }).component();
|
|
||||||
serviceStatusRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${overviewHealthStatusCellWidthPx}px`, 'min-width': `${overviewHealthStatusCellWidthPx}px` } });
|
|
||||||
|
|
||||||
if (serviceStatus.healthStatus !== 'healthy' && serviceStatus.details && serviceStatus.details.length > 0) {
|
|
||||||
serviceStatusRow.addItem(createViewDetailsButton(this.modelBuilder, serviceStatus.details), { flex: '0 0 auto' });
|
|
||||||
}
|
|
||||||
|
|
||||||
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, endpoint: EndpointModel, bdcModel: BdcDashboardModel, isHyperlink: boolean, isLastRow: boolean): void {
|
function createEndpointComponent(modelBuilder: azdata.ModelBuilder, endpoint: EndpointModel, bdcModel: BdcDashboardModel, isHyperlink: boolean): azdata.HyperlinkComponent | azdata.TextComponent {
|
||||||
const endPointRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '40px' }).component();
|
|
||||||
const nameCell = modelBuilder.text().withProperties({ value: getEndpointDisplayText(endpoint.name, endpoint.description), CSSStyles: { ...cssStyles.text } }).component();
|
|
||||||
endPointRow.addItem(nameCell, { CSSStyles: { 'width': `${serviceEndpointRowServiceNameCellWidth}px`, 'min-width': `${serviceEndpointRowServiceNameCellWidth}px`, 'text-align': 'center' } });
|
|
||||||
if (isHyperlink) {
|
if (isHyperlink) {
|
||||||
const endpointCell = modelBuilder.hyperlink()
|
return modelBuilder.hyperlink()
|
||||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: endpoint.endpoint,
|
label: endpoint.endpoint,
|
||||||
title: endpoint.endpoint,
|
title: endpoint.endpoint,
|
||||||
url: endpoint.endpoint, CSSStyles: { 'height': '15px' }
|
url: endpoint.endpoint, CSSStyles: { ...cssStyles.hyperlink }
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px`, 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.hyperlink } });
|
|
||||||
}
|
}
|
||||||
else if (endpoint.name === Endpoint.sqlServerMaster) {
|
else if (endpoint.name === Endpoint.sqlServerMaster) {
|
||||||
const endpointCell = modelBuilder.hyperlink()
|
const endpointCell = modelBuilder.hyperlink()
|
||||||
@@ -346,7 +468,7 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
|
|||||||
title: endpoint.endpoint,
|
title: endpoint.endpoint,
|
||||||
label: endpoint.endpoint,
|
label: endpoint.endpoint,
|
||||||
url: '',
|
url: '',
|
||||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text, ...cssStyles.hyperlink }
|
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
||||||
}).component();
|
}).component();
|
||||||
endpointCell.onDidClick(async () => {
|
endpointCell.onDidClick(async () => {
|
||||||
const connProfile = bdcModel.getSqlServerMasterConnectionProfile();
|
const connProfile = bdcModel.getSqlServerMasterConnectionProfile();
|
||||||
@@ -361,27 +483,15 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
|
|||||||
azdata.connection.openConnectionDialog(undefined, connProfile);
|
azdata.connection.openConnectionDialog(undefined, connProfile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px` } });
|
return endpointCell;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const endpointCell = modelBuilder.text()
|
return modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
value: endpoint.endpoint,
|
value: endpoint.endpoint,
|
||||||
title: endpoint.endpoint,
|
title: endpoint.endpoint,
|
||||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text }
|
CSSStyles: { ...cssStyles.text }
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': `${serviceEndpointRowEndpointCellWidth}px`, 'min-width': `${serviceEndpointRowEndpointCellWidth}px` } });
|
|
||||||
}
|
}
|
||||||
const copyValueCell = modelBuilder.button().withProperties<azdata.ButtonProperties>({ title: localize('bdc.dashboard.copyTitle', "Copy") }).component();
|
|
||||||
copyValueCell.iconPath = IconPathHelper.copy;
|
|
||||||
copyValueCell.onDidClick(() => {
|
|
||||||
vscode.env.clipboard.writeText(endpoint.endpoint);
|
|
||||||
vscode.window.showInformationMessage(localize('copiedEndpoint', "Endpoint '{0}' copied to clipboard", getEndpointDisplayText(endpoint.name, endpoint.description)));
|
|
||||||
});
|
|
||||||
copyValueCell.iconHeight = '14px';
|
|
||||||
copyValueCell.iconWidth = '14px';
|
|
||||||
endPointRow.addItem(copyValueCell, { CSSStyles: { 'width': '14px', 'min-width': '14px', 'padding-left': '10px', ...cssStyles.text } });
|
|
||||||
|
|
||||||
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' } });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||||
import { BdcStatusModel, InstanceStatusModel } from '../controller/apiGenerated';
|
import { BdcStatusModel, InstanceStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
||||||
import { getHealthStatusDisplayText, getHealthStatusIcon, getStateDisplayText, Service } from '../utils';
|
import { getHealthStatusDisplayText, getHealthStatusIcon, getStateDisplayText, Service } from '../utils';
|
||||||
import { cssStyles } from '../constants';
|
import { cssStyles } from '../constants';
|
||||||
import { isNullOrUndefined } from 'util';
|
import { isNullOrUndefined } from 'util';
|
||||||
@@ -15,38 +15,35 @@ import { BdcDashboardPage } from './bdcDashboardPage';
|
|||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
const healthAndStatusIconColumnWidth = 25;
|
|
||||||
const healthAndStatusInstanceNameColumnWidth = 100;
|
|
||||||
const healthAndStatusStateColumnWidth = 150;
|
|
||||||
const healthAndStatusHealthColumnWidth = 100;
|
|
||||||
|
|
||||||
const metricsAndLogsInstanceNameColumnWidth = 125;
|
|
||||||
const metricsAndLogsNodeMetricsColumnWidth = 80;
|
|
||||||
const metricsAndLogsSqlMetricsColumnWidth = 80;
|
|
||||||
const metricsAndLogsLogsColumnWidth = 75;
|
|
||||||
|
|
||||||
const viewText = localize('bdc.dashboard.viewHyperlink', "View");
|
const viewText = localize('bdc.dashboard.viewHyperlink', "View");
|
||||||
const notAvailableText = localize('bdc.dashboard.notAvailable', "N/A");
|
const notAvailableText = localize('bdc.dashboard.notAvailable', "N/A");
|
||||||
|
|
||||||
export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
||||||
|
|
||||||
|
private resourceStatusModel: ResourceStatusModel;
|
||||||
private rootContainer: azdata.FlexContainer;
|
private rootContainer: azdata.FlexContainer;
|
||||||
private instanceHealthStatusRowsContainer: azdata.FlexContainer;
|
private instanceHealthStatusTable: azdata.DeclarativeTableComponent;
|
||||||
private metricsAndLogsRowsContainer: azdata.FlexContainer;
|
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(private model: BdcDashboardModel, private modelView: azdata.ModelView, private serviceName: string, private resourceName: string) {
|
||||||
super();
|
super();
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
this.rootContainer = this.createContainer(modelView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get container(): azdata.FlexContainer {
|
public get container(): azdata.FlexContainer {
|
||||||
|
// Lazily create the container only when needed
|
||||||
|
if (!this.rootContainer) {
|
||||||
|
// We do this here so that we can have the resource model to use for populating the data
|
||||||
|
// in the tables. This is to get around a timing issue with ModelView tables
|
||||||
|
this.updateResourceStatusModel(this.model.bdcStatus);
|
||||||
|
this.createContainer();
|
||||||
|
}
|
||||||
return this.rootContainer;
|
return this.rootContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createContainer(view: azdata.ModelView): azdata.FlexContainer {
|
private createContainer(): void {
|
||||||
const rootContainer = view.modelBuilder.flexContainer().withLayout(
|
this.rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
||||||
{
|
{
|
||||||
flexFlow: 'column',
|
flexFlow: 'column',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -57,11 +54,11 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
|||||||
// # INSTANCE HEALTH AND STATUS #
|
// # INSTANCE HEALTH AND STATUS #
|
||||||
// ##############################
|
// ##############################
|
||||||
|
|
||||||
const healthStatusHeaderContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
const healthStatusHeaderContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
||||||
rootContainer.addItem(healthStatusHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
this.rootContainer.addItem(healthStatusHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
||||||
|
|
||||||
// Header label
|
// Header label
|
||||||
const healthStatusHeaderLabel = view.modelBuilder.text()
|
const healthStatusHeaderLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
value: localize('bdc.dashboard.healthStatusDetailsHeader', "Health Status Details"),
|
value: localize('bdc.dashboard.healthStatusDetailsHeader', "Health Status Details"),
|
||||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' }
|
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' }
|
||||||
@@ -71,180 +68,306 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
|||||||
healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { ...cssStyles.title } });
|
healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { ...cssStyles.title } });
|
||||||
|
|
||||||
// Last updated label
|
// Last updated label
|
||||||
this.lastUpdatedLabel = view.modelBuilder.text()
|
this.lastUpdatedLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: localize('bdc.dashboard.lastUpdated', "Last Updated : {0}", '-'),
|
value: this.getLastUpdatedText(),
|
||||||
CSSStyles: { ...cssStyles.lastUpdatedText }
|
CSSStyles: { ...cssStyles.lastUpdatedText }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
healthStatusHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
healthStatusHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
||||||
|
|
||||||
// Header row
|
this.instanceHealthStatusTable = this.modelView.modelBuilder.declarativeTable()
|
||||||
const instanceHealthStatusHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
const instanceHealthAndStatusNameHeader = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.instanceHeader', "Instance") }).component();
|
{
|
||||||
// Instance name cell covers both icon + service name so width stretches both cells
|
columns: [
|
||||||
instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusNameHeader, { CSSStyles: { 'width': `${healthAndStatusIconColumnWidth + healthAndStatusInstanceNameColumnWidth}px`, 'min-width': `${healthAndStatusIconColumnWidth + healthAndStatusInstanceNameColumnWidth}px`, ...cssStyles.tableHeader } });
|
{ // status icon
|
||||||
const instanceHealthAndStatusState = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.stateHeader', "State") }).component();
|
displayName: '',
|
||||||
instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusState, { CSSStyles: { 'width': `${healthAndStatusStateColumnWidth}px`, 'min-width': `${healthAndStatusStateColumnWidth}px`, ...cssStyles.tableHeader } });
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
const instanceHealthAndStatusHealthStatus = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.healthStatusHeader', "Health Status") }).component();
|
isReadOnly: true,
|
||||||
instanceHealthStatusHeaderRow.addItem(instanceHealthAndStatusHealthStatus, { CSSStyles: { 'width': `${healthAndStatusHealthColumnWidth}px`, 'min-width': `${healthAndStatusHealthColumnWidth}px`, ...cssStyles.tableHeader } });
|
width: 25,
|
||||||
rootContainer.addItem(instanceHealthStatusHeaderRow, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box' } });
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
this.instanceHealthStatusRowsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
'background-color': '#FFFFFF'
|
||||||
rootContainer.addItem(this.instanceHealthStatusRowsContainer, { flex: '0 0 auto' });
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // instance
|
||||||
|
displayName: localize('bdc.dashboard.instanceHeader', "Instance"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // state
|
||||||
|
displayName: localize('bdc.dashboard.stateHeader', "State"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 150,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // health status
|
||||||
|
displayName: localize('bdc.dashboard.healthStatusHeader', "Health Status"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
'text-align': 'left',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ // view details button
|
||||||
|
displayName: '',
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 150,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF'
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: this.createHealthStatusRows()
|
||||||
|
}).component();
|
||||||
|
this.rootContainer.addItem(this.instanceHealthStatusTable, { flex: '0 0 auto' });
|
||||||
|
|
||||||
// ####################
|
// ####################
|
||||||
// # METRICS AND LOGS #
|
// # METRICS AND LOGS #
|
||||||
// ####################
|
// ####################
|
||||||
|
|
||||||
// Title label
|
// Title label
|
||||||
const endpointsLabel = view.modelBuilder.text()
|
const endpointsLabel = this.modelView.modelBuilder.text()
|
||||||
.withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.metricsAndLogsLabel', "Metrics and Logs"), CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
.withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.metricsAndLogsLabel', "Metrics and Logs"), CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
||||||
.component();
|
.component();
|
||||||
rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
this.rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
||||||
|
|
||||||
|
let metricsAndLogsColumns: azdata.DeclarativeTableColumn[] =
|
||||||
|
[
|
||||||
|
{ // instance
|
||||||
|
displayName: localize('bdc.dashboard.instanceHeader', "Instance"),
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 125,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // node metrics
|
||||||
|
displayName: localize('bdc.dashboard.nodeMetricsHeader', "Node Metrics"),
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// Header row
|
|
||||||
const metricsAndLogsHeaderRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
|
||||||
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.instanceHeader', "Instance") }).component();
|
|
||||||
metricsAndLogsHeaderRow.addItem(nameCell, { CSSStyles: { 'width': `${metricsAndLogsInstanceNameColumnWidth}px`, 'min-width': `${metricsAndLogsInstanceNameColumnWidth}px`, ...cssStyles.tableHeader } });
|
|
||||||
const nodeMetricsCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.nodeMetricsHeader', "Node Metrics") }).component();
|
|
||||||
metricsAndLogsHeaderRow.addItem(nodeMetricsCell, { CSSStyles: { 'width': `${metricsAndLogsNodeMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsNodeMetricsColumnWidth}px`, ...cssStyles.tableHeader } });
|
|
||||||
// Only show SQL metrics column for SQL resource instances
|
// Only show SQL metrics column for SQL resource instances
|
||||||
if (this.serviceName.toLowerCase() === Service.sql) {
|
if (this.serviceName.toLowerCase() === Service.sql) {
|
||||||
const sqlMetricsCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.sqlMetricsHeader', "SQL Metrics") }).component();
|
metricsAndLogsColumns.push(
|
||||||
metricsAndLogsHeaderRow.addItem(sqlMetricsCell, { CSSStyles: { 'width': `${metricsAndLogsSqlMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsSqlMetricsColumnWidth}px`, ...cssStyles.tableHeader } });
|
{ // sql metrics
|
||||||
|
displayName: localize('bdc.dashboard.sqlMetricsHeader', "SQL Metrics"),
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
'text-align': 'left',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const healthStatusCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: localize('bdc.dashboard.logsHeader', "Logs") }).component();
|
|
||||||
metricsAndLogsHeaderRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${metricsAndLogsLogsColumnWidth}px`, 'min-width': `${metricsAndLogsLogsColumnWidth}px`, ...cssStyles.tableHeader } });
|
|
||||||
rootContainer.addItem(metricsAndLogsHeaderRow, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px', 'box-sizing': 'border-box' } });
|
|
||||||
|
|
||||||
this.metricsAndLogsRowsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
metricsAndLogsColumns.push(
|
||||||
rootContainer.addItem(this.metricsAndLogsRowsContainer, { flex: '0 0 auto' });
|
{ // logs
|
||||||
|
displayName: localize('bdc.dashboard.logsHeader', "Logs"),
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: 100,
|
||||||
|
headerCssStyles: {
|
||||||
|
'border': 'none',
|
||||||
|
'background-color': '#FFFFFF',
|
||||||
|
'text-align': 'left',
|
||||||
|
...cssStyles.tableHeader
|
||||||
|
},
|
||||||
|
rowCssStyles: {
|
||||||
|
'border-top': 'solid 1px #ccc',
|
||||||
|
'border-bottom': 'solid 1px #ccc',
|
||||||
|
'border-left': 'none',
|
||||||
|
'border-right': 'none'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.metricsAndLogsRowsTable = this.modelView.modelBuilder.declarativeTable()
|
||||||
|
.withProperties<azdata.DeclarativeTableProperties>(
|
||||||
|
{
|
||||||
|
columns: metricsAndLogsColumns,
|
||||||
|
data: this.createMetricsAndLogsRows()
|
||||||
|
}).component();
|
||||||
|
this.rootContainer.addItem(this.metricsAndLogsRowsTable, { flex: '0 0 auto' });
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
|
||||||
|
|
||||||
return rootContainer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
private updateResourceStatusModel(bdcStatus?: BdcStatusModel): void {
|
||||||
|
// If we can't find the resource model for this resource then just
|
||||||
|
// default to keeping what we had originally
|
||||||
if (!bdcStatus) {
|
if (!bdcStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const service = bdcStatus.services ? bdcStatus.services.find(s => s.serviceName === this.serviceName) : undefined;
|
const service = bdcStatus.services ? bdcStatus.services.find(s => s.serviceName === this.serviceName) : undefined;
|
||||||
const resource = service ? service.resources.find(r => r.resourceName === this.resourceName) : undefined;
|
this.resourceStatusModel = service ? service.resources.find(r => r.resourceName === this.resourceName) : this.resourceStatusModel;
|
||||||
|
}
|
||||||
|
|
||||||
if (!resource || isNullOrUndefined(resource.instances)) {
|
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
||||||
|
this.updateResourceStatusModel(bdcStatus);
|
||||||
|
|
||||||
|
if (!this.resourceStatusModel || isNullOrUndefined(this.resourceStatusModel.instances)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastUpdatedLabel.value =
|
this.lastUpdatedLabel.value = this.getLastUpdatedText();
|
||||||
localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
|
|
||||||
this.model.bdcStatusLastUpdated ?
|
|
||||||
`${this.model.bdcStatusLastUpdated.toLocaleDateString()} ${this.model.bdcStatusLastUpdated.toLocaleTimeString()}`
|
|
||||||
: '-');
|
|
||||||
|
|
||||||
this.instanceHealthStatusRowsContainer.clearItems();
|
this.instanceHealthStatusTable.data = this.createHealthStatusRows();
|
||||||
this.metricsAndLogsRowsContainer.clearItems();
|
|
||||||
|
|
||||||
resource.instances.forEach(i => {
|
this.metricsAndLogsRowsTable.data = this.createMetricsAndLogsRows();
|
||||||
const instanceHealthStatusRow = createInstanceHealthStatusRow(this.modelView.modelBuilder, i);
|
|
||||||
this.instanceHealthStatusRowsContainer.addItem(instanceHealthStatusRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
|
||||||
|
|
||||||
let metricsAndLogsRow = createMetricsAndLogsRow(this.modelView.modelBuilder, i, this.serviceName);
|
|
||||||
this.metricsAndLogsRowsContainer.addItem(metricsAndLogsRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private createMetricsAndLogsRows(): any[][] {
|
||||||
* Creates a row with the name, state and health status for a particular instance on this resource
|
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createMetricsAndLogsRow(instanceStatus)) : [];
|
||||||
*
|
|
||||||
* @param modelBuilder The builder used to create the component
|
|
||||||
* @param instanceStatus The status object for the instance this row is for
|
|
||||||
*/
|
|
||||||
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<azdata.TextComponentProperties>({
|
|
||||||
value: getHealthStatusIcon(instanceStatus.healthStatus),
|
|
||||||
ariaRole: 'img',
|
|
||||||
title: getHealthStatusDisplayText(instanceStatus.healthStatus),
|
|
||||||
CSSStyles: { 'user-select': 'none' }
|
|
||||||
}).component();
|
|
||||||
instanceHealthStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': `${healthAndStatusIconColumnWidth}px`, 'min-width': `${healthAndStatusIconColumnWidth}px` } });
|
|
||||||
const nameCell = modelBuilder.text().withProperties({ value: instanceStatus.instanceName, CSSStyles: { ...cssStyles.text } }).component();
|
|
||||||
instanceHealthStatusRow.addItem(nameCell, { CSSStyles: { 'width': `${healthAndStatusInstanceNameColumnWidth}px`, 'min-width': `${healthAndStatusInstanceNameColumnWidth}px`, ...cssStyles.text } });
|
|
||||||
const stateText = getStateDisplayText(instanceStatus.state);
|
|
||||||
const stateCell = modelBuilder.text().withProperties({ value: stateText, title: stateText, CSSStyles: { ...cssStyles.overflowEllipsisText } }).component();
|
|
||||||
instanceHealthStatusRow.addItem(stateCell, { CSSStyles: { 'width': `${healthAndStatusStateColumnWidth}px`, 'min-width': `${healthAndStatusStateColumnWidth}px` } });
|
|
||||||
const healthStatusText = getHealthStatusDisplayText(instanceStatus.healthStatus);
|
|
||||||
const healthStatusCell = modelBuilder.text().withProperties({ value: healthStatusText, title: healthStatusText, CSSStyles: { ...cssStyles.overflowEllipsisText } }).component();
|
|
||||||
instanceHealthStatusRow.addItem(healthStatusCell, { CSSStyles: { 'width': `${healthAndStatusHealthColumnWidth}px`, 'min-width': `${healthAndStatusHealthColumnWidth}px` } });
|
|
||||||
|
|
||||||
if (instanceStatus.healthStatus !== 'healthy' && instanceStatus.details && instanceStatus.details.length > 0) {
|
|
||||||
instanceHealthStatusRow.addItem(createViewDetailsButton(modelBuilder, instanceStatus.details), { flex: '0 0 auto' });
|
|
||||||
}
|
}
|
||||||
return instanceHealthStatusRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private createHealthStatusRows(): any[][] {
|
||||||
* Creates a row with the name, link to the metrics and a link to the logs for a particular instance on this resource
|
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createHealthStatusRow(instanceStatus)) : [];
|
||||||
* @param modelBuilder The builder used to create the component
|
}
|
||||||
* @param instanceStatus The status object for the instance this row is for
|
|
||||||
* @param serviceName The name of the service this resource instance belongs to
|
private createMetricsAndLogsRow(instanceStatus: InstanceStatusModel): any[] {
|
||||||
*/
|
const row: any[] = [instanceStatus.instanceName];
|
||||||
function createMetricsAndLogsRow(modelBuilder: azdata.ModelBuilder, instanceStatus: InstanceStatusModel, serviceName: string): azdata.FlexContainer {
|
|
||||||
const metricsAndLogsRow = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component();
|
|
||||||
const nameCell = modelBuilder.text().withProperties({ value: instanceStatus.instanceName, CSSStyles: { ...cssStyles.text } }).component();
|
|
||||||
metricsAndLogsRow.addItem(nameCell, { CSSStyles: { 'width': `${metricsAndLogsInstanceNameColumnWidth}px`, 'min-width': `${metricsAndLogsInstanceNameColumnWidth}px`, ...cssStyles.text } });
|
|
||||||
|
|
||||||
// Not all instances have all logs available - in that case just display N/A instead of a link
|
// Not all instances have all logs available - in that case just display N/A instead of a link
|
||||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.nodeMetricsUrl)) {
|
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.nodeMetricsUrl)) {
|
||||||
const metricsCell = modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component();
|
row.push(this.modelView.modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component());
|
||||||
metricsAndLogsRow.addItem(metricsCell, { CSSStyles: { 'width': `${metricsAndLogsNodeMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsNodeMetricsColumnWidth}px`, ...cssStyles.text } });
|
|
||||||
} else {
|
} else {
|
||||||
const nodeMetricsCell = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
row.push(this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: viewText,
|
label: viewText,
|
||||||
url: instanceStatus.dashboards.nodeMetricsUrl,
|
url: instanceStatus.dashboards.nodeMetricsUrl,
|
||||||
title: instanceStatus.dashboards.nodeMetricsUrl,
|
title: instanceStatus.dashboards.nodeMetricsUrl,
|
||||||
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
||||||
})
|
}).component());
|
||||||
.component();
|
|
||||||
metricsAndLogsRow.addItem(nodeMetricsCell, { CSSStyles: { 'width': `${metricsAndLogsNodeMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsNodeMetricsColumnWidth}px` } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show SQL metrics column for SQL resource instances
|
// Only show SQL metrics column for SQL resource instances
|
||||||
if (serviceName === Service.sql) {
|
if (this.serviceName === Service.sql) {
|
||||||
// Not all instances have all logs available - in that case just display N/A instead of a link
|
// Not all instances have all logs available - in that case just display N/A instead of a link
|
||||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.sqlMetricsUrl)) {
|
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.sqlMetricsUrl)) {
|
||||||
const metricsCell = modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component();
|
row.push(this.modelView.modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component());
|
||||||
metricsAndLogsRow.addItem(metricsCell, { CSSStyles: { 'width': `${metricsAndLogsSqlMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsSqlMetricsColumnWidth}px`, ...cssStyles.text } });
|
|
||||||
} else {
|
} else {
|
||||||
const sqlMetricsCell = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
row.push(this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: viewText,
|
label: viewText,
|
||||||
url: instanceStatus.dashboards.sqlMetricsUrl,
|
url: instanceStatus.dashboards.sqlMetricsUrl,
|
||||||
title: instanceStatus.dashboards.sqlMetricsUrl,
|
title: instanceStatus.dashboards.sqlMetricsUrl,
|
||||||
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
||||||
})
|
}).component());
|
||||||
.component();
|
|
||||||
metricsAndLogsRow.addItem(sqlMetricsCell, { CSSStyles: { 'width': `${metricsAndLogsSqlMetricsColumnWidth}px`, 'min-width': `${metricsAndLogsSqlMetricsColumnWidth}px` } });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.logsUrl)) {
|
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.logsUrl)) {
|
||||||
const logsCell = modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component();
|
row.push(this.modelView.modelBuilder.text().withProperties({ value: notAvailableText, CSSStyles: { ...cssStyles.text } }).component());
|
||||||
metricsAndLogsRow.addItem(logsCell, { CSSStyles: { 'width': `${metricsAndLogsLogsColumnWidth}px`, 'min-width': `${metricsAndLogsLogsColumnWidth}px`, ...cssStyles.text } });
|
|
||||||
} else {
|
} else {
|
||||||
const logsCell = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
row.push(this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: viewText,
|
label: viewText,
|
||||||
url: instanceStatus.dashboards.logsUrl,
|
url: instanceStatus.dashboards.logsUrl,
|
||||||
title: instanceStatus.dashboards.logsUrl,
|
title: instanceStatus.dashboards.logsUrl,
|
||||||
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
|
||||||
})
|
}).component());
|
||||||
.component();
|
}
|
||||||
metricsAndLogsRow.addItem(logsCell, { CSSStyles: { 'width': `${metricsAndLogsLogsColumnWidth}px`, 'min-width': `${metricsAndLogsLogsColumnWidth}px` } });
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
return metricsAndLogsRow;
|
private createHealthStatusRow(instanceStatus: InstanceStatusModel): any[] {
|
||||||
|
const statusIconCell = this.modelView.modelBuilder.text()
|
||||||
|
.withProperties<azdata.TextComponentProperties>({
|
||||||
|
value: getHealthStatusIcon(instanceStatus.healthStatus),
|
||||||
|
ariaRole: 'img',
|
||||||
|
title: getHealthStatusDisplayText(instanceStatus.healthStatus),
|
||||||
|
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const viewDetailsButton = instanceStatus.healthStatus !== 'healthy' && instanceStatus.details && instanceStatus.details.length > 0 ? createViewDetailsButton(this.modelView.modelBuilder, instanceStatus.details) : undefined;
|
||||||
|
return [
|
||||||
|
statusIconCell,
|
||||||
|
instanceStatus.instanceName,
|
||||||
|
getStateDisplayText(instanceStatus.state),
|
||||||
|
getHealthStatusDisplayText(instanceStatus.healthStatus),
|
||||||
|
viewDetailsButton];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLastUpdatedText(): string {
|
||||||
|
return localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
|
||||||
|
this.model.bdcStatusLastUpdated ?
|
||||||
|
`${this.model.bdcStatusLastUpdated.toLocaleDateString()} ${this.model.bdcStatusLastUpdated.toLocaleTimeString()}`
|
||||||
|
: '-');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type ServiceTab = { div: azdata.DivContainer, dot: azdata.TextComponent, text: a
|
|||||||
export class BdcServiceStatusPage extends BdcDashboardPage {
|
export class BdcServiceStatusPage extends BdcDashboardPage {
|
||||||
|
|
||||||
private currentTab: { tab: ServiceTab, index: number };
|
private currentTab: { tab: ServiceTab, index: number };
|
||||||
private currentTabPage: azdata.FlexContainer;
|
private currentTabPage: BdcDashboardResourceStatusPage;
|
||||||
private rootContainer: azdata.FlexContainer;
|
private rootContainer: azdata.FlexContainer;
|
||||||
private resourceHeader: azdata.FlexContainer;
|
private resourceHeader: azdata.FlexContainer;
|
||||||
|
|
||||||
@@ -25,10 +25,13 @@ export class BdcServiceStatusPage extends BdcDashboardPage {
|
|||||||
constructor(private serviceName: string, private model: BdcDashboardModel, private modelView: azdata.ModelView) {
|
constructor(private serviceName: string, private model: BdcDashboardModel, private modelView: azdata.ModelView) {
|
||||||
super();
|
super();
|
||||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||||
this.createPage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get container(): azdata.FlexContainer {
|
public get container(): azdata.FlexContainer {
|
||||||
|
// Lazily create the container only when needed
|
||||||
|
if (!this.rootContainer) {
|
||||||
|
this.createPage();
|
||||||
|
}
|
||||||
return this.rootContainer;
|
return this.rootContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +70,11 @@ export class BdcServiceStatusPage extends BdcDashboardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private changeSelectedTabPage(newPage: azdata.FlexContainer): void {
|
private changeSelectedTabPage(newPage: BdcDashboardResourceStatusPage): void {
|
||||||
if (this.currentTabPage) {
|
if (this.currentTabPage) {
|
||||||
this.rootContainer.removeItem(this.currentTabPage);
|
this.rootContainer.removeItem(this.currentTabPage.container);
|
||||||
}
|
}
|
||||||
this.rootContainer.addItem(newPage);
|
this.rootContainer.addItem(newPage.container);
|
||||||
this.currentTabPage = newPage;
|
this.currentTabPage = newPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +93,7 @@ export class BdcServiceStatusPage extends BdcDashboardPage {
|
|||||||
const currentIndex = tabIndex++;
|
const currentIndex = tabIndex++;
|
||||||
const resourceHeaderTab = createResourceHeaderTab(this.modelView.modelBuilder, resource);
|
const resourceHeaderTab = createResourceHeaderTab(this.modelView.modelBuilder, resource);
|
||||||
this.createdTabs.set(resource.resourceName, resourceHeaderTab);
|
this.createdTabs.set(resource.resourceName, resourceHeaderTab);
|
||||||
const resourceStatusPage: azdata.FlexContainer = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName).container;
|
const resourceStatusPage = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName);
|
||||||
resourceHeaderTab.div.onDidClick(() => {
|
resourceHeaderTab.div.onDidClick(() => {
|
||||||
// Don't need to do anything if this is already the currently selected tab
|
// Don't need to do anything if this is already the currently selected tab
|
||||||
if (this.currentTab.index === currentIndex) {
|
if (this.currentTab.index === currentIndex) {
|
||||||
|
|||||||
2
src/sql/azdata.d.ts
vendored
2
src/sql/azdata.d.ts
vendored
@@ -3171,10 +3171,10 @@ declare module 'azdata' {
|
|||||||
|
|
||||||
export interface DeclarativeTableColumn {
|
export interface DeclarativeTableColumn {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
categoryValues: CategoryValue[];
|
|
||||||
valueType: DeclarativeDataType;
|
valueType: DeclarativeDataType;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
width: number | string;
|
width: number | string;
|
||||||
|
categoryValues?: CategoryValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeclarativeTableProperties {
|
export interface DeclarativeTableProperties {
|
||||||
|
|||||||
9
src/sql/azdata.proposed.d.ts
vendored
9
src/sql/azdata.proposed.d.ts
vendored
@@ -87,6 +87,15 @@ declare module 'azdata' {
|
|||||||
onDidClick: vscode.Event<any>;
|
onDidClick: vscode.Event<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeclarativeTableColumn {
|
||||||
|
headerCssStyles?: { [key: string]: string };
|
||||||
|
rowCssStyles?: { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DeclarativeDataType {
|
||||||
|
component = 'component'
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add optional azureAccount for connectionWidget.
|
* Add optional azureAccount for connectionWidget.
|
||||||
*/
|
*/
|
||||||
|
|||||||
5
src/sql/sqlops.proposed.d.ts
vendored
5
src/sql/sqlops.proposed.d.ts
vendored
@@ -587,7 +587,8 @@ declare module 'sqlops' {
|
|||||||
string = 'string',
|
string = 'string',
|
||||||
category = 'category',
|
category = 'category',
|
||||||
boolean = 'boolean',
|
boolean = 'boolean',
|
||||||
editableCategory = 'editableCategory'
|
editableCategory = 'editableCategory',
|
||||||
|
component = 'component'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RadioButtonProperties {
|
export interface RadioButtonProperties {
|
||||||
@@ -625,10 +626,10 @@ declare module 'sqlops' {
|
|||||||
|
|
||||||
export interface DeclarativeTableColumn {
|
export interface DeclarativeTableColumn {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
categoryValues: CategoryValue[];
|
|
||||||
valueType: DeclarativeDataType;
|
valueType: DeclarativeDataType;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
width: number | string;
|
width: number | string;
|
||||||
|
categoryValues?: CategoryValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeclarativeTableProperties {
|
export interface DeclarativeTableProperties {
|
||||||
|
|||||||
@@ -1368,6 +1368,46 @@ class DeclarativeTableWrapper extends ComponentWrapper implements azdata.Declara
|
|||||||
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
||||||
return emitter && emitter.event;
|
return emitter && emitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected notifyPropertyChanged(): Thenable<void> {
|
||||||
|
return this._proxy.$setProperties(this._handle, this._id, this.getPropertiesForMainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public toComponentShape(): IComponentShape {
|
||||||
|
// Overridden to ensure we send the correct properties mapping.
|
||||||
|
return <IComponentShape>{
|
||||||
|
id: this.id,
|
||||||
|
type: this.type,
|
||||||
|
layout: this.layout,
|
||||||
|
properties: this.getPropertiesForMainThread(),
|
||||||
|
itemConfigs: this.itemConfigs ? this.itemConfigs.map<IItemConfig>(item => item.toIItemConfig()) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the properties map to send to the main thread.
|
||||||
|
*/
|
||||||
|
private getPropertiesForMainThread(): { [key: string]: string } {
|
||||||
|
// This is necessary because we can't send the actual ComponentWrapper objects
|
||||||
|
// and so map them into their IDs instead. We don't want to update the actual
|
||||||
|
// data property though since the caller would still expect that to contain
|
||||||
|
// the Component objects they created
|
||||||
|
const properties = assign({}, this.properties);
|
||||||
|
if (properties.data) {
|
||||||
|
properties.data = properties.data.map((row: any[]) => row.map(cell => {
|
||||||
|
if (cell instanceof ComponentWrapper) {
|
||||||
|
// First ensure that we register the component using addItem
|
||||||
|
// such that it gets added to the ModelStore. We don't want to
|
||||||
|
// make the table component an actual container since that exposes
|
||||||
|
// a lot of functionality we don't need.
|
||||||
|
this.addItem(cell);
|
||||||
|
return cell.id;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListBoxWrapper extends ComponentWrapper implements azdata.ListBoxComponent {
|
class ListBoxWrapper extends ComponentWrapper implements azdata.ListBoxComponent {
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ export enum DeclarativeDataType {
|
|||||||
string = 'string',
|
string = 'string',
|
||||||
category = 'category',
|
category = 'category',
|
||||||
boolean = 'boolean',
|
boolean = 'boolean',
|
||||||
editableCategory = 'editableCategory'
|
editableCategory = 'editableCategory',
|
||||||
|
component = 'component'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CardType {
|
export enum CardType {
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export enum DeclarativeDataType {
|
|||||||
string = 'string',
|
string = 'string',
|
||||||
category = 'category',
|
category = 'category',
|
||||||
boolean = 'boolean',
|
boolean = 'boolean',
|
||||||
editableCategory = 'editableCategory'
|
editableCategory = 'editableCategory',
|
||||||
|
component = 'component'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -29,20 +30,21 @@ export enum DeclarativeDataType {
|
|||||||
template: `
|
template: `
|
||||||
<table role=grid #container *ngIf="columns" class="declarative-table" [style.height]="getHeight()" [attr.aria-label]="ariaLabel">
|
<table role=grid #container *ngIf="columns" class="declarative-table" [style.height]="getHeight()" [attr.aria-label]="ariaLabel">
|
||||||
<thead>
|
<thead>
|
||||||
<ng-container *ngFor="let column of columns;let h = index">
|
<ng-container *ngFor="let column of columns;">
|
||||||
<th class="declarative-table-header" tabindex="-1" aria-sort="none">{{column.displayName}}</th>
|
<th class="declarative-table-header" tabindex="-1" aria-sort="none" [ngStyle]="column.headerCssStyles">{{column.displayName}}</th>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</thead>
|
</thead>
|
||||||
<ng-container *ngIf="data">
|
<ng-container *ngIf="data">
|
||||||
<ng-container *ngFor="let row of data;let r = index">
|
<ng-container *ngFor="let row of data;let r = index">
|
||||||
<tr class="declarative-table-row">
|
<tr class="declarative-table-row">
|
||||||
<ng-container *ngFor="let cellData of row;let c = index">
|
<ng-container *ngFor="let cellData of row;let c = index">
|
||||||
<td class="declarative-table-cell" tabindex="-1" [style.width]="getColumnWidth(c)" [attr.aria-label]="getAriaLabel(r, c)" >
|
<td class="declarative-table-cell" tabindex="-1" [style.width]="getColumnWidth(c)" [attr.aria-label]="getAriaLabel(r, c)" [ngStyle]="columns[c].rowCssStyles">
|
||||||
<checkbox *ngIf="isCheckBox(c)" label="" (onChange)="onCheckBoxChanged($event,r,c)" [enabled]="isControlEnabled(c)" [checked]="isChecked(r,c)"></checkbox>
|
<checkbox *ngIf="isCheckBox(c)" label="" (onChange)="onCheckBoxChanged($event,r,c)" [enabled]="isControlEnabled(c)" [checked]="isChecked(r,c)"></checkbox>
|
||||||
<select-box *ngIf="isSelectBox(c)" [options]="getOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="getSelectedOptionDisplayName(r,c)"></select-box>
|
<select-box *ngIf="isSelectBox(c)" [options]="getOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="getSelectedOptionDisplayName(r,c)"></select-box>
|
||||||
<editable-select-box *ngIf="isEditableSelectBox(c)" [options]="getOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="getSelectedOptionDisplayName(r,c)"></editable-select-box>
|
<editable-select-box *ngIf="isEditableSelectBox(c)" [options]="getOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="getSelectedOptionDisplayName(r,c)"></editable-select-box>
|
||||||
<input-box *ngIf="isInputBox(c)" [value]="cellData" (onDidChange)="onInputBoxChanged($event,r,c)"></input-box>
|
<input-box *ngIf="isInputBox(c)" [value]="cellData" (onDidChange)="onInputBoxChanged($event,r,c)"></input-box>
|
||||||
<ng-container *ngIf="isLabel(c)" >{{cellData}}</ng-container>
|
<ng-container *ngIf="isLabel(c)" >{{cellData}}</ng-container>
|
||||||
|
<model-component-wrapper *ngIf="isComponent(c)" [descriptor]="getItemDescriptor(cellData)" [modelStore]="modelStore"></model-component-wrapper>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -79,57 +81,57 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
|||||||
this.baseDestroy();
|
this.baseDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isCheckBox(cell: number): boolean {
|
public isCheckBox(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.valueType === DeclarativeDataType.boolean;
|
return column.valueType === DeclarativeDataType.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isControlEnabled(cell: number): boolean {
|
public isControlEnabled(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return !column.isReadOnly;
|
return !column.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isLabel(cell: number): boolean {
|
private isLabel(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.isReadOnly && column.valueType === DeclarativeDataType.string;
|
return column.isReadOnly && column.valueType === DeclarativeDataType.string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isChecked(row: number, cell: number): boolean {
|
public isChecked(rowIdx: number, colIdx: number): boolean {
|
||||||
let cellData = this.data[row][cell];
|
let cellData = this.data[rowIdx][colIdx];
|
||||||
return cellData;
|
return cellData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onInputBoxChanged(e: string, row: number, cell: number): void {
|
public onInputBoxChanged(e: string, rowIdx: number, colIdx: number): void {
|
||||||
this.onCellDataChanged(e, row, cell);
|
this.onCellDataChanged(e, rowIdx, colIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCheckBoxChanged(e: boolean, row: number, cell: number): void {
|
public onCheckBoxChanged(e: boolean, rowIdx: number, colIdx: number): void {
|
||||||
this.onCellDataChanged(e, row, cell);
|
this.onCellDataChanged(e, rowIdx, colIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSelectBoxChanged(e: ISelectData | string, row: number, cell: number): void {
|
public onSelectBoxChanged(e: ISelectData | string, rowIdx: number, colIdx: number): void {
|
||||||
|
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
if (column.categoryValues) {
|
if (column.categoryValues) {
|
||||||
if (typeof e === 'string') {
|
if (typeof e === 'string') {
|
||||||
let category = find(column.categoryValues, c => c.displayName === e);
|
let category = find(column.categoryValues, c => c.displayName === e);
|
||||||
if (category) {
|
if (category) {
|
||||||
this.onCellDataChanged(category.name, row, cell);
|
this.onCellDataChanged(category.name, rowIdx, colIdx);
|
||||||
} else {
|
} else {
|
||||||
this.onCellDataChanged(e, row, cell);
|
this.onCellDataChanged(e, rowIdx, colIdx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.onCellDataChanged(column.categoryValues[e.index].name, row, cell);
|
this.onCellDataChanged(column.categoryValues[e.index].name, rowIdx, colIdx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCellDataChanged(newValue: any, row: number, cell: number): void {
|
private onCellDataChanged(newValue: any, rowIdx: number, colIdx: number): void {
|
||||||
this.data[row][cell] = newValue;
|
this.data[rowIdx][colIdx] = newValue;
|
||||||
this.data = this.data;
|
this.data = this.data;
|
||||||
let newCellData: azdata.TableCell = {
|
let newCellData: azdata.TableCell = {
|
||||||
row: row,
|
row: rowIdx,
|
||||||
column: cell,
|
column: colIdx,
|
||||||
value: newValue
|
value: newValue
|
||||||
};
|
};
|
||||||
this.fireEvent({
|
this.fireEvent({
|
||||||
@@ -138,39 +140,43 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public isSelectBox(cell: number): boolean {
|
public isSelectBox(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.valueType === DeclarativeDataType.category;
|
return column.valueType === DeclarativeDataType.category;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEditableSelectBox(cell: number): boolean {
|
private isEditableSelectBox(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.valueType === DeclarativeDataType.editableCategory;
|
return column.valueType === DeclarativeDataType.editableCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isInputBox(cell: number): boolean {
|
public isInputBox(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.valueType === DeclarativeDataType.string && !column.isReadOnly;
|
return column.valueType === DeclarativeDataType.string && !column.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getColumnWidth(cell: number): string {
|
public isComponent(colIdx: number): boolean {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
return this.columns[colIdx].valueType === DeclarativeDataType.component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getColumnWidth(colIdx: number): string {
|
||||||
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return this.convertSize(column.width, '30px');
|
return this.convertSize(column.width, '30px');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOptions(cell: number): string[] {
|
public getOptions(colIdx: number): string[] {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
return column.categoryValues ? column.categoryValues.map(x => x.displayName) : [];
|
return column.categoryValues ? column.categoryValues.map(x => x.displayName) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSelectedOptionDisplayName(row: number, cell: number): string {
|
public getSelectedOptionDisplayName(rowIdx: number, colIdx: number): string {
|
||||||
let column: azdata.DeclarativeTableColumn = this.columns[cell];
|
let column: azdata.DeclarativeTableColumn = this.columns[colIdx];
|
||||||
let cellData = this.data[row][cell];
|
let cellData = this.data[rowIdx][colIdx];
|
||||||
if (cellData && column.categoryValues) {
|
if (cellData && column.categoryValues) {
|
||||||
let category = find(column.categoryValues, v => v.name === cellData);
|
let category = find(column.categoryValues, v => v.name === cellData);
|
||||||
if (category) {
|
if (category) {
|
||||||
return category.displayName;
|
return category.displayName;
|
||||||
} else if (this.isEditableSelectBox(cell)) {
|
} else if (this.isEditableSelectBox(colIdx)) {
|
||||||
return cellData;
|
return cellData;
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -180,9 +186,13 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAriaLabel(row: number, column: number): string {
|
public getAriaLabel(rowIdx: number, colIdx: number): string {
|
||||||
const cellData = this.data[row][column];
|
const cellData = this.data[rowIdx][colIdx];
|
||||||
return this.isLabel(column) ? (cellData && cellData !== '' ? cellData : localize('blankValue', "blank")) : '';
|
return this.isLabel(colIdx) ? (cellData && cellData !== '' ? cellData : localize('blankValue', "blank")) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItemDescriptor(componentId: string): IComponentDescriptor {
|
||||||
|
return this.modelStore.getComponentDescriptor(componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// IComponent implementation
|
/// IComponent implementation
|
||||||
@@ -211,4 +221,18 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
|||||||
public set columns(newValue: azdata.DeclarativeTableColumn[]) {
|
public set columns(newValue: azdata.DeclarativeTableColumn[]) {
|
||||||
this.setPropertyFromUI<azdata.DeclarativeTableProperties, azdata.DeclarativeTableColumn[]>((props, value) => props.columns = value, newValue);
|
this.setPropertyFromUI<azdata.DeclarativeTableProperties, azdata.DeclarativeTableColumn[]>((props, value) => props.columns = value, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IComponent container-related implementation
|
||||||
|
// This is needed for the component column type - in order to have the components in the cells registered we call addItem
|
||||||
|
// on the extension side to create and register the component with the ModelStore. That requires that these methods be implemented
|
||||||
|
// though which isn't done by default for non-Container components and so we just stub out the implementation here (we already have
|
||||||
|
// the component IDs in the data property so there's no need to store them here as well)
|
||||||
|
public addToContainer(componentDescriptor: IComponentDescriptor, config: any, index?: number): void {
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearContainer(): void {
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,26 +11,30 @@
|
|||||||
|
|
||||||
.declarative-table-header {
|
.declarative-table-header {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid gray;
|
border-left: 1px solid gray;
|
||||||
|
border-top: 1px solid gray;
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
background-color: #F5F5F5;
|
background-color: #F5F5F5;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .declarative-table-header {
|
.vs-dark .declarative-table-header
|
||||||
padding: 5px;
|
|
||||||
border: 1px solid gray;
|
|
||||||
background-color: #333334;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hc-black .declarative-table-header {
|
.hc-black .declarative-table-header {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid gray;
|
border-left: 1px solid gray;
|
||||||
|
border-top: 1px solid gray;
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
background-color: #333334;
|
background-color: #333334;
|
||||||
}
|
}
|
||||||
|
|
||||||
.declarative-table-cell {
|
.declarative-table-cell {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid gray;
|
border-left: 1px solid gray;
|
||||||
|
border-top: 1px solid gray;
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.declarative-table [role="gridcell"]:focus,
|
.declarative-table [role="gridcell"]:focus,
|
||||||
|
|||||||
Reference in New Issue
Block a user