mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add refresh support to Arc Postgres pages (#10607)
This commit is contained in:
3
extensions/arc/images/refresh.svg
Normal file
3
extensions/arc/images/refresh.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.1328 0.296875C10.9974 0.53125 11.7891 0.898438 12.5078 1.39844C13.2266 1.89323 13.8438 2.48177 14.3594 3.16406C14.8802 3.84115 15.2839 4.59375 15.5703 5.42188C15.8568 6.24479 16 7.10417 16 8C16 8.73438 15.9036 9.44271 15.7109 10.125C15.5234 10.8073 15.2552 11.4453 14.9062 12.0391C14.5625 12.6328 14.1458 13.1745 13.6562 13.6641C13.1719 14.1484 12.6328 14.5651 12.0391 14.9141C11.4453 15.2578 10.8073 15.526 10.125 15.7188C9.44271 15.9062 8.73438 16 8 16C7.26562 16 6.55729 15.9062 5.875 15.7188C5.19271 15.526 4.55469 15.2578 3.96094 14.9141C3.36719 14.5651 2.82552 14.1484 2.33594 13.6641C1.85156 13.1745 1.4349 12.6328 1.08594 12.0391C0.742188 11.4453 0.473958 10.8099 0.28125 10.1328C0.09375 9.45052 0 8.73958 0 8C0 7.27083 0.0963542 6.5625 0.289062 5.875C0.481771 5.1875 0.755208 4.54167 1.10938 3.9375C1.46875 3.32812 1.90365 2.77604 2.41406 2.28125C2.92448 1.78125 3.5 1.35417 4.14062 1H2V0H6V4H5V1.67969C4.39062 1.97135 3.83854 2.33854 3.34375 2.78125C2.85417 3.21875 2.4349 3.71354 2.08594 4.26562C1.73698 4.8125 1.46875 5.40365 1.28125 6.03906C1.09375 6.67448 1 7.32812 1 8C1 8.64062 1.08333 9.26042 1.25 9.85938C1.41667 10.4531 1.65104 11.0104 1.95312 11.5312C2.26042 12.0469 2.6276 12.5182 3.05469 12.9453C3.48177 13.3724 3.95312 13.7396 4.46875 14.0469C4.98958 14.349 5.54688 14.5833 6.14062 14.75C6.73438 14.9167 7.35417 15 8 15C8.64062 15 9.25781 14.9167 9.85156 14.75C10.4505 14.5833 11.0078 14.349 11.5234 14.0469C12.0443 13.7396 12.5182 13.3724 12.9453 12.9453C13.3724 12.5182 13.737 12.0469 14.0391 11.5312C14.3464 11.0104 14.5833 10.4531 14.75 9.85938C14.9167 9.26562 15 8.64583 15 8C15 7.21875 14.8724 6.46615 14.6172 5.74219C14.3672 5.01823 14.0156 4.35938 13.5625 3.76562C13.1094 3.17188 12.5677 2.65885 11.9375 2.22656C11.3125 1.78906 10.6224 1.46615 9.86719 1.25781L10.1328 0.296875Z" fill="#0078D4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -27,6 +27,7 @@ export class IconPathHelper {
|
|||||||
public static backup: IconPath;
|
public static backup: IconPath;
|
||||||
public static properties: IconPath;
|
public static properties: IconPath;
|
||||||
public static networking: IconPath;
|
public static networking: IconPath;
|
||||||
|
public static refresh: IconPath;
|
||||||
|
|
||||||
public static setExtensionContext(context: vscode.ExtensionContext) {
|
public static setExtensionContext(context: vscode.ExtensionContext) {
|
||||||
IconPathHelper.context = context;
|
IconPathHelper.context = context;
|
||||||
@@ -86,6 +87,10 @@ export class IconPathHelper {
|
|||||||
light: context.asAbsolutePath('images/security.svg'),
|
light: context.asAbsolutePath('images/security.svg'),
|
||||||
dark: context.asAbsolutePath('images/security.svg')
|
dark: context.asAbsolutePath('images/security.svg')
|
||||||
};
|
};
|
||||||
|
IconPathHelper.refresh = {
|
||||||
|
light: context.asAbsolutePath('images/refresh.svg'),
|
||||||
|
dark: context.asAbsolutePath('images/refresh.svg')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,19 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||||||
const dbNamespace = '';
|
const dbNamespace = '';
|
||||||
const dbName = '';
|
const dbName = '';
|
||||||
|
|
||||||
|
try {
|
||||||
const controllerModel = new ControllerModel(controllerUrl, auth);
|
const controllerModel = new ControllerModel(controllerUrl, auth);
|
||||||
const databaseModel = new PostgresModel(controllerUrl, auth, dbNamespace, dbName);
|
const postgresModel = new PostgresModel(controllerUrl, auth, dbNamespace, dbName);
|
||||||
const postgresDashboard = new PostgresDashboard(loc.postgresDashboard, controllerModel, databaseModel);
|
const postgresDashboard = new PostgresDashboard(loc.postgresDashboard, controllerModel, postgresModel);
|
||||||
await postgresDashboard.showDashboard();
|
|
||||||
|
await Promise.all([
|
||||||
|
postgresDashboard.showDashboard(),
|
||||||
|
controllerModel.refresh(),
|
||||||
|
postgresModel.refresh()
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.failedToManagePostgres(`${dbNamespace}.${dbName}`, error));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const feedback = localize('arc.feedback', 'Feedback');
|
|||||||
export const selectConnectionString = localize('arc.selectConnectionString', 'Select from available client connection strings below');
|
export const selectConnectionString = localize('arc.selectConnectionString', 'Select from available client connection strings below');
|
||||||
export const vCores = localize('arc.vCores', 'vCores');
|
export const vCores = localize('arc.vCores', 'vCores');
|
||||||
export const ram = localize('arc.ram', 'RAM');
|
export const ram = localize('arc.ram', 'RAM');
|
||||||
|
export const refresh = localize('arc.refresh', 'Refresh');
|
||||||
|
|
||||||
// Postgres constants
|
// Postgres constants
|
||||||
export const coordinatorEndpoint = localize('arc.coordinatorEndpoint', 'Coordinator endpoint');
|
export const coordinatorEndpoint = localize('arc.coordinatorEndpoint', 'Coordinator endpoint');
|
||||||
@@ -66,14 +67,16 @@ export const node = localize('arc.node', 'node');
|
|||||||
export const nodes = localize('arc.nodes', 'nodes');
|
export const nodes = localize('arc.nodes', 'nodes');
|
||||||
export const storagePerNode = localize('arc.storagePerNode', 'storage per node');
|
export const storagePerNode = localize('arc.storagePerNode', 'storage per node');
|
||||||
|
|
||||||
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database '{0}' created", name); }
|
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
|
||||||
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database '{0}'. {1}", name, (error instanceof Error ? error.message : error)); }
|
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, (error instanceof Error ? error.message : error)); }
|
||||||
export function passwordReset(name: string): string { return localize('arc.passwordReset', "Password reset for service '{0}'", name); }
|
export function passwordReset(name: string): string { return localize('arc.passwordReset', "Password reset for service {0}", name); }
|
||||||
export function passwordResetFailed(name: string, error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password for service '{0}'. {1}", name, (error instanceof Error ? error.message : error)); }
|
export function passwordResetFailed(name: string, error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password for service {0}. {1}", name, (error instanceof Error ? error.message : error)); }
|
||||||
export function deleteServicePrompt(name: string): string { return localize('arc.deleteServicePrompt', "Delete service '{0}'?", name); }
|
export function deleteServicePrompt(name: string): string { return localize('arc.deleteServicePrompt', "Delete service {0}?", name); }
|
||||||
export function serviceDeleted(name: string): string { return localize('arc.serviceDeleted', "Service '{0}' deleted", name); }
|
export function serviceDeleted(name: string): string { return localize('arc.serviceDeleted', "Service {0} deleted", name); }
|
||||||
export function serviceDeletionFailed(name: string, error: any): string { return localize('arc.serviceDeletionFailed', "Failed to delete service '{0}'. {1}", name, (error instanceof Error ? error.message : error)); }
|
export function serviceDeletionFailed(name: string, error: any): string { return localize('arc.serviceDeletionFailed', "Failed to delete service {0}. {1}", name, (error instanceof Error ? error.message : error)); }
|
||||||
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for '{0}'", name); }
|
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
||||||
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', '{0} copied to clipboard', name); }
|
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', '{0} copied to clipboard', name); }
|
||||||
|
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", (error instanceof Error ? error.message : error)); }
|
||||||
|
export function failedToManagePostgres(name: string, error: any): string { return localize('arc.failedToManagePostgres', "Failed to manage Postgres {0}. {1}", name, (error instanceof Error ? error.message : error)); }
|
||||||
|
|
||||||
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
|
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import { Authentication } from '../controller/auth';
|
import { Authentication } from '../controller/auth';
|
||||||
import { EndpointsRouterApi, EndpointModel, RegistrationRouterApi, RegistrationResponse, TokenRouterApi } from '../controller/generated/v1/api';
|
import { EndpointsRouterApi, EndpointModel, RegistrationRouterApi, RegistrationResponse, TokenRouterApi } from '../controller/generated/v1/api';
|
||||||
|
|
||||||
@@ -10,9 +11,16 @@ export class ControllerModel {
|
|||||||
private _endpointsRouter: EndpointsRouterApi;
|
private _endpointsRouter: EndpointsRouterApi;
|
||||||
private _tokenRouter: TokenRouterApi;
|
private _tokenRouter: TokenRouterApi;
|
||||||
private _registrationRouter: RegistrationRouterApi;
|
private _registrationRouter: RegistrationRouterApi;
|
||||||
private _endpoints!: EndpointModel[];
|
private _endpoints?: EndpointModel[];
|
||||||
private _namespace!: string;
|
private _namespace?: string;
|
||||||
private _registrations!: RegistrationResponse[];
|
private _registrations?: RegistrationResponse[];
|
||||||
|
|
||||||
|
private readonly _onEndpointsUpdated = new vscode.EventEmitter<EndpointModel[]>();
|
||||||
|
private readonly _onRegistrationsUpdated = new vscode.EventEmitter<RegistrationResponse[]>();
|
||||||
|
public onEndpointsUpdated = this._onEndpointsUpdated.event;
|
||||||
|
public onRegistrationsUpdated = this._onRegistrationsUpdated.event;
|
||||||
|
public endpointsLastUpdated?: Date;
|
||||||
|
public registrationsLastUpdated?: Date;
|
||||||
|
|
||||||
constructor(controllerUrl: string, auth: Authentication) {
|
constructor(controllerUrl: string, auth: Authentication) {
|
||||||
this._endpointsRouter = new EndpointsRouterApi(controllerUrl);
|
this._endpointsRouter = new EndpointsRouterApi(controllerUrl);
|
||||||
@@ -29,33 +37,36 @@ export class ControllerModel {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
this._endpointsRouter.apiV1BdcEndpointsGet().then(response => {
|
this._endpointsRouter.apiV1BdcEndpointsGet().then(response => {
|
||||||
this._endpoints = response.body;
|
this._endpoints = response.body;
|
||||||
|
this.endpointsLastUpdated = new Date();
|
||||||
|
this._onEndpointsUpdated.fire(this._endpoints);
|
||||||
}),
|
}),
|
||||||
this._tokenRouter.apiV1TokenPost().then(async response => {
|
this._tokenRouter.apiV1TokenPost().then(async response => {
|
||||||
this._namespace = response.body.namespace!;
|
this._namespace = response.body.namespace!;
|
||||||
})
|
|
||||||
]).then(async _ => {
|
|
||||||
this._registrations = (await this._registrationRouter.apiV1RegistrationListResourcesNsGet(this._namespace)).body;
|
this._registrations = (await this._registrationRouter.apiV1RegistrationListResourcesNsGet(this._namespace)).body;
|
||||||
});
|
this.registrationsLastUpdated = new Date();
|
||||||
|
this._onRegistrationsUpdated.fire(this._registrations);
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public endpoints(): EndpointModel[] {
|
public endpoints(): EndpointModel[] | undefined {
|
||||||
return this._endpoints;
|
return this._endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
public endpoint(name: string): EndpointModel | undefined {
|
public endpoint(name: string): EndpointModel | undefined {
|
||||||
return this._endpoints.find(e => e.name === name);
|
return this._endpoints?.find(e => e.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public namespace(): string {
|
public namespace(): string | undefined {
|
||||||
return this._namespace;
|
return this._namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public registrations(): RegistrationResponse[] {
|
public registrations(): RegistrationResponse[] | undefined {
|
||||||
return this._registrations;
|
return this._registrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public registration(type: string, namespace: string, name: string): RegistrationResponse | undefined {
|
public registration(type: string, namespace: string, name: string): RegistrationResponse | undefined {
|
||||||
return this._registrations.find(r => {
|
return this._registrations?.find(r => {
|
||||||
// Resources deployed outside the controller's namespace are named in the format 'namespace_name'
|
// Resources deployed outside the controller's namespace are named in the format 'namespace_name'
|
||||||
let instanceName = r.instanceName!;
|
let instanceName = r.instanceName!;
|
||||||
const parts: string[] = instanceName.split('_');
|
const parts: string[] = instanceName.split('_');
|
||||||
|
|||||||
@@ -3,14 +3,21 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
import { DuskyObjectModelsDatabaseService, DatabaseRouterApi, DuskyObjectModelsDatabase, V1Status } from '../controller/generated/dusky/api';
|
import { DuskyObjectModelsDatabaseService, DatabaseRouterApi, DuskyObjectModelsDatabase, V1Status } from '../controller/generated/dusky/api';
|
||||||
import { Authentication } from '../controller/auth';
|
import { Authentication } from '../controller/auth';
|
||||||
|
|
||||||
export class PostgresModel {
|
export class PostgresModel {
|
||||||
private _databaseRouter: DatabaseRouterApi;
|
private _databaseRouter: DatabaseRouterApi;
|
||||||
private _service!: DuskyObjectModelsDatabaseService;
|
private _service?: DuskyObjectModelsDatabaseService;
|
||||||
private _password!: string;
|
private _password?: string;
|
||||||
|
private readonly _onServiceUpdated = new vscode.EventEmitter<DuskyObjectModelsDatabaseService>();
|
||||||
|
private readonly _onPasswordUpdated = new vscode.EventEmitter<string>();
|
||||||
|
public onServiceUpdated = this._onServiceUpdated.event;
|
||||||
|
public onPasswordUpdated = this._onPasswordUpdated.event;
|
||||||
|
public serviceLastUpdated?: Date;
|
||||||
|
public passwordLastUpdated?: Date;
|
||||||
|
|
||||||
constructor(controllerUrl: string, auth: Authentication, private _namespace: string, private _name: string) {
|
constructor(controllerUrl: string, auth: Authentication, private _namespace: string, private _name: string) {
|
||||||
this._databaseRouter = new DatabaseRouterApi(controllerUrl);
|
this._databaseRouter = new DatabaseRouterApi(controllerUrl);
|
||||||
@@ -33,23 +40,27 @@ export class PostgresModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's spec */
|
/** Returns the service's spec */
|
||||||
public service(): DuskyObjectModelsDatabaseService {
|
public service(): DuskyObjectModelsDatabaseService | undefined {
|
||||||
return this._service;
|
return this._service;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's password */
|
/** Returns the service's password */
|
||||||
public password(): string {
|
public password(): string | undefined {
|
||||||
return this._password;
|
return this._password;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Refreshes the service's model */
|
/** Refreshes the model */
|
||||||
public async refresh() {
|
public async refresh() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this._databaseRouter.getDuskyDatabaseService(this._namespace, this._name).then(response => {
|
this._databaseRouter.getDuskyDatabaseService(this._namespace, this._name).then(response => {
|
||||||
this._service = response.body;
|
this._service = response.body;
|
||||||
|
this.serviceLastUpdated = new Date();
|
||||||
|
this._onServiceUpdated.fire(this._service);
|
||||||
}),
|
}),
|
||||||
this._databaseRouter.getDuskyPassword(this._namespace, this._name).then(async response => {
|
this._databaseRouter.getDuskyPassword(this._namespace, this._name).then(async response => {
|
||||||
this._password = response.body;
|
this._password = response.body;
|
||||||
|
this.passwordLastUpdated = new Date();
|
||||||
|
this._onPasswordUpdated.fire(this._password!);
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -82,7 +93,7 @@ export class PostgresModel {
|
|||||||
|
|
||||||
/** Returns the number of nodes in the service */
|
/** Returns the number of nodes in the service */
|
||||||
public numNodes(): number {
|
public numNodes(): number {
|
||||||
let nodes = this._service.spec.scale?.shards ?? 1;
|
let nodes = this._service?.spec.scale?.shards ?? 1;
|
||||||
if (nodes > 1) { nodes++; } // for multiple shards there is an additional node for the coordinator
|
if (nodes > 1) { nodes++; } // for multiple shards there is an additional node for the coordinator
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
@@ -92,10 +103,10 @@ export class PostgresModel {
|
|||||||
* internal IP. If either field is not available it will be set to undefined.
|
* internal IP. If either field is not available it will be set to undefined.
|
||||||
*/
|
*/
|
||||||
public endpoint(): { ip?: string, port?: number } {
|
public endpoint(): { ip?: string, port?: number } {
|
||||||
const externalIp = this._service.status?.externalIP;
|
const externalIp = this._service?.status?.externalIP;
|
||||||
const internalIp = this._service.status?.internalIP;
|
const internalIp = this._service?.status?.internalIP;
|
||||||
const externalPort = this._service.status?.externalPort;
|
const externalPort = this._service?.status?.externalPort;
|
||||||
const internalPort = this._service.status?.internalPort;
|
const internalPort = this._service?.status?.internalPort;
|
||||||
|
|
||||||
return externalIp ? { ip: externalIp, port: externalPort ?? undefined }
|
return externalIp ? { ip: externalIp, port: externalPort ?? undefined }
|
||||||
: internalIp ? { ip: internalIp, port: internalPort ?? undefined }
|
: internalIp ? { ip: internalIp, port: internalPort ?? undefined }
|
||||||
@@ -105,26 +116,22 @@ export class PostgresModel {
|
|||||||
/** Returns the service's configuration e.g. '3 nodes, 1.5 vCores, 1GiB RAM, 2GiB storage per node' */
|
/** Returns the service's configuration e.g. '3 nodes, 1.5 vCores, 1GiB RAM, 2GiB storage per node' */
|
||||||
public configuration(): string {
|
public configuration(): string {
|
||||||
const nodes = this.numNodes();
|
const nodes = this.numNodes();
|
||||||
const cpuLimit = this._service.spec.scheduling?.resources?.limits?.['cpu'];
|
const cpuLimit = this._service?.spec.scheduling?.resources?.limits?.['cpu'];
|
||||||
const ramLimit = this._service.spec.scheduling?.resources?.limits?.['memory'];
|
const ramLimit = this._service?.spec.scheduling?.resources?.limits?.['memory'];
|
||||||
const cpuRequest = this._service.spec.scheduling?.resources?.requests?.['cpu'];
|
const cpuRequest = this._service?.spec.scheduling?.resources?.requests?.['cpu'];
|
||||||
const ramRequest = this._service.spec.scheduling?.resources?.requests?.['memory'];
|
const ramRequest = this._service?.spec.scheduling?.resources?.requests?.['memory'];
|
||||||
const storage = this._service.spec.storage.volumeSize;
|
const storage = this._service?.spec.storage.volumeSize;
|
||||||
|
|
||||||
// Prefer limits if they're provided, otherwise use requests if they're provided
|
// Prefer limits if they're provided, otherwise use requests if they're provided
|
||||||
let nodeConfiguration = `${nodes} ${nodes > 1 ? loc.nodes : loc.node}`;
|
let configuration = `${nodes} ${nodes > 1 ? loc.nodes : loc.node}`;
|
||||||
if (cpuLimit) {
|
if (cpuLimit || cpuRequest) {
|
||||||
nodeConfiguration += `, ${this.formatCores(cpuLimit)} ${loc.vCores}`;
|
configuration += `, ${this.formatCores(cpuLimit ?? cpuRequest!)} ${loc.vCores}`;
|
||||||
} else if (cpuRequest) {
|
|
||||||
nodeConfiguration += `, ${this.formatCores(cpuRequest)} ${loc.vCores}`;
|
|
||||||
}
|
}
|
||||||
if (ramLimit) {
|
if (ramLimit || ramRequest) {
|
||||||
nodeConfiguration += `, ${this.formatMemory(ramLimit)} ${loc.ram}`;
|
configuration += `, ${this.formatMemory(ramLimit ?? ramRequest!)} ${loc.ram}`;
|
||||||
} else if (ramRequest) {
|
|
||||||
nodeConfiguration += `, ${this.formatMemory(ramRequest)} ${loc.ram}`;
|
|
||||||
}
|
}
|
||||||
if (storage) { nodeConfiguration += `, ${storage} ${loc.storagePerNode}`; }
|
if (storage) { configuration += `, ${storage} ${loc.storagePerNode}`; }
|
||||||
return nodeConfiguration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper } from '../../../constants';
|
import { IconPathHelper } from '../../../constants';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
|
||||||
export class PostgresBackupPage extends PostgresDashboardPage {
|
export class PostgresBackupPage extends DashboardPage {
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.backup;
|
return loc.backup;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper } from '../../../constants';
|
import { IconPathHelper } from '../../../constants';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
|
||||||
export class PostgresComputeStoragePage extends PostgresDashboardPage {
|
export class PostgresComputeStoragePage extends DashboardPage {
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.computeAndStorage;
|
return loc.computeAndStorage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,23 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* 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 azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { KeyValueContainer, InputKeyValue } from '../../components/keyValueContainer';
|
||||||
import { KeyValueContainer, KeyValue, InputKeyValue } from '../../components/keyValueContainer';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export class PostgresConnectionStringsPage extends DashboardPage {
|
||||||
|
private keyValueContainer?: KeyValueContainer;
|
||||||
|
|
||||||
|
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
||||||
|
super(modelView);
|
||||||
|
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
|
||||||
|
this._postgresModel.onPasswordUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
|
||||||
|
}
|
||||||
|
|
||||||
export class PostgresConnectionStringsPage extends PostgresDashboardPage {
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.connectionStrings;
|
return loc.connectionStrings;
|
||||||
}
|
}
|
||||||
@@ -46,10 +56,39 @@ export class PostgresConnectionStringsPage extends PostgresDashboardPage {
|
|||||||
this.modelView.modelBuilder.flexContainer().withItems([info, link]).withLayout({ flexWrap: 'wrap' }).component(),
|
this.modelView.modelBuilder.flexContainer().withItems([info, link]).withLayout({ flexWrap: 'wrap' }).component(),
|
||||||
{ CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } });
|
{ CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } });
|
||||||
|
|
||||||
const endpoint: { ip?: string, port?: number } = this.databaseModel.endpoint();
|
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
|
||||||
const password = this.databaseModel.password();
|
content.addItem(this.keyValueContainer.container);
|
||||||
|
this.initialized = true;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
const pairs: KeyValue[] = [
|
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() {
|
||||||
|
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint();
|
||||||
|
const password = this._postgresModel.password();
|
||||||
|
|
||||||
|
this.keyValueContainer?.refresh([
|
||||||
new InputKeyValue('ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password=${password};Ssl Mode=Require;`),
|
new InputKeyValue('ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password=${password};Ssl Mode=Require;`),
|
||||||
new InputKeyValue('C++ (libpq)', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password=${password} sslmode=require`),
|
new InputKeyValue('C++ (libpq)', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password=${password} sslmode=require`),
|
||||||
new InputKeyValue('JDBC', `jdbc:postgresql://${endpoint.ip}:${endpoint.port}/postgres?user=postgres&password=${password}&sslmode=require`),
|
new InputKeyValue('JDBC', `jdbc:postgresql://${endpoint.ip}:${endpoint.port}/postgres?user=postgres&password=${password}&sslmode=require`),
|
||||||
@@ -59,14 +98,6 @@ export class PostgresConnectionStringsPage extends PostgresDashboardPage {
|
|||||||
new InputKeyValue('Python', `dbname='postgres' user='postgres' host='${endpoint.ip}' password='${password}' port='${endpoint.port}' sslmode='true'`),
|
new InputKeyValue('Python', `dbname='postgres' user='postgres' host='${endpoint.ip}' password='${password}' port='${endpoint.port}' sslmode='true'`),
|
||||||
new InputKeyValue('Ruby', `host=${endpoint.ip}; dbname=postgres user=postgres password=${password} port=${endpoint.port} sslmode=require`),
|
new InputKeyValue('Ruby', `host=${endpoint.ip}; dbname=postgres user=postgres password=${password} port=${endpoint.port} sslmode=require`),
|
||||||
new InputKeyValue('Web App', `Database=postgres; Data Source=${endpoint.ip}; User Id=postgres; Password=${password}`)
|
new InputKeyValue('Web App', `Database=postgres; Data Source=${endpoint.ip}; User Id=postgres; Password=${password}`)
|
||||||
];
|
]);
|
||||||
|
|
||||||
const keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, pairs);
|
|
||||||
content.addItem(keyValueContainer.container);
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().component();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,19 +16,17 @@ import { PostgresNetworkingPage } from './postgresNetworkingPage';
|
|||||||
import { Dashboard } from '../../components/dashboard';
|
import { Dashboard } from '../../components/dashboard';
|
||||||
|
|
||||||
export class PostgresDashboard extends Dashboard {
|
export class PostgresDashboard extends Dashboard {
|
||||||
constructor(title: string, private _controllerModel: ControllerModel, private _databaseModel: PostgresModel) {
|
constructor(title: string, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(title);
|
super(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||||
await Promise.all([this._controllerModel.refresh(), this._databaseModel.refresh()]);
|
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
|
||||||
|
const computeStoragePage = new PostgresComputeStoragePage(modelView);
|
||||||
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._databaseModel);
|
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
|
||||||
const computeStoragePage = new PostgresComputeStoragePage(modelView, this._controllerModel, this._databaseModel);
|
const backupPage = new PostgresBackupPage(modelView);
|
||||||
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._controllerModel, this._databaseModel);
|
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
|
||||||
const backupPage = new PostgresBackupPage(modelView, this._controllerModel, this._databaseModel);
|
const networkingPage = new PostgresNetworkingPage(modelView);
|
||||||
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._databaseModel);
|
|
||||||
const networkingPage = new PostgresNetworkingPage(modelView, this._controllerModel, this._databaseModel);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
overviewPage.tab,
|
overviewPage.tab,
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* 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 { ControllerModel } from '../../../models/controllerModel';
|
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
|
|
||||||
export abstract class PostgresDashboardPage extends DashboardPage {
|
|
||||||
constructor(protected modelView: azdata.ModelView, protected controllerModel: ControllerModel, protected databaseModel: PostgresModel) {
|
|
||||||
super(modelView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper } from '../../../constants';
|
import { IconPathHelper } from '../../../constants';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
|
||||||
export class PostgresNetworkingPage extends PostgresDashboardPage {
|
export class PostgresNetworkingPage extends DashboardPage {
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.networking;
|
return loc.networking;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,32 @@ import * as azdata from 'azdata';
|
|||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { DuskyObjectModelsDatabase, DuskyObjectModelsDatabaseServiceArcPayload } from '../../../controller/generated/dusky/api';
|
import { DuskyObjectModelsDatabase, DuskyObjectModelsDatabaseServiceArcPayload } from '../../../controller/generated/dusky/api';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export class PostgresOverviewPage extends DashboardPage {
|
||||||
|
private propertiesLoading?: azdata.LoadingComponent;
|
||||||
|
private kibanaLoading?: azdata.LoadingComponent;
|
||||||
|
private grafanaLoading?: azdata.LoadingComponent;
|
||||||
|
private nodesTableLoading?: azdata.LoadingComponent;
|
||||||
|
|
||||||
|
private properties?: azdata.PropertiesContainerComponent;
|
||||||
|
private kibanaLink?: azdata.HyperlinkComponent;
|
||||||
|
private grafanaLink?: azdata.HyperlinkComponent;
|
||||||
|
private nodesTable?: azdata.DeclarativeTableComponent;
|
||||||
|
|
||||||
|
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.onPasswordUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshProperties()));
|
||||||
|
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => {
|
||||||
|
this.refreshProperties();
|
||||||
|
this.refreshNodes();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export class PostgresOverviewPage extends PostgresDashboardPage {
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.overview;
|
return loc.overview;
|
||||||
}
|
}
|
||||||
@@ -28,34 +51,18 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
const content = this.modelView.modelBuilder.divContainer().component();
|
const content = this.modelView.modelBuilder.divContainer().component();
|
||||||
root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
|
root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
|
||||||
|
|
||||||
const registration = this.controllerModel.registration('postgresInstances', this.databaseModel.namespace(), this.databaseModel.name());
|
// Properties
|
||||||
const endpoint: { ip?: string, port?: number } = this.databaseModel.endpoint();
|
this.properties = this.modelView.modelBuilder.propertiesContainer().component();
|
||||||
const essentials = this.modelView.modelBuilder.propertiesContainer().withProperties<azdata.PropertiesContainerComponentProperties>({
|
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.properties).component();
|
||||||
propertyItems: [
|
content.addItem(this.propertiesLoading, { CSSStyles: cssStyles.text });
|
||||||
{ displayName: loc.name, value: this.databaseModel.name() },
|
|
||||||
{ displayName: loc.serverGroupType, value: loc.postgresArcProductName },
|
|
||||||
{ displayName: loc.resourceGroup, value: registration?.resourceGroupName ?? 'None' },
|
|
||||||
{ displayName: loc.coordinatorEndpoint, value: `postgresql://postgres:${this.databaseModel.password()}@${endpoint.ip}:${endpoint.port}` },
|
|
||||||
{ displayName: loc.status, value: this.databaseModel.service().status?.state ?? '' },
|
|
||||||
{ displayName: loc.postgresAdminUsername, value: 'postgres' },
|
|
||||||
{ displayName: loc.dataController, value: this.controllerModel.namespace() },
|
|
||||||
{ displayName: loc.nodeConfiguration, value: this.databaseModel.configuration() },
|
|
||||||
{ displayName: loc.subscriptionId, value: registration?.subscriptionId ?? 'None' },
|
|
||||||
{ displayName: loc.postgresVersion, value: this.databaseModel.service().spec.engine.version?.toString() ?? '' }
|
|
||||||
]
|
|
||||||
}).component();
|
|
||||||
content.addItem(essentials, { CSSStyles: cssStyles.text });
|
|
||||||
|
|
||||||
// Service endpoints
|
// Service endpoints
|
||||||
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
|
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
|
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
|
||||||
|
this.kibanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||||
const kibanaQuery = `kubernetes_namespace:"${this.databaseModel.namespace()}" and cluster_name:"${this.databaseModel.name()}"`;
|
this.grafanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||||
const kibanaUrl = `${this.controllerModel.endpoint('logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
|
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.kibanaLink).component();
|
||||||
const grafanaUrl = `${this.controllerModel.endpoint('metricsui')?.endpoint}/d/postgres-metrics?var-Namespace=${this.databaseModel.namespace()}&var-Name=${this.databaseModel.name()}`;
|
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.grafanaLink).component();
|
||||||
|
|
||||||
const kibanaLink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: kibanaUrl, url: kibanaUrl, }).component();
|
|
||||||
const grafanaLink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: grafanaUrl, url: grafanaUrl }).component();
|
|
||||||
|
|
||||||
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -92,14 +99,14 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: [
|
data: [
|
||||||
[loc.kibanaDashboard, kibanaLink, loc.kibanaDashboardDescription],
|
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
||||||
[loc.grafanaDashboard, grafanaLink, loc.grafanaDashboardDescription]]
|
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
||||||
}).component();
|
}).component();
|
||||||
content.addItem(endpointsTable);
|
content.addItem(endpointsTable);
|
||||||
|
|
||||||
// Server group nodes
|
// Server group nodes
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serverGroupNodes, CSSStyles: titleCSS }).component());
|
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serverGroupNodes, CSSStyles: titleCSS }).component());
|
||||||
const nodesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
this.nodesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -130,15 +137,9 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
data: []
|
data: []
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const nodes = this.databaseModel.numNodes();
|
this.nodesTableLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.nodesTable).component();
|
||||||
for (let i = 0; i < nodes; i++) {
|
content.addItem(this.nodesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
|
||||||
nodesTable.data.push([
|
this.initialized = true;
|
||||||
`${this.databaseModel.name()}-${i}`,
|
|
||||||
i === 0 ? loc.coordinatorEndpoint : loc.worker,
|
|
||||||
i === 0 ? `${endpoint.ip}:${endpoint.port}` : `${this.databaseModel.name()}-${i}.${this.databaseModel.name()}-svc.${this.databaseModel.namespace()}.svc.cluster.local`]);
|
|
||||||
}
|
|
||||||
|
|
||||||
content.addItem(nodesTable, { CSSStyles: { 'margin-bottom': '20px' } });
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,14 +151,18 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
newDatabaseButton.onDidClick(async () => {
|
newDatabaseButton.onDidClick(async () => {
|
||||||
const name = await vscode.window.showInputBox({ prompt: loc.databaseName });
|
newDatabaseButton.enabled = false;
|
||||||
|
let name;
|
||||||
|
try {
|
||||||
|
name = await vscode.window.showInputBox({ prompt: loc.databaseName });
|
||||||
if (name === undefined) { return; }
|
if (name === undefined) { return; }
|
||||||
const db: DuskyObjectModelsDatabase = { name: name }; // TODO support other options (sharded, owner)
|
const db: DuskyObjectModelsDatabase = { name: name }; // TODO support other options (sharded, owner)
|
||||||
try {
|
await this._postgresModel.createDatabase(db);
|
||||||
await this.databaseModel.createDatabase(db);
|
|
||||||
vscode.window.showInformationMessage(loc.databaseCreated(db.name));
|
vscode.window.showInformationMessage(loc.databaseCreated(db.name));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.databaseCreationFailed(db.name, error));
|
vscode.window.showErrorMessage(loc.databaseCreationFailed(name ?? '', error));
|
||||||
|
} finally {
|
||||||
|
newDatabaseButton.enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,16 +173,19 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
resetPasswordButton.onDidClick(async () => {
|
resetPasswordButton.onDidClick(async () => {
|
||||||
|
resetPasswordButton.enabled = false;
|
||||||
|
try {
|
||||||
const password = await vscode.window.showInputBox({ prompt: loc.newPassword, password: true });
|
const password = await vscode.window.showInputBox({ prompt: loc.newPassword, password: true });
|
||||||
if (password === undefined) { return; }
|
if (password === undefined) { return; }
|
||||||
try {
|
await this._postgresModel.update(s => {
|
||||||
await this.databaseModel.update(s => {
|
|
||||||
s.arc = s.arc ?? new DuskyObjectModelsDatabaseServiceArcPayload();
|
s.arc = s.arc ?? new DuskyObjectModelsDatabaseServiceArcPayload();
|
||||||
s.arc.servicePassword = password;
|
s.arc.servicePassword = password;
|
||||||
});
|
});
|
||||||
vscode.window.showInformationMessage(loc.passwordReset(this.databaseModel.fullName()));
|
vscode.window.showInformationMessage(loc.passwordReset(this._postgresModel.fullName()));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.passwordResetFailed(this.databaseModel.fullName(), error));
|
vscode.window.showErrorMessage(loc.passwordResetFailed(this._postgresModel.fullName(), error));
|
||||||
|
} finally {
|
||||||
|
resetPasswordButton.enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,15 +196,44 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
deleteButton.onDidClick(async () => {
|
deleteButton.onDidClick(async () => {
|
||||||
|
deleteButton.enabled = false;
|
||||||
|
try {
|
||||||
const response = await vscode.window.showQuickPick([loc.yes, loc.no], {
|
const response = await vscode.window.showQuickPick([loc.yes, loc.no], {
|
||||||
placeHolder: loc.deleteServicePrompt(this.databaseModel.fullName())
|
placeHolder: loc.deleteServicePrompt(this._postgresModel.fullName())
|
||||||
});
|
});
|
||||||
if (response !== loc.yes) { return; }
|
if (response !== loc.yes) { return; }
|
||||||
try {
|
await this._postgresModel.delete();
|
||||||
await this.databaseModel.delete();
|
vscode.window.showInformationMessage(loc.serviceDeleted(this._postgresModel.fullName()));
|
||||||
vscode.window.showInformationMessage(loc.serviceDeleted(this.databaseModel.fullName()));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.serviceDeletionFailed(this.databaseModel.fullName(), error));
|
vscode.window.showErrorMessage(loc.serviceDeletionFailed(this._postgresModel.fullName(), error));
|
||||||
|
} finally {
|
||||||
|
deleteButton.enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh
|
||||||
|
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
label: loc.refresh,
|
||||||
|
iconPath: IconPathHelper.refresh
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
refreshButton.onDidClick(async () => {
|
||||||
|
refreshButton.enabled = false;
|
||||||
|
try {
|
||||||
|
this.propertiesLoading!.loading = true;
|
||||||
|
this.kibanaLoading!.loading = true;
|
||||||
|
this.grafanaLoading!.loading = true;
|
||||||
|
this.nodesTableLoading!.loading = true;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this._postgresModel.refresh(),
|
||||||
|
this._controllerModel.refresh()
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
refreshButton.enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -207,27 +244,70 @@ export class PostgresOverviewPage extends PostgresDashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
openInAzurePortalButton.onDidClick(async () => {
|
openInAzurePortalButton.onDidClick(async () => {
|
||||||
const r = this.controllerModel.registration('postgresInstances', this.databaseModel.namespace(), this.databaseModel.name());
|
const r = this._controllerModel.registration('postgresInstances', this._postgresModel.namespace(), this._postgresModel.name());
|
||||||
if (r === undefined) {
|
if (r === undefined) {
|
||||||
vscode.window.showErrorMessage(loc.couldNotFindAzureResource(this.databaseModel.fullName()));
|
vscode.window.showErrorMessage(loc.couldNotFindAzureResource(this._postgresModel.fullName()));
|
||||||
} else {
|
} else {
|
||||||
vscode.env.openExternal(vscode.Uri.parse(
|
vscode.env.openExternal(vscode.Uri.parse(
|
||||||
`https://portal.azure.com/#resource/subscriptions/${r.subscriptionId}/resourceGroups/${r.resourceGroupName}/providers/Microsoft.AzureData/postgresInstances/${r.instanceName}`));
|
`https://portal.azure.com/#resource/subscriptions/${r.subscriptionId}/resourceGroups/${r.resourceGroupName}/providers/Microsoft.AzureData/postgresInstances/${r.instanceName}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO implement click
|
|
||||||
const feedbackButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
|
||||||
label: loc.feedback,
|
|
||||||
iconPath: IconPathHelper.heart
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
{ component: newDatabaseButton },
|
{ component: newDatabaseButton },
|
||||||
{ component: resetPasswordButton },
|
{ component: resetPasswordButton },
|
||||||
{ component: deleteButton, toolbarSeparatorAfter: true },
|
{ component: deleteButton },
|
||||||
{ component: openInAzurePortalButton },
|
{ component: refreshButton, toolbarSeparatorAfter: true },
|
||||||
{ component: feedbackButton }
|
{ component: openInAzurePortalButton }
|
||||||
]).component();
|
]).component();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private refreshProperties() {
|
||||||
|
const registration = this._controllerModel.registration('postgresInstances', this._postgresModel.namespace(), this._postgresModel.name());
|
||||||
|
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint();
|
||||||
|
|
||||||
|
this.properties!.propertyItems = [
|
||||||
|
{ displayName: loc.name, value: this._postgresModel.name() },
|
||||||
|
{ displayName: loc.coordinatorEndpoint, value: `postgresql://postgres:${this._postgresModel.password()}@${endpoint.ip}:${endpoint.port}` },
|
||||||
|
{ displayName: loc.status, value: this._postgresModel.service()?.status?.state ?? '' },
|
||||||
|
{ displayName: loc.postgresAdminUsername, value: 'postgres' },
|
||||||
|
{ displayName: loc.dataController, value: this._controllerModel?.namespace() ?? '' },
|
||||||
|
{ displayName: loc.nodeConfiguration, value: this._postgresModel.configuration() },
|
||||||
|
{ displayName: loc.subscriptionId, value: registration?.subscriptionId ?? '' },
|
||||||
|
{ displayName: loc.postgresVersion, value: this._postgresModel.service()?.spec.engine.version?.toString() ?? '' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.propertiesLoading!.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshEndpoints() {
|
||||||
|
const kibanaQuery = `kubernetes_namespace:"${this._postgresModel.namespace()}" and cluster_name:"${this._postgresModel.name()}"`;
|
||||||
|
const kibanaUrl = `${this._controllerModel.endpoint('logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
|
||||||
|
this.kibanaLink!.label = kibanaUrl;
|
||||||
|
this.kibanaLink!.url = kibanaUrl;
|
||||||
|
|
||||||
|
const grafanaUrl = `${this._controllerModel.endpoint('metricsui')?.endpoint}/d/postgres-metrics?var-Namespace=${this._postgresModel.namespace()}&var-Name=${this._postgresModel.name()}`;
|
||||||
|
this.grafanaLink!.label = grafanaUrl;
|
||||||
|
this.grafanaLink!.url = grafanaUrl;
|
||||||
|
|
||||||
|
this.kibanaLoading!.loading = false;
|
||||||
|
this.grafanaLoading!.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshNodes() {
|
||||||
|
const nodes = this._postgresModel.numNodes();
|
||||||
|
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint();
|
||||||
|
|
||||||
|
const data: any[][] = [];
|
||||||
|
for (let i = 0; i < nodes; i++) {
|
||||||
|
data.push([
|
||||||
|
`${this._postgresModel.name()}-${i}`,
|
||||||
|
i === 0 ? loc.coordinatorEndpoint : loc.worker,
|
||||||
|
i === 0 ? `${endpoint.ip}:${endpoint.port}` :
|
||||||
|
`${this._postgresModel.name()}-${i}.${this._postgresModel.name()}-svc.${this._postgresModel.namespace()}.svc.cluster.local`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodesTable!.data = data;
|
||||||
|
this.nodesTableLoading!.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,21 @@ import * as vscode from 'vscode';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { PostgresDashboardPage } from './postgresDashboardPage';
|
import { KeyValueContainer, InputKeyValue, LinkKeyValue, TextKeyValue } from '../../components/keyValueContainer';
|
||||||
import { KeyValueContainer, KeyValue, InputKeyValue, LinkKeyValue, TextKeyValue } from '../../components/keyValueContainer';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export class PostgresPropertiesPage extends DashboardPage {
|
||||||
|
private keyValueContainer?: KeyValueContainer;
|
||||||
|
|
||||||
|
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
|
super(modelView);
|
||||||
|
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
|
||||||
|
this._postgresModel.onPasswordUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
|
||||||
|
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
|
||||||
|
}
|
||||||
|
|
||||||
export class PostgresPropertiesPage extends PostgresDashboardPage {
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
return loc.properties;
|
return loc.properties;
|
||||||
}
|
}
|
||||||
@@ -34,27 +44,52 @@ export class PostgresPropertiesPage extends PostgresDashboardPage {
|
|||||||
CSSStyles: { ...cssStyles.title, 'margin-bottom': '25px' }
|
CSSStyles: { ...cssStyles.title, 'margin-bottom': '25px' }
|
||||||
}).component());
|
}).component());
|
||||||
|
|
||||||
const endpoint: { ip?: string, port?: number } = this.databaseModel.endpoint();
|
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
|
||||||
const connectionString = `postgresql://postgres:${this.databaseModel.password()}@${endpoint.ip}:${endpoint.port}`;
|
content.addItem(this.keyValueContainer.container);
|
||||||
const registration = this.controllerModel.registration('postgresInstances', this.databaseModel.namespace(), this.databaseModel.name());
|
this.initialized = true;
|
||||||
|
|
||||||
const pairs: KeyValue[] = [
|
|
||||||
new InputKeyValue(loc.coordinatorEndpoint, connectionString),
|
|
||||||
new InputKeyValue(loc.postgresAdminUsername, 'postgres'),
|
|
||||||
new TextKeyValue(loc.status, this.databaseModel.service().status?.state ?? 'Unknown'),
|
|
||||||
new LinkKeyValue(loc.dataController, this.controllerModel.namespace(), _ => vscode.window.showInformationMessage('goto data controller')),
|
|
||||||
new LinkKeyValue(loc.nodeConfiguration, this.databaseModel.configuration(), _ => vscode.window.showInformationMessage('goto configuration')),
|
|
||||||
new TextKeyValue(loc.postgresVersion, this.databaseModel.service().spec.engine.version?.toString() ?? ''),
|
|
||||||
new TextKeyValue(loc.resourceGroup, registration?.resourceGroupName ?? ''),
|
|
||||||
new TextKeyValue(loc.subscriptionId, registration?.subscriptionId ?? '')
|
|
||||||
];
|
|
||||||
|
|
||||||
const keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, pairs);
|
|
||||||
content.addItem(keyValueContainer.container);
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
return this.modelView.modelBuilder.toolbarContainer().component();
|
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
label: loc.refresh,
|
||||||
|
iconPath: IconPathHelper.refresh
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
refreshButton.onDidClick(async () => {
|
||||||
|
refreshButton.enabled = false;
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
this._postgresModel.refresh(),
|
||||||
|
this._controllerModel.refresh()
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
refreshButton.enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
|
{ component: refreshButton }
|
||||||
|
]).component();
|
||||||
|
}
|
||||||
|
|
||||||
|
private refresh() {
|
||||||
|
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint();
|
||||||
|
const connectionString = `postgresql://postgres:${this._postgresModel.password()}@${endpoint.ip}:${endpoint.port}`;
|
||||||
|
const registration = this._controllerModel.registration('postgresInstances', this._postgresModel.namespace(), this._postgresModel.name());
|
||||||
|
|
||||||
|
this.keyValueContainer?.refresh([
|
||||||
|
new InputKeyValue(loc.coordinatorEndpoint, connectionString),
|
||||||
|
new InputKeyValue(loc.postgresAdminUsername, 'postgres'),
|
||||||
|
new TextKeyValue(loc.status, this._postgresModel.service()?.status?.state ?? 'Unknown'),
|
||||||
|
new LinkKeyValue(loc.dataController, this._controllerModel.namespace() ?? '', _ => vscode.window.showInformationMessage('TODO: Go to data controller')),
|
||||||
|
new LinkKeyValue(loc.nodeConfiguration, this._postgresModel.configuration(), _ => vscode.window.showInformationMessage('TODO: Go to configuration')),
|
||||||
|
new TextKeyValue(loc.postgresVersion, this._postgresModel.service()?.spec.engine.version?.toString() ?? ''),
|
||||||
|
new TextKeyValue(loc.resourceGroup, registration?.resourceGroupName ?? ''),
|
||||||
|
new TextKeyValue(loc.subscriptionId, registration?.subscriptionId ?? '')
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user