diff --git a/extensions/arc/images/fail.svg b/extensions/arc/images/fail.svg
new file mode 100644
index 0000000000..1a87f5e8da
--- /dev/null
+++ b/extensions/arc/images/fail.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/extensions/arc/images/health.svg b/extensions/arc/images/health.svg
new file mode 100644
index 0000000000..b0361922cb
--- /dev/null
+++ b/extensions/arc/images/health.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/arc/images/heart.svg b/extensions/arc/images/heart.svg
deleted file mode 100644
index 3c60b8d751..0000000000
--- a/extensions/arc/images/heart.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/extensions/arc/images/success.svg b/extensions/arc/images/success.svg
new file mode 100644
index 0000000000..0a3a05baf7
--- /dev/null
+++ b/extensions/arc/images/success.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/extensions/arc/src/common/date.ts b/extensions/arc/src/common/date.ts
new file mode 100644
index 0000000000..45f2cee696
--- /dev/null
+++ b/extensions/arc/src/common/date.ts
@@ -0,0 +1,121 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+// This file was copied from src/vs/base/common/date.ts
+const minute = 60;
+const hour = minute * 60;
+const day = hour * 24;
+const week = day * 7;
+const month = day * 30;
+const year = day * 365;
+
+export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
+ if (typeof date !== 'number') {
+ date = date.getTime();
+ }
+
+ const seconds = Math.round((new Date().getTime() - date) / 1000);
+ if (seconds < -30) {
+ return localize('date.fromNow.in', 'in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
+ }
+
+ if (seconds < 30) {
+ return localize('date.fromNow.now', 'now');
+ }
+
+ let value: number;
+ if (seconds < minute) {
+ value = seconds;
+
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value)
+ : localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.seconds.singular', '{0} sec', value)
+ : localize('date.fromNow.seconds.plural', '{0} secs', value);
+ }
+ }
+
+ if (seconds < hour) {
+ value = Math.floor(seconds / minute);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.minutes.singular.ago', '{0} min ago', value)
+ : localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.minutes.singular', '{0} min', value)
+ : localize('date.fromNow.minutes.plural', '{0} mins', value);
+ }
+ }
+
+ if (seconds < day) {
+ value = Math.floor(seconds / hour);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.hours.singular.ago', '{0} hr ago', value)
+ : localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.hours.singular', '{0} hr', value)
+ : localize('date.fromNow.hours.plural', '{0} hrs', value);
+ }
+ }
+
+ if (seconds < week) {
+ value = Math.floor(seconds / day);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.days.singular.ago', '{0} day ago', value)
+ : localize('date.fromNow.days.plural.ago', '{0} days ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.days.singular', '{0} day', value)
+ : localize('date.fromNow.days.plural', '{0} days', value);
+ }
+ }
+
+ if (seconds < month) {
+ value = Math.floor(seconds / week);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value)
+ : localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.weeks.singular', '{0} wk', value)
+ : localize('date.fromNow.weeks.plural', '{0} wks', value);
+ }
+ }
+
+ if (seconds < year) {
+ value = Math.floor(seconds / month);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.months.singular.ago', '{0} mo ago', value)
+ : localize('date.fromNow.months.plural.ago', '{0} mos ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.months.singular', '{0} mo', value)
+ : localize('date.fromNow.months.plural', '{0} mos', value);
+ }
+ }
+
+ value = Math.floor(seconds / year);
+ if (appendAgoLabel) {
+ return value === 1
+ ? localize('date.fromNow.years.singular.ago', '{0} yr ago', value)
+ : localize('date.fromNow.years.plural.ago', '{0} yrs ago', value);
+ } else {
+ return value === 1
+ ? localize('date.fromNow.years.singular', '{0} yr', value)
+ : localize('date.fromNow.years.plural', '{0} yrs', value);
+ }
+}
diff --git a/extensions/arc/src/constants.ts b/extensions/arc/src/constants.ts
index d65a51e4ab..1b351231c2 100644
--- a/extensions/arc/src/constants.ts
+++ b/extensions/arc/src/constants.ts
@@ -19,7 +19,6 @@ export class IconPathHelper {
public static edit: IconPath;
public static delete: IconPath;
public static openInTab: IconPath;
- public static heart: IconPath;
public static copy: IconPath;
public static collapseUp: IconPath;
public static collapseDown: IconPath;
@@ -34,6 +33,9 @@ export class IconPathHelper {
public static wrench: IconPath;
public static miaa: IconPath;
public static controller: IconPath;
+ public static health: IconPath;
+ public static success: IconPath;
+ public static fail: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.context = context;
@@ -53,10 +55,6 @@ export class IconPathHelper {
light: IconPathHelper.context.asAbsolutePath('images/open-in-tab.svg'),
dark: IconPathHelper.context.asAbsolutePath('images/open-in-tab.svg')
};
- IconPathHelper.heart = {
- light: IconPathHelper.context.asAbsolutePath('images/heart.svg'),
- dark: IconPathHelper.context.asAbsolutePath('images/heart.svg')
- };
IconPathHelper.copy = {
light: IconPathHelper.context.asAbsolutePath('images/copy.svg'),
dark: IconPathHelper.context.asAbsolutePath('images/copy.svg')
@@ -105,6 +103,18 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/data_controller.svg'),
dark: context.asAbsolutePath('images/data_controller.svg'),
};
+ IconPathHelper.health = {
+ light: context.asAbsolutePath('images/health.svg'),
+ dark: context.asAbsolutePath('images/health.svg'),
+ };
+ IconPathHelper.success = {
+ light: context.asAbsolutePath('images/success.svg'),
+ dark: context.asAbsolutePath('images/success.svg'),
+ };
+ IconPathHelper.fail = {
+ light: context.asAbsolutePath('images/fail.svg'),
+ dark: context.asAbsolutePath('images/fail.svg'),
+ };
}
}
diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts
index 7f2373c3d9..4d92fff6e8 100644
--- a/extensions/arc/src/localizedConstants.ts
+++ b/extensions/arc/src/localizedConstants.ts
@@ -28,6 +28,7 @@ export const backup = localize('arc.backup', "Backup");
export const newSupportRequest = localize('arc.newSupportRequest', "New support request");
export const diagnoseAndSolveProblems = localize('arc.diagnoseAndSolveProblems', "Diagnose and solve problems");
export const supportAndTroubleshooting = localize('arc.supportAndTroubleshooting', "Support + troubleshooting");
+export const resourceHealth = localize('arc.resourceHealth', "Resource health");
export const newInstance = localize('arc.createNew', "New Instance");
export const deleteText = localize('arc.delete', "Delete");
@@ -64,6 +65,9 @@ export const refresh = localize('arc.refresh', "Refresh");
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal.");
export const running = localize('arc.running', "Running");
+export const pending = localize('arc.pending', "Pending");
+export const failed = localize('arc.failed', "Failed");
+export const unknown = localize('arc.unknown', "Unknown");
export const connected = localize('arc.connected', "Connected");
export const disconnected = localize('arc.disconnected', "Disconnected");
export const loading = localize('arc.loading', "Loading...");
@@ -109,7 +113,10 @@ export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
export const enterANonEmptyPassword = localize('arc.enterANonEmptyPassword', "Enter a non empty password or press escape to exit.");
export const thePasswordsDoNotMatch = localize('arc.thePasswordsDoNotMatch', "The passwords do not match. Confirm the password or press escape to exit.");
export const passwordReset = localize('arc.passwordReset', "Password reset successfully");
-export const passwordResetFailed = localize('arc.passwordResetFailed', "Failed to reset password");
+export const podOverview = localize('arc.podOverview', "Pod overview");
+export const condition = localize('arc.condition', "Condition");
+export const details = localize('arc.details', "Details");
+export const lastUpdated = localize('arc.lastUpdated', "Last updated");
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
export function resourceDeleted(name: string): string { return localize('arc.resourceDeleted', "Resource '{0}' deleted", name); }
@@ -126,6 +133,7 @@ export function numVCores(vCores: string | undefined): string {
export function couldNotFindRegistration(namespace: string, name: string) { return localize('arc.couldNotFindRegistration', "Could not find controller registration for {0} ({1})", name, namespace); }
export function resourceDeletionWarning(namespace: string, name: string): string { return localize('arc.resourceDeletionWarning', "Warning! Deleting a resource is permanent and cannot be undone. To delete the resource '{0}.{1}' type the name '{1}' below to proceed.", namespace, name); }
export function invalidResourceDeletionName(name: string): string { return localize('arc.invalidResourceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
+export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
// Errors
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
@@ -133,3 +141,4 @@ export function openDashboardFailed(error: any): string { return localize('arc.o
export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, getErrorMessage(error)); }
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
+export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
index 1ca1772a39..9638835a92 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
@@ -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 {
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
index b3753a485b..738aca22cc 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
@@ -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
]
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
index 809bc5675c..b6cdacce34 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
@@ -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;
}
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
index ad154fd27f..dd24f3866c 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
@@ -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 {
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresResourceHealthPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresResourceHealthPage.ts
new file mode 100644
index 0000000000..e828af562b
--- /dev/null
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresResourceHealthPage.ts
@@ -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({
+ 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({
+ 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({
+ 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({
+ 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({
+ 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({
+ 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)
+ ];
+ }) ?? [];
+ }
+}