Arc Postgres resource health page (#11065)

This commit is contained in:
Brian Bergeron
2020-06-23 17:31:16 -07:00
committed by GitHub
parent 64dc9b365f
commit c4a85bbd78
12 changed files with 408 additions and 23 deletions

View File

@@ -12,11 +12,20 @@ import { DashboardPage } from '../../components/dashboardPage';
import { PostgresModel } from '../../../models/postgresModel';
export class PostgresConnectionStringsPage extends DashboardPage {
private disposables: vscode.Disposable[] = [];
private keyValueContainer?: KeyValueContainer;
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
super(modelView);
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
modelView.onClosed(() =>
this.disposables.forEach(d => {
try { d.dispose(); }
catch { }
}));
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
}
protected get title(): string {

View File

@@ -14,6 +14,7 @@ import { PostgresPropertiesPage } from './postgresPropertiesPage';
import { Dashboard } from '../../components/dashboard';
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
import { PostgresResourceHealthPage } from './postgresResourceHealthPage';
export class PostgresDashboard extends Dashboard {
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
@@ -24,6 +25,7 @@ export class PostgresDashboard extends Dashboard {
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
const resourceHealthPage = new PostgresResourceHealthPage(modelView, this._postgresModel);
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);
@@ -39,6 +41,7 @@ export class PostgresDashboard extends Dashboard {
{
title: loc.supportAndTroubleshooting,
tabs: [
resourceHealthPage.tab,
diagnoseAndSolveProblemsPage.tab,
supportRequestPage.tab
]

View File

@@ -14,6 +14,8 @@ import { PostgresModel, PodRole } from '../../../models/postgresModel';
import { promptForResourceDeletion, promptAndConfirmPassword } from '../../../common/utils';
export class PostgresOverviewPage extends DashboardPage {
private disposables: vscode.Disposable[] = [];
private propertiesLoading?: azdata.LoadingComponent;
private kibanaLoading?: azdata.LoadingComponent;
private grafanaLoading?: azdata.LoadingComponent;
@@ -26,18 +28,30 @@ export class PostgresOverviewPage extends DashboardPage {
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
super(modelView);
this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshEndpoints()));
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshProperties()));
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
}));
modelView.onClosed(() =>
this.disposables.forEach(d => {
try { d.dispose(); }
catch { }
}));
this._postgresModel.onPodsUpdated(() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
}));
this.disposables.push(this._controllerModel.onEndpointsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refreshEndpoints())));
this.disposables.push(this._controllerModel.onRegistrationsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refreshProperties())));
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
})));
this.disposables.push(this._postgresModel.onPodsUpdated(
() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
})));
}
protected get title(): string {
@@ -207,7 +221,7 @@ export class PostgresOverviewPage extends DashboardPage {
vscode.window.showInformationMessage(loc.passwordReset);
}
} catch (error) {
vscode.window.showErrorMessage(loc.passwordResetFailed);
vscode.window.showErrorMessage(loc.passwordResetFailed(error));
} finally {
resetPasswordButton.enabled = true;
}

View File

@@ -13,12 +13,23 @@ import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel';
export class PostgresPropertiesPage extends DashboardPage {
private disposables: vscode.Disposable[] = [];
private keyValueContainer?: KeyValueContainer;
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
super(modelView);
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
modelView.onClosed(() =>
this.disposables.forEach(d => {
try { d.dispose(); }
catch { }
}));
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
this.disposables.push(this._controllerModel.onRegistrationsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
}
protected get title(): string {

View File

@@ -0,0 +1,192 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
import { PostgresModel } from '../../../models/postgresModel';
import { fromNow } from '../../../common/date';
export class PostgresResourceHealthPage extends DashboardPage {
private disposables: vscode.Disposable[] = [];
private interval: NodeJS.Timeout;
private podsUpdated?: azdata.TextComponent;
private podsTable?: azdata.DeclarativeTableComponent;
private conditionsTable?: azdata.DeclarativeTableComponent;
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
super(modelView);
modelView.onClosed(() => {
try { clearInterval(this.interval); }
catch { }
this.disposables.forEach(d => {
try { d.dispose(); }
catch { }
});
});
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
// Keep the last updated timestamps up to date with the current time
this.interval = setInterval(() => this.refresh(), 60 * 1000);
}
protected get title(): string {
return loc.resourceHealth;
}
protected get id(): string {
return 'postgres-resource-health';
}
protected get icon(): { dark: string; light: string; } {
return IconPathHelper.health;
}
protected get container(): azdata.Component {
const root = this.modelView.modelBuilder.divContainer().component();
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: loc.resourceHealth,
CSSStyles: { ...cssStyles.title, 'margin-bottom': '30px' }
}).component());
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: loc.podOverview,
CSSStyles: titleCSS
}).component());
this.podsUpdated = this.modelView.modelBuilder.text().component();
content.addItem(this.podsUpdated);
// Pod overview
this.podsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
columns: [
{
displayName: '',
valueType: azdata.DeclarativeDataType.string,
width: '50%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: { ...cssStyles.tableRow, 'font-size': '20px', 'font-weight': 'bold' }
},
{
displayName: '',
valueType: azdata.DeclarativeDataType.string,
width: '50%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: []
}).component();
content.addItem(this.podsTable, { CSSStyles: { 'margin-bottom': '30px' } });
// Conditions table
this.conditionsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%',
columns: [
{
displayName: loc.condition,
valueType: azdata.DeclarativeDataType.string,
width: '15%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: '',
valueType: azdata.DeclarativeDataType.component,
width: '1%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.details,
valueType: azdata.DeclarativeDataType.string,
width: '64%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.lastUpdated,
valueType: azdata.DeclarativeDataType.string,
width: '20%',
isReadOnly: true,
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: { ...cssStyles.tableRow, 'white-space': 'nowrap' }
}
],
data: []
}).component();
content.addItem(this.conditionsTable);
this.initialized = true;
return root;
}
protected get toolbarContainer(): azdata.ToolbarContainer {
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: loc.refresh,
iconPath: IconPathHelper.refresh
}).component();
refreshButton.onDidClick(async () => {
refreshButton.enabled = false;
try {
await this._postgresModel.refresh();
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));
} finally {
refreshButton.enabled = true;
}
});
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
{ component: refreshButton }
]).component();
}
private refresh() {
this.podsUpdated!.value = loc.updated(fromNow(this._postgresModel.serviceLastUpdated!, true));
this.podsTable!.data = [
[this._postgresModel.service?.status?.podsRunning, loc.running],
[this._postgresModel.service?.status?.podsPending, loc.pending],
[this._postgresModel.service?.status?.podsFailed, loc.failed],
[this._postgresModel.service?.status?.podsUnknown, loc.unknown]
];
this.conditionsTable!.data = this._postgresModel.service?.status?.conditions?.map(c => {
const healthy = c.type === 'Ready' ? c.status === 'True' : c.status === 'False';
const image = this.modelView.modelBuilder.image().withProperties<azdata.ImageComponentProperties>({
iconPath: healthy ? IconPathHelper.success : IconPathHelper.fail,
iconHeight: '20px',
iconWidth: '20px',
width: '20px',
height: '20px'
}).component();
return [
c.type,
image,
c.message,
fromNow(c.lastTransitionTime!, true)
];
}) ?? [];
}
}