mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 01:25:39 -05:00
* Transition to withProps in arc * Transition to withProps inputbox * Transition to withProps in checkbox * Transition to withProps text * Transition to withProps in declarative table * Transition to withProps hyperlink * Transition to withProps in button * Transition to withProps radiobutton * Transition to withProps in input * Transition to withProps button * Transition to withProps in text * Transition to withProps image * Transition to withProps declare table * Transition to withProps in table * Transition to withProps radio button * Transition to withProps in image * Transition to withProps radio button * Transition to withProps in commit * Transition to withProps div cont * Transition to withProps in comp * Transition to withProps radio card * Transition to withProps in comp icon * Transition to withProps card * Transition to withProps list
359 lines
12 KiB
TypeScript
359 lines
12 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as azdata from 'azdata';
|
|
import { BdcDashboardModel } from './bdcDashboardModel';
|
|
import { BdcStatusModel, InstanceStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
|
import { getHealthStatusDisplayText, getHealthStatusIcon, getStateDisplayText, Service } from '../utils';
|
|
import { cssStyles } from '../constants';
|
|
import { isNullOrUndefined } from 'util';
|
|
import { createViewDetailsButton } from './commonControls';
|
|
import { BdcDashboardPage } from './bdcDashboardPage';
|
|
import * as loc from '../localizedConstants';
|
|
|
|
export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
|
|
|
private resourceStatusModel: ResourceStatusModel;
|
|
private rootContainer: azdata.FlexContainer;
|
|
private instanceHealthStatusTable: azdata.DeclarativeTableComponent;
|
|
private metricsAndLogsRowsTable: azdata.DeclarativeTableComponent;
|
|
private lastUpdatedLabel: azdata.TextComponent;
|
|
|
|
constructor(model: BdcDashboardModel, modelView: azdata.ModelView, serviceName: string, private resourceName: string) {
|
|
super(model, modelView, serviceName);
|
|
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private createContainer(): void {
|
|
this.rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
|
{
|
|
flexFlow: 'column',
|
|
width: '100%',
|
|
height: '100%'
|
|
}).component();
|
|
|
|
// ##############################
|
|
// # INSTANCE HEALTH AND STATUS #
|
|
// ##############################
|
|
|
|
const healthStatusHeaderContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
|
this.rootContainer.addItem(healthStatusHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
|
|
|
// Header label
|
|
const healthStatusHeaderLabel = this.modelView.modelBuilder.text()
|
|
.withProps({
|
|
value: loc.healthStatusDetails,
|
|
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' }
|
|
})
|
|
.component();
|
|
|
|
healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { ...cssStyles.title } });
|
|
|
|
// Last updated label
|
|
this.lastUpdatedLabel = this.modelView.modelBuilder.text()
|
|
.withProps({
|
|
value: loc.lastUpdated(this.model.bdcStatusLastUpdated),
|
|
CSSStyles: { ...cssStyles.lastUpdatedText }
|
|
}).component();
|
|
|
|
healthStatusHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
|
|
|
this.instanceHealthStatusTable = this.modelView.modelBuilder.declarativeTable()
|
|
.withProps(
|
|
{
|
|
columns: [
|
|
{ // status icon
|
|
displayName: '',
|
|
ariaLabel: loc.statusIcon,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: 25,
|
|
headerCssStyles: {
|
|
'border': 'none'
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
},
|
|
},
|
|
{ // instance
|
|
displayName: loc.instance,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: 100,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
...cssStyles.tableHeader
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
},
|
|
},
|
|
{ // state
|
|
displayName: loc.state,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: 150,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
...cssStyles.tableHeader
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
},
|
|
},
|
|
{ // health status
|
|
displayName: loc.healthStatus,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: 100,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
'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: '',
|
|
ariaLabel: loc.viewErrorDetails,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: 150,
|
|
headerCssStyles: {
|
|
'border': 'none'
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
},
|
|
},
|
|
],
|
|
data: this.createHealthStatusRows(),
|
|
ariaLabel: loc.healthStatusDetails
|
|
}).component();
|
|
this.rootContainer.addItem(this.instanceHealthStatusTable, { flex: '0 0 auto' });
|
|
|
|
// ####################
|
|
// # METRICS AND LOGS #
|
|
// ####################
|
|
|
|
// Title label
|
|
const endpointsLabel = this.modelView.modelBuilder.text()
|
|
.withProps({ value: loc.metricsAndLogs, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
|
.component();
|
|
this.rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
|
|
|
let metricsAndLogsColumns: azdata.DeclarativeTableColumn[] =
|
|
[
|
|
{ // instance
|
|
displayName: loc.instance,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: 125,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
...cssStyles.tableHeader
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
}
|
|
},
|
|
{ // node metrics
|
|
displayName: loc.nodeMetrics,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: 100,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
...cssStyles.tableHeader
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
}
|
|
}
|
|
];
|
|
|
|
// Only show SQL metrics column for SQL resource instances
|
|
if (this.serviceName.toLowerCase() === Service.sql) {
|
|
metricsAndLogsColumns.push(
|
|
{ // sql metrics
|
|
displayName: loc.sqlMetrics,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: 100,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
'text-align': 'left',
|
|
...cssStyles.tableHeader
|
|
},
|
|
rowCssStyles: {
|
|
'border-top': 'solid 1px #ccc',
|
|
'border-bottom': 'solid 1px #ccc',
|
|
'border-left': 'none',
|
|
'border-right': 'none'
|
|
}
|
|
});
|
|
}
|
|
|
|
metricsAndLogsColumns.push(
|
|
{ // logs
|
|
displayName: loc.logs,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: 100,
|
|
headerCssStyles: {
|
|
'border': 'none',
|
|
'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()
|
|
.withProps(
|
|
{
|
|
columns: metricsAndLogsColumns,
|
|
data: this.createMetricsAndLogsRows(),
|
|
ariaLabel: loc.metricsAndLogs
|
|
}).component();
|
|
this.rootContainer.addItem(this.metricsAndLogsRowsTable, { flex: '0 0 auto' });
|
|
this.initialized = true;
|
|
}
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
const service = bdcStatus.services ? bdcStatus.services.find(s => s.serviceName === this.serviceName) : undefined;
|
|
this.resourceStatusModel = service ? service.resources.find(r => r.resourceName === this.resourceName) : this.resourceStatusModel;
|
|
}
|
|
|
|
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
|
this.updateResourceStatusModel(bdcStatus);
|
|
|
|
if (!this.resourceStatusModel || isNullOrUndefined(this.resourceStatusModel.instances)) {
|
|
return;
|
|
}
|
|
|
|
this.lastUpdatedLabel.value = loc.lastUpdated(this.model.bdcStatusLastUpdated);
|
|
|
|
this.instanceHealthStatusTable.data = this.createHealthStatusRows();
|
|
|
|
this.metricsAndLogsRowsTable.data = this.createMetricsAndLogsRows();
|
|
}
|
|
|
|
private createMetricsAndLogsRows(): any[][] {
|
|
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createMetricsAndLogsRow(instanceStatus)) : [];
|
|
}
|
|
|
|
private createHealthStatusRows(): any[][] {
|
|
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createHealthStatusRow(instanceStatus)) : [];
|
|
}
|
|
|
|
private createMetricsAndLogsRow(instanceStatus: InstanceStatusModel): any[] {
|
|
const row: any[] = [instanceStatus.instanceName];
|
|
|
|
// 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)) {
|
|
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
|
} else {
|
|
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
|
label: loc.view,
|
|
url: instanceStatus.dashboards.nodeMetricsUrl,
|
|
title: instanceStatus.dashboards.nodeMetricsUrl,
|
|
ariaLabel: loc.viewNodeMetrics(instanceStatus.dashboards.nodeMetricsUrl),
|
|
CSSStyles: { ...cssStyles.text }
|
|
}).component());
|
|
}
|
|
|
|
// Only show SQL metrics column for SQL resource instances
|
|
if (this.serviceName === Service.sql) {
|
|
// 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)) {
|
|
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
|
} else {
|
|
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
|
label: loc.view,
|
|
url: instanceStatus.dashboards.sqlMetricsUrl,
|
|
title: instanceStatus.dashboards.sqlMetricsUrl,
|
|
ariaLabel: loc.viewSqlMetrics(instanceStatus.dashboards.sqlMetricsUrl),
|
|
CSSStyles: { ...cssStyles.text }
|
|
}).component());
|
|
}
|
|
}
|
|
|
|
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.logsUrl)) {
|
|
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
|
} else {
|
|
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
|
label: loc.view,
|
|
url: instanceStatus.dashboards.logsUrl,
|
|
title: instanceStatus.dashboards.logsUrl,
|
|
ariaLabel: loc.viewLogs(instanceStatus.dashboards.logsUrl),
|
|
CSSStyles: { ...cssStyles.text }
|
|
}).component());
|
|
}
|
|
return row;
|
|
}
|
|
|
|
private createHealthStatusRow(instanceStatus: InstanceStatusModel): any[] {
|
|
const statusIconCell = this.modelView.modelBuilder.text()
|
|
.withProps({
|
|
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];
|
|
}
|
|
}
|