mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Arc Postgres resource health page (#11065)
This commit is contained in:
8
extensions/arc/images/fail.svg
Normal file
8
extensions/arc/images/fail.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill="#E00B1C" cx="8" cy="8" r="8"/>
|
||||
<path fill="#FFFFFF" d="M12.694,4.728l-1.422-1.422L8,6.578L4.728,3.306L3.306,4.728L6.578,8l-3.272,3.272l1.422,1.422L8,9.422
|
||||
l3.272,3.272l1.422-1.422L9.422,8L12.694,4.728z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 592 B |
1
extensions/arc/images/health.svg
Normal file
1
extensions/arc/images/health.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="e6af6735-bd3f-44c2-910e-4b8b228f3336" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="f5e553fa-756a-4028-aba5-719555f5d693" x1="8.66" y1="17.12" x2="8.66" y2="1.03" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1988d9"/><stop offset="0.22" stop-color="#218ddc"/><stop offset="0.56" stop-color="#379ce5"/><stop offset="0.9" stop-color="#54aef0"/></linearGradient></defs><title>Icon-general-4</title><path d="M8.66,17.12c8.18-5.86,8.46-9.28,8.49-10.35C17.2,5.27,17,1.36,13,1.05A4.26,4.26,0,0,0,8.66,3.9,4.28,4.28,0,0,0,4.27,1.05C.32,1.36.11,5.27.16,6.77c0,1.07.32,4.49,8.5,10.35" fill="url(#f5e553fa-756a-4028-aba5-719555f5d693)"/><path d="M17.15,6.77C17.2,5.27,17,1.36,13,1.05A4.26,4.26,0,0,0,8.66,3.9,4.28,4.28,0,0,0,4.27,1.05C.32,1.36.11,5.27.16,6.77c0,1.07.24,4.44,8.43,10.3" fill="none"/><path d="M17.15,6.18h-4a.17.17,0,0,0-.13.07L11.81,8.34a.16.16,0,0,1-.27,0L9.81,5.05a.31.31,0,0,0-.56,0L7.59,10a.16.16,0,0,1-.29,0L5.88,6.71a.31.31,0,0,0-.55,0L3.39,10.2a.16.16,0,0,1-.13.08H1.42a13,13,0,0,0,.9,1.22H4a.13.13,0,0,0,.13-.08L5.34,9.19a.16.16,0,0,1,.28,0l1.66,3.86a.31.31,0,0,0,.58,0L9.61,7.85a.15.15,0,0,1,.28,0l1.46,2.77a.3.3,0,0,0,.53,0l1.86-3.13a.16.16,0,0,1,.13-.08H17.1" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8002 -8.11447e-06C12.3474 0.00189106 12.8896 0.103554 13.4002 0.299992L14.8002 1.19999L15.7002 2.59999C15.9043 3.14328 16.0061 3.71966 16.0002 4.29999C15.9983 4.84713 15.8967 5.38933 15.7002 5.89999C15.4966 6.42202 15.1906 6.89803 14.8002 7.29999L8.00024 14L1.20024 7.29999C0.809851 6.89803 0.503844 6.42202 0.300236 5.89999C0.103798 5.38933 0.0021352 4.84713 0.000236026 4.29999C-0.00557996 3.71966 0.0961354 3.14328 0.300236 2.59999L1.20024 1.19999L2.60024 0.299992C3.14352 0.0958913 3.71991 -0.0058241 4.30024 -8.11447e-06H5.40024L6.30024 0.499992C6.64198 0.628901 6.9498 0.834113 7.20024 1.09999L8.00024 1.99999L8.80024 1.19999C9.05067 0.934113 9.35849 0.728901 9.70024 0.599992L10.6002 0.0999919L11.8002 -8.11447e-06ZM14.0002 6.49999C14.3492 6.24177 14.6249 5.89713 14.8002 5.49999C14.9155 5.10962 14.9827 4.70664 15.0002 4.29999C15.0064 3.84882 14.9035 3.40282 14.7002 2.99999C14.5855 2.6224 14.3794 2.27893 14.1002 1.99999C13.799 1.67702 13.4204 1.43611 13.0002 1.29999L11.8002 0.999992L10.6002 1.19999L9.60024 1.79999L8.80024 2.49999L8.00024 3.39999L7.20024 2.59999L6.30024 1.79999L5.40024 1.19999L4.30024 0.999992C3.84906 0.993784 3.40307 1.09671 3.00024 1.29999C2.60363 1.4394 2.23217 1.64201 1.90024 1.89999C1.64226 2.23192 1.43964 2.60338 1.30024 2.99999C1.09695 3.40282 0.994028 3.84882 1.00024 4.29999C1.01781 4.70664 1.08497 5.10962 1.20024 5.49999C1.37556 5.89713 1.65127 6.24177 2.00024 6.49999L8.00024 12.6L14.0002 6.49999Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
10
extensions/arc/images/success.svg
Normal file
10
extensions/arc/images/success.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill="#5DB300" cx="8" cy="8" r="8"/>
|
||||
<path fill="#FFFFFF" d="M3.553,8.291C3.479,8.212,3.441,8.108,3.445,8c0.004-0.109,0.05-0.209,0.13-0.284L4.41,6.944
|
||||
c0.076-0.069,0.173-0.107,0.274-0.107c0.112,0,0.22,0.047,0.296,0.129l2.136,2.292l3.807-4.875C11,4.284,11.116,4.226,11.242,4.226
|
||||
c0.09,0,0.175,0.029,0.247,0.084l0.906,0.699c0.173,0.128,0.21,0.377,0.08,0.554l-4.868,6.233c-0.189,0.242-0.55,0.258-0.76,0.033
|
||||
L3.553,8.291z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 804 B |
121
extensions/arc/src/common/date.ts
Normal file
121
extensions/arc/src/common/date.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)); }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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.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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
];
|
||||
}) ?? [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user