mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 17:23:40 -05:00
Fixed PG update (#20484)
Co-authored-by: Candice Ye <canye@microsoft.com>
This commit is contained in:
@@ -50,11 +50,6 @@ export class PostgresModel extends ResourceModel {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
/** Returns the major version of Postgres */
|
||||
public get engineVersion(): string | undefined {
|
||||
return this._config?.spec.engine.version;
|
||||
}
|
||||
|
||||
/** Returns the IP address and port of Postgres */
|
||||
public get endpoint(): { ip: string, port: string } | undefined {
|
||||
return this._config?.status.primaryEndpoint
|
||||
@@ -76,12 +71,7 @@ export class PostgresModel extends ResourceModel {
|
||||
const logStorage = this._config.spec.storage?.logs?.volumes?.[0]?.size;
|
||||
const backupsStorage = this._config.spec.storage?.backups?.volumes?.[0]?.size;
|
||||
|
||||
// scale.shards was renamed to scale.workers. Check both for backwards compatibility.
|
||||
const scale = this._config.spec.scale;
|
||||
const nodes = (scale?.workers ?? scale?.shards ?? 0) + 1; // An extra node for the coordinator
|
||||
|
||||
let configuration: string[] = [];
|
||||
configuration.push(`${nodes} ${nodes > 1 ? loc.nodes : loc.node}`);
|
||||
|
||||
// Prefer limits if they're provided, otherwise use requests if they're provided
|
||||
if (cpuLimit || cpuRequest) {
|
||||
@@ -162,12 +152,6 @@ export class PostgresModel extends ResourceModel {
|
||||
|
||||
await this.createCoordinatorEngineSettings(provider, ownerUri, skippedEngineSettings);
|
||||
|
||||
const scale = this._config?.spec.scale;
|
||||
const nodes = (scale?.workers ?? scale?.shards ?? 0);
|
||||
if (nodes !== 0) {
|
||||
await this.createWorkerEngineSettings(provider, ownerUri, skippedEngineSettings);
|
||||
}
|
||||
|
||||
this.engineSettingsLastUpdated = new Date();
|
||||
this._engineSettingsPromise.resolve();
|
||||
} catch (err) {
|
||||
@@ -202,39 +186,6 @@ export class PostgresModel extends ResourceModel {
|
||||
|
||||
}
|
||||
|
||||
private async createWorkerEngineSettings(provider: azdata.QueryProvider, ownerUri: string, skip: String[]): Promise<void> {
|
||||
|
||||
const engineSettingsWorker = await provider.runQueryAndReturn(ownerUri,
|
||||
`with settings as (select nodename, success, result from run_command_on_workers('select json_agg(pg_settings) from pg_settings') order by success desc, nodename asc)
|
||||
select * from settings limit case when exists(select 1 from settings where success) then 1 end`);
|
||||
|
||||
if (engineSettingsWorker.rows[0][1].displayValue === 'False') {
|
||||
let errorString = engineSettingsWorker.rows.map(row => row[2].displayValue);
|
||||
throw new Error(errorString.join('\n'));
|
||||
}
|
||||
|
||||
let engineSettingsWorkerJSON = JSON.parse(engineSettingsWorker.rows[0][2].displayValue);
|
||||
this.workerNodesEngineSettings = [];
|
||||
|
||||
for (let i = 0; i < engineSettingsWorkerJSON.length; i++) {
|
||||
let rowValues = engineSettingsWorkerJSON[i];
|
||||
let name = rowValues.name;
|
||||
if (!skip.includes(name!)) {
|
||||
let result: EngineSettingsModel = {
|
||||
parameterName: name,
|
||||
value: rowValues.setting,
|
||||
description: rowValues.short_desc,
|
||||
min: rowValues.min_val,
|
||||
max: rowValues.max_val,
|
||||
options: rowValues.enumvals,
|
||||
type: rowValues.vartype
|
||||
};
|
||||
|
||||
this.workerNodesEngineSettings.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected createConnectionProfile(): azdata.IConnectionProfile {
|
||||
const ipAndPort = parseIpAndPort(this.config?.status.primaryEndpoint || '');
|
||||
|
||||
@@ -18,19 +18,19 @@ export type RoleSpecifier = {
|
||||
|
||||
export type ConfigurationSpecModel = {
|
||||
workers?: number,
|
||||
coresRequest?: RoleSpecifier,
|
||||
coresLimit?: RoleSpecifier,
|
||||
memoryRequest?: RoleSpecifier,
|
||||
memoryLimit?: RoleSpecifier
|
||||
coresRequest?: string,
|
||||
coresLimit?: string,
|
||||
memoryRequest?: string,
|
||||
memoryLimit?: string
|
||||
};
|
||||
|
||||
export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
private userInputContainer!: azdata.DivContainer;
|
||||
|
||||
private coordinatorCoresLimitBox!: azdata.InputBoxComponent;
|
||||
private coordinatorCoresRequestBox!: azdata.InputBoxComponent;
|
||||
private coordinatorMemoryLimitBox!: azdata.InputBoxComponent;
|
||||
private coordinatorMemoryRequestBox!: azdata.InputBoxComponent;
|
||||
private coresLimitBox!: azdata.InputBoxComponent;
|
||||
private coresRequestBox!: azdata.InputBoxComponent;
|
||||
private memoryLimitBox!: azdata.InputBoxComponent;
|
||||
private memoryRequestBox!: azdata.InputBoxComponent;
|
||||
|
||||
private currentConfiguration: ConfigurationSpecModel = {};
|
||||
private saveArgs: ConfigurationSpecModel = {};
|
||||
@@ -154,10 +154,10 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
await this._azApi.az.postgres.serverarc.update(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
coresRequest: this.schedulingParamsToEdit(this.saveArgs.coresRequest!),
|
||||
coresLimit: this.schedulingParamsToEdit(this.saveArgs.coresLimit!),
|
||||
memoryRequest: this.schedulingParamsToEdit(this.saveArgs.memoryRequest!),
|
||||
memoryLimit: this.schedulingParamsToEdit(this.saveArgs.memoryLimit!)
|
||||
coresRequest: this.saveArgs.coresRequest!,
|
||||
coresLimit: this.saveArgs.coresLimit!,
|
||||
memoryRequest: this.saveArgs.memoryRequest!,
|
||||
memoryLimit: this.saveArgs.memoryLimit!
|
||||
},
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
@@ -195,10 +195,10 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
this.discardButton.onDidClick(async () => {
|
||||
this.discardButton.enabled = false;
|
||||
try {
|
||||
this.coordinatorCoresRequestBox.value = this.currentConfiguration.coresRequest!.coordinator;
|
||||
this.coordinatorCoresLimitBox.value = this.currentConfiguration.coresLimit!.coordinator;
|
||||
this.coordinatorMemoryRequestBox.value = this.currentConfiguration.memoryRequest!.coordinator;
|
||||
this.coordinatorMemoryLimitBox.value = this.currentConfiguration.memoryLimit!.coordinator;
|
||||
this.coresRequestBox.value = this.currentConfiguration.coresRequest!;
|
||||
this.coresLimitBox.value = this.currentConfiguration.coresLimit!;
|
||||
this.memoryRequestBox.value = this.currentConfiguration.memoryRequest!;
|
||||
this.memoryLimitBox.value = this.currentConfiguration.memoryLimit!;
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
||||
} finally {
|
||||
@@ -212,18 +212,9 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
]).component();
|
||||
}
|
||||
|
||||
private schedulingParamsToEdit(arg: RoleSpecifier): string | undefined {
|
||||
// A comma-separated list of roles with values can be specified in format <role>=<value>.
|
||||
if (arg.coordinator) {
|
||||
return `"${arg.coordinator}"`;
|
||||
} else {
|
||||
return arg.coordinator ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeConfigurationBoxes(): void {
|
||||
// Coordinator node cores request
|
||||
this.coordinatorCoresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
// Cores request
|
||||
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 1,
|
||||
inputType: 'number',
|
||||
@@ -232,40 +223,34 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.coordinatorCoresRequestBox.onTextChanged(() => {
|
||||
if (!(this.saveValueToEdit(this.coordinatorCoresRequestBox, this.currentConfiguration.coresRequest!.coordinator!))) {
|
||||
this.saveArgs.coresRequest!.coordinator = undefined;
|
||||
} else if (this.coordinatorCoresRequestBox.value === '') {
|
||||
this.saveArgs.coresRequest!.coordinator = 'c=';
|
||||
this.coresRequestBox.onTextChanged(() => {
|
||||
if (!(this.handleOnTextChanged(this.coresRequestBox!))) {
|
||||
this.saveArgs.coresRequest = undefined;
|
||||
} else {
|
||||
this.saveArgs.coresRequest!.coordinator = `c=${this.coordinatorCoresRequestBox.value}`;
|
||||
this.saveArgs.coresRequest = this.coresRequestBox!.value;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Coordinator node cores limit
|
||||
this.coordinatorCoresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
// Cores limit
|
||||
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 1,
|
||||
inputType: 'number',
|
||||
placeHolder: loc.loading,
|
||||
ariaLabel: loc.coresLimit
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.coordinatorCoresLimitBox.onTextChanged(() => {
|
||||
if (!(this.saveValueToEdit(this.coordinatorCoresLimitBox, this.currentConfiguration.coresLimit!.coordinator!))) {
|
||||
this.saveArgs.coresLimit!.coordinator = undefined;
|
||||
} else if (this.coordinatorCoresLimitBox.value === '') {
|
||||
this.saveArgs.coresLimit!.coordinator = 'c=';
|
||||
this.coresLimitBox.onTextChanged(() => {
|
||||
if (!(this.handleOnTextChanged(this.coresLimitBox!))) {
|
||||
this.saveArgs.coresLimit = undefined;
|
||||
} else {
|
||||
this.saveArgs.coresLimit!.coordinator = `c=${this.coordinatorCoresLimitBox.value}`;
|
||||
this.saveArgs.coresLimit = this.coresLimitBox!.value;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Coordinator node memory request
|
||||
this.coordinatorMemoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
// Memory request
|
||||
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 0.25,
|
||||
inputType: 'number',
|
||||
@@ -274,19 +259,17 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.coordinatorMemoryRequestBox.onTextChanged(() => {
|
||||
if (!(this.saveValueToEdit(this.coordinatorMemoryRequestBox, this.currentConfiguration.memoryRequest!.coordinator!))) {
|
||||
this.saveArgs.memoryRequest!.coordinator = undefined;
|
||||
} else if (this.coordinatorMemoryRequestBox.value === '') {
|
||||
this.saveArgs.memoryRequest!.coordinator = 'c=';
|
||||
this.memoryRequestBox.onTextChanged(() => {
|
||||
if (!(this.handleOnTextChanged(this.memoryRequestBox!))) {
|
||||
this.saveArgs.memoryRequest = undefined;
|
||||
} else {
|
||||
this.saveArgs.memoryRequest!.coordinator = `c=${this.coordinatorMemoryRequestBox.value}Gi`;
|
||||
this.saveArgs.memoryRequest = this.memoryRequestBox!.value + 'Gi';
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Coordinator node memory limit
|
||||
this.coordinatorMemoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
// Memory limit
|
||||
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 0.25,
|
||||
inputType: 'number',
|
||||
@@ -295,13 +278,11 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.coordinatorMemoryLimitBox.onTextChanged(() => {
|
||||
if (!(this.saveValueToEdit(this.coordinatorMemoryLimitBox, this.currentConfiguration.memoryLimit!.coordinator!))) {
|
||||
this.saveArgs.memoryLimit!.coordinator = undefined;
|
||||
} else if (this.coordinatorMemoryLimitBox.value === '') {
|
||||
this.saveArgs.memoryLimit!.coordinator = 'c=';
|
||||
this.memoryLimitBox.onTextChanged(() => {
|
||||
if (!(this.handleOnTextChanged(this.memoryLimitBox!))) {
|
||||
this.saveArgs.memoryLimit = undefined;
|
||||
} else {
|
||||
this.saveArgs.memoryLimit!.coordinator = `c=${this.coordinatorMemoryLimitBox.value}Gi`;
|
||||
this.saveArgs.memoryLimit = this.memoryLimitBox!.value + 'Gi';
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -309,18 +290,16 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
|
||||
private createUserInputWorkerSection(): azdata.Component[] {
|
||||
if (this._postgresModel.configLastUpdated) {
|
||||
this.refreshCoresRequest();
|
||||
this.refreshCoresLimit();
|
||||
this.refreshMemoryRequest();
|
||||
this.refreshMemoryLimit();
|
||||
this.editCores();
|
||||
this.editMemory();
|
||||
}
|
||||
|
||||
return [
|
||||
this.createCoresMemorySection(loc.configuration),
|
||||
this.createConfigurationSectionContainer(loc.coresRequest, this.coordinatorCoresRequestBox),
|
||||
this.createConfigurationSectionContainer(loc.coresLimit, this.coordinatorCoresLimitBox),
|
||||
this.createConfigurationSectionContainer(loc.memoryRequest, this.coordinatorMemoryRequestBox),
|
||||
this.createConfigurationSectionContainer(loc.memoryLimit, this.coordinatorMemoryLimitBox)
|
||||
this.createConfigurationSectionContainer(loc.coresRequest, this.coresRequestBox),
|
||||
this.createConfigurationSectionContainer(loc.coresLimit, this.coresLimitBox),
|
||||
this.createConfigurationSectionContainer(loc.memoryRequest, this.memoryRequestBox),
|
||||
this.createConfigurationSectionContainer(loc.memoryLimit, this.memoryLimitBox)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -351,30 +330,24 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
return flexContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that determines if an input box's value should be considered or not.
|
||||
* Triggers the save and discard buttons to become enabled depending on the value change.
|
||||
*
|
||||
* If new value is the same as value found in config, do not consider this new value for editing.
|
||||
* If new value is invalid, do not consider this new value for editing and enable discard button.
|
||||
* If value is valid and not equal to original value found in config, add this new value to be considered
|
||||
* for editing and enable save/discard buttons.
|
||||
*
|
||||
* @param component The input box that had an onTextChanged event triggered.
|
||||
* @param originalValue The value that was contained in the input box before user interaction.
|
||||
* @return A boolean that reads true if the new value should be taken in for editing or not.
|
||||
*/
|
||||
private saveValueToEdit(component: azdata.InputBoxComponent, originalValue: string): boolean {
|
||||
if (component.value === originalValue) {
|
||||
private handleOnTextChanged(component: azdata.InputBoxComponent): boolean {
|
||||
if ((!component.value)) {
|
||||
// if there is no text found in the inputbox component return false
|
||||
return false;
|
||||
} else if ((!component.valid)) {
|
||||
this.discardButton.enabled = true;
|
||||
// if value given by user is not valid enable discard button for user
|
||||
// to clear all inputs and return false
|
||||
this.discardButton!.enabled = true;
|
||||
return false;
|
||||
} else {
|
||||
this.saveButton.enabled = true;
|
||||
this.discardButton.enabled = true;
|
||||
// if a valid value has been entered into the input box, enable save and discard buttons
|
||||
// so that user could choose to either edit instance or clear all inputs
|
||||
// return true
|
||||
this.saveButton!.enabled = true;
|
||||
this.discardButton!.enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private createCoresMemorySection(title: string): azdata.DivContainer {
|
||||
@@ -401,88 +374,62 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
return configurationSection;
|
||||
}
|
||||
|
||||
private refreshCoresRequest(): void {
|
||||
// Coordinator
|
||||
let coordinatorCR = this._postgresModel.config?.spec.scheduling?.roles?.coordinator?.resources?.requests?.cpu ?? this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.cpu;
|
||||
if (!coordinatorCR) {
|
||||
coordinatorCR = '';
|
||||
private editCores(): void {
|
||||
let currentCPUSize = this._postgresModel.config?.spec?.scheduling?.default?.resources?.requests?.cpu;
|
||||
|
||||
if (!currentCPUSize) {
|
||||
currentCPUSize = '';
|
||||
}
|
||||
|
||||
this.coordinatorCoresRequestBox.placeHolder = '';
|
||||
this.coordinatorCoresRequestBox.value = coordinatorCR;
|
||||
this.coresRequestBox!.value = currentCPUSize;
|
||||
this.coresRequestBox!.placeHolder = '';
|
||||
|
||||
// Update saved current configuration
|
||||
this.currentConfiguration.coresRequest = {
|
||||
coordinator: coordinatorCR
|
||||
};
|
||||
this.saveArgs.coresRequest = undefined;
|
||||
|
||||
// Discard argument changes
|
||||
this.saveArgs.coresRequest = {};
|
||||
currentCPUSize = this._postgresModel.config?.spec?.scheduling?.default?.resources?.limits?.cpu;
|
||||
|
||||
if (!currentCPUSize) {
|
||||
currentCPUSize = '';
|
||||
}
|
||||
|
||||
this.coresLimitBox!.placeHolder = currentCPUSize;
|
||||
this.coresLimitBox!.value = '';
|
||||
|
||||
this.saveArgs.coresLimit = undefined;
|
||||
}
|
||||
|
||||
private refreshCoresLimit(): void {
|
||||
// Coordinator
|
||||
let coordinatorCL = this._postgresModel.config?.spec.scheduling?.roles?.coordinator?.resources?.limits?.cpu ?? this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.cpu;
|
||||
if (!coordinatorCL) {
|
||||
coordinatorCL = '';
|
||||
private editMemory(): void {
|
||||
let currentMemSizeConversion: string;
|
||||
let currentMemorySize = this._postgresModel.config?.spec?.scheduling?.default?.resources?.requests?.memory;
|
||||
|
||||
if (!currentMemorySize) {
|
||||
currentMemSizeConversion = '';
|
||||
} else {
|
||||
currentMemSizeConversion = convertToGibibyteString(currentMemorySize);
|
||||
}
|
||||
|
||||
this.coordinatorCoresLimitBox.placeHolder = '';
|
||||
this.coordinatorCoresLimitBox.value = coordinatorCL;
|
||||
this.memoryRequestBox!.placeHolder = currentMemSizeConversion!;
|
||||
this.memoryRequestBox!.value = '';
|
||||
|
||||
// Update saved current configuration
|
||||
this.currentConfiguration.coresLimit = {
|
||||
coordinator: coordinatorCL
|
||||
};
|
||||
this.saveArgs.memoryRequest = undefined;
|
||||
|
||||
// Discard argument changes
|
||||
this.saveArgs.coresLimit = {};
|
||||
}
|
||||
currentMemorySize = this._postgresModel.config?.spec?.scheduling?.default?.resources?.limits?.memory;
|
||||
|
||||
private refreshMemoryRequest(): void {
|
||||
// Coordinator
|
||||
let currentCoordinatorMemoryRequest = this._postgresModel.config?.spec.scheduling?.roles?.coordinator?.resources?.requests?.memory ?? this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.memory;
|
||||
let coordinatorMR = '';
|
||||
if (currentCoordinatorMemoryRequest) {
|
||||
coordinatorMR = convertToGibibyteString(currentCoordinatorMemoryRequest);
|
||||
if (!currentMemorySize) {
|
||||
currentMemSizeConversion = '';
|
||||
} else {
|
||||
currentMemSizeConversion = convertToGibibyteString(currentMemorySize);
|
||||
}
|
||||
|
||||
this.coordinatorMemoryRequestBox.placeHolder = '';
|
||||
this.coordinatorMemoryRequestBox.value = coordinatorMR;
|
||||
this.memoryLimitBox!.placeHolder = currentMemSizeConversion!;
|
||||
this.memoryLimitBox!.value = '';
|
||||
|
||||
// Update saved current configuration
|
||||
this.currentConfiguration.memoryRequest = {
|
||||
coordinator: coordinatorMR
|
||||
};
|
||||
|
||||
// Discard argument changes
|
||||
this.saveArgs.memoryRequest = {};
|
||||
}
|
||||
|
||||
private refreshMemoryLimit(): void {
|
||||
// Coordinator
|
||||
let currentCoordinatorMemoryLimit = this._postgresModel.config?.spec.scheduling?.roles?.coordinator?.resources?.limits?.memory ?? this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.memory;
|
||||
let coordinatorML = '';
|
||||
if (currentCoordinatorMemoryLimit) {
|
||||
coordinatorML = convertToGibibyteString(currentCoordinatorMemoryLimit);
|
||||
}
|
||||
|
||||
this.coordinatorMemoryLimitBox.placeHolder = '';
|
||||
this.coordinatorMemoryLimitBox.value = coordinatorML;
|
||||
|
||||
// Update saved current configuration
|
||||
this.currentConfiguration.memoryLimit = {
|
||||
coordinator: coordinatorML
|
||||
};
|
||||
|
||||
// Discard argument changes
|
||||
this.saveArgs.memoryLimit = {};
|
||||
this.saveArgs.memoryLimit = undefined;
|
||||
}
|
||||
|
||||
private handleServiceUpdated(): void {
|
||||
this.refreshCoresRequest();
|
||||
this.refreshCoresLimit();
|
||||
this.refreshMemoryRequest();
|
||||
this.refreshMemoryLimit();
|
||||
this.editCores();
|
||||
this.editMemory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +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 * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper } from '../../../constants';
|
||||
import { PostgresParametersPage } from './postgresParameters';
|
||||
import { PostgresModel, EngineSettingsModel } from '../../../models/postgresModel';
|
||||
|
||||
export class PostgresCoordinatorNodeParametersPage extends PostgresParametersPage {
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard, postgresModel);
|
||||
}
|
||||
|
||||
protected get title(): string {
|
||||
return loc.coordinatorNodeParameters;
|
||||
}
|
||||
|
||||
protected get id(): string {
|
||||
return 'postgres-coordinator-node-parameters';
|
||||
}
|
||||
|
||||
protected get icon(): { dark: string; light: string; } {
|
||||
return IconPathHelper.gearGray;
|
||||
}
|
||||
|
||||
protected get description(): string {
|
||||
return loc.coordinatorNodeParametersDescription;
|
||||
}
|
||||
|
||||
protected get engineSettings(): EngineSettingsModel[] {
|
||||
return this._postgresModel.coordinatorNodeEngineSettings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,291 +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 vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
import { PostgresModel } from '../../../models/postgresModel';
|
||||
import { AddPGExtensionsDialog } from '../../dialogs/addPGExtensionsDialog';
|
||||
|
||||
export class PostgresExtensionsPage extends DashboardPage {
|
||||
|
||||
private extensionNames: string[] = [];
|
||||
private droppedExtensions: string[] = [];
|
||||
private extensionsTable!: azdata.DeclarativeTableComponent;
|
||||
private extensionsLoading!: azdata.LoadingComponent;
|
||||
private addExtensionsButton!: azdata.ButtonComponent;
|
||||
private dropExtensionsButton!: azdata.ButtonComponent;
|
||||
private extensionsLink!: azdata.HyperlinkComponent;
|
||||
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.disposables.push(
|
||||
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated())));
|
||||
}
|
||||
|
||||
protected get title(): string {
|
||||
return loc.preLoadedExtensions;
|
||||
}
|
||||
|
||||
protected get id(): string {
|
||||
return 'postgres-extensions';
|
||||
}
|
||||
|
||||
protected get icon(): { dark: string; light: string; } {
|
||||
return IconPathHelper.extensions;
|
||||
}
|
||||
|
||||
protected get container(): azdata.Component {
|
||||
const root = this.modelView.modelBuilder.divContainer().component();
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: this.title,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.extensionsDescription,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component());
|
||||
|
||||
const info = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.extensionsFunction,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
this.extensionsLink = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnMore,
|
||||
url: 'https://www.postgresql.org/docs/12/external-extensions.html',
|
||||
}).component();
|
||||
|
||||
const infoAndLink = this.modelView.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).component();
|
||||
infoAndLink.addItem(info, { CSSStyles: { 'margin-right': '5px' } });
|
||||
infoAndLink.addItem(this.extensionsLink);
|
||||
content.addItem(infoAndLink, { CSSStyles: { 'margin-bottom': '15px', 'margin-top': '25px' } });
|
||||
|
||||
this.extensionsTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
ariaLabel: loc.extensionsTableLabel,
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '20px',
|
||||
isReadOnly: true,
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.extensionName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '100%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
dataValues: []
|
||||
}).component();
|
||||
|
||||
this.extensionsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.extensionsTable).withProps({
|
||||
loading: !this._postgresModel.configLastUpdated,
|
||||
loadingText: loc.extensionsTableLoading,
|
||||
loadingCompletedText: loc.extensionsTableLoadingComplete
|
||||
}).component();
|
||||
|
||||
content.addItem(this.extensionsLoading, { CSSStyles: cssStyles.text });
|
||||
|
||||
this.initialized = true;
|
||||
return root;
|
||||
}
|
||||
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Add extensions
|
||||
this.addExtensionsButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.loadExtensions,
|
||||
ariaLabel: loc.loadExtensions,
|
||||
iconPath: IconPathHelper.add
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.addExtensionsButton.onDidClick(async () => {
|
||||
const addExtDialog = new AddPGExtensionsDialog(this._postgresModel);
|
||||
addExtDialog.showDialog(loc.loadExtensions);
|
||||
|
||||
let extArg = await addExtDialog.waitForClose();
|
||||
if (extArg) {
|
||||
try {
|
||||
this.addExtensionsButton.enabled = false;
|
||||
this.dropExtensionsButton.enabled = false;
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
|
||||
await this._azApi.az.postgres.serverarc.update(
|
||||
this._postgresModel.info.name,
|
||||
{},
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
|
||||
try {
|
||||
await this._postgresModel.refresh();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
vscode.window.showInformationMessage(loc.extensionsAdded(extArg));
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
} finally {
|
||||
this.addExtensionsButton.enabled = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Drop extensions
|
||||
this.dropExtensionsButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.unloadExtensions,
|
||||
ariaLabel: loc.unloadExtensions,
|
||||
iconPath: IconPathHelper.delete,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.dropExtensionsButton.onDidClick(async () => {
|
||||
try {
|
||||
this.addExtensionsButton.enabled = false;
|
||||
this.dropExtensionsButton.enabled = false;
|
||||
await this.dropExtension();
|
||||
|
||||
try {
|
||||
await this._postgresModel.refresh();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage(loc.extensionsDropped(this.droppedExtensions.join()));
|
||||
this.droppedExtensions = [];
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
} finally {
|
||||
this.addExtensionsButton.enabled = true;
|
||||
}
|
||||
}));
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||
{ component: this.addExtensionsButton },
|
||||
{ component: this.dropExtensionsButton }
|
||||
]).component();
|
||||
}
|
||||
|
||||
private refreshExtensionsTable(): void {
|
||||
let extensions = this._postgresModel.config!.spec.engine.extensions;
|
||||
let extenesionFinalData: azdata.DeclarativeTableCellValue[][] = [];
|
||||
let extensionBasicData: (string | azdata.CheckBoxComponent | azdata.ImageComponent)[][] = [];
|
||||
|
||||
if (extensions) {
|
||||
extensionBasicData = extensions.map(e => {
|
||||
this.extensionNames.push(e.name);
|
||||
return [this.createDropCheckBox(e.name), e.name];
|
||||
});
|
||||
} else {
|
||||
extensionBasicData = [[this.modelView.modelBuilder.image().component(), loc.noExtensions]];
|
||||
}
|
||||
|
||||
extenesionFinalData = extensionBasicData.map(e => {
|
||||
return e.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
|
||||
this.extensionsTable.setDataValues(extenesionFinalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates checkboxes to select which extensions to drop.
|
||||
* Allows user to drop multiple extension.
|
||||
* @param name name of postgres extension the checkbox will be tied to.
|
||||
*/
|
||||
public createDropCheckBox(name: string): azdata.CheckBoxComponent {
|
||||
// Can select extensions to drop
|
||||
let checkBox = this.modelView.modelBuilder.checkBox().withProps({
|
||||
ariaLabel: loc.unloadExtensions,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
if (name === 'citus') {
|
||||
checkBox.enabled = false;
|
||||
}
|
||||
|
||||
this.disposables.push(
|
||||
checkBox.onChanged(() => {
|
||||
if (checkBox.checked) {
|
||||
this.droppedExtensions.push(name);
|
||||
this.dropExtensionsButton.focus();
|
||||
} else {
|
||||
let index = this.droppedExtensions.indexOf(name, 0);
|
||||
this.droppedExtensions.splice(index, 1);
|
||||
}
|
||||
this.dropExtensionsButton.enabled = this.droppedExtensions.length ? true : false;
|
||||
})
|
||||
);
|
||||
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls edit on postgres extensions with an updated extensions list.
|
||||
*/
|
||||
public async dropExtension(): Promise<void> {
|
||||
this.droppedExtensions.forEach(d => {
|
||||
let index = this.extensionNames.indexOf(d, 0);
|
||||
this.extensionNames.splice(index, 1);
|
||||
});
|
||||
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
await this._azApi.az.postgres.serverarc.update(
|
||||
this._postgresModel.info.name,
|
||||
{},
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private handleConfigUpdated(): void {
|
||||
if (this._postgresModel.config) {
|
||||
this.extensionsLoading.loading = false;
|
||||
this.extensionsLink.url = `https://www.postgresql.org/docs/${this._postgresModel.engineVersion}/external-extensions.html`;
|
||||
this.extensionNames = [];
|
||||
this.refreshExtensionsTable();
|
||||
this.addExtensionsButton.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,649 +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 vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { UserCancelledError } from '../../../common/api';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
import { EngineSettingsModel, PostgresModel } from '../../../models/postgresModel';
|
||||
import { debounce, instanceOfCheckBox } from '../../../common/utils';
|
||||
|
||||
export type ParametersModel = {
|
||||
parameterName: string,
|
||||
originalValue: string,
|
||||
valueComponent: azdata.TextComponent | azdata.DropDownComponent | azdata.CheckBoxComponent,
|
||||
information?: azdata.ButtonComponent,
|
||||
description: string,
|
||||
resetButton: azdata.ButtonComponent
|
||||
};
|
||||
|
||||
export abstract class PostgresParametersPage extends DashboardPage {
|
||||
private searchBox!: azdata.InputBoxComponent;
|
||||
protected _parametersTable!: azdata.DeclarativeTableComponent;
|
||||
private parameterContainer!: azdata.DivContainer;
|
||||
private parametersTableLoading?: azdata.LoadingComponent;
|
||||
|
||||
private discardButton!: azdata.ButtonComponent;
|
||||
private saveButton!: azdata.ButtonComponent;
|
||||
private resetAllButton!: azdata.ButtonComponent;
|
||||
private connectToServerButton?: azdata.ButtonComponent;
|
||||
|
||||
protected _parameters: ParametersModel[] = [];
|
||||
private changedComponentValues: Set<string> = new Set();
|
||||
private parameterUpdates: Map<string, string> = new Map();
|
||||
|
||||
protected readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, protected _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.initializeSearchBox();
|
||||
|
||||
this.disposables.push(
|
||||
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated()))
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract get description(): string;
|
||||
|
||||
protected abstract get engineSettings(): EngineSettingsModel[];
|
||||
|
||||
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().withProps({
|
||||
value: this.title,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: this.description,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnAboutNodeParameters,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale'
|
||||
}).component(), { CSSStyles: { 'margin-bottom': '20px' } });
|
||||
|
||||
content.addItem(this.searchBox!, { CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-bottom': '20px' } });
|
||||
|
||||
this._parametersTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.parameterName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '20%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.value,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: false,
|
||||
width: '20%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.description,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '50%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: {
|
||||
...cssStyles.tableRow
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.resetToDefault,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: false,
|
||||
width: '10%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: []
|
||||
}).component();
|
||||
|
||||
this.parameterContainer = this.modelView.modelBuilder.divContainer().component();
|
||||
this.selectComponent();
|
||||
content.addItem(this.parameterContainer);
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Save Edits
|
||||
this.saveButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.saveText,
|
||||
iconPath: IconPathHelper.save,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
let engineSettings: string[] = [];
|
||||
this.disposables.push(
|
||||
this.saveButton.onDidClick(async () => {
|
||||
this.saveButton.enabled = false;
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
this.parameterUpdates.forEach((value, key) => {
|
||||
engineSettings.push(`${key}="${value}"`);
|
||||
});
|
||||
} catch (err) {
|
||||
// If an error occurs while editing the instance then re-enable the save button since
|
||||
// the edit wasn't successfully applied
|
||||
this.saveButton.enabled = true;
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
await this.callGetEngineSettings();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||
|
||||
engineSettings = [];
|
||||
this.changedComponentValues.clear();
|
||||
this.parameterUpdates.clear();
|
||||
this.discardButton.enabled = false;
|
||||
this.resetAllButton.enabled = true;
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Discard
|
||||
this.discardButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.discardText,
|
||||
iconPath: IconPathHelper.discard,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.discardButton.onDidClick(async () => {
|
||||
this.discardButton.enabled = false;
|
||||
try {
|
||||
this.discardParametersTableChanges();
|
||||
} catch (error) {
|
||||
this.discardButton!.enabled = true;
|
||||
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
||||
} finally {
|
||||
this.changedComponentValues.clear();
|
||||
this.saveButton.enabled = false;
|
||||
this.parameterUpdates.clear();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Reset all
|
||||
this.resetAllButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.resetAllToDefault,
|
||||
iconPath: IconPathHelper.reset,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.resetAllButton.onDidClick(async () => {
|
||||
this.resetAllButton.enabled = false;
|
||||
this.discardButton.enabled = false;
|
||||
this.saveButton.enabled = false;
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
} catch (err) {
|
||||
// If an error occurs while resetting the instance then re-enable the reset button since
|
||||
// the edit wasn't successfully applied
|
||||
if (this.parameterUpdates.size > 0) {
|
||||
this.discardButton.enabled = true;
|
||||
this.saveButton.enabled = true;
|
||||
}
|
||||
this.resetAllButton.enabled = true;
|
||||
throw err;
|
||||
}
|
||||
this.changedComponentValues.clear();
|
||||
try {
|
||||
await this.callGetEngineSettings();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||
this.parameterUpdates.clear();
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.resetFailed(error));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||
{ component: this.saveButton },
|
||||
{ component: this.discardButton },
|
||||
{ component: this.resetAllButton }
|
||||
]).component();
|
||||
}
|
||||
|
||||
protected initializeConnectButton(): void {
|
||||
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.connectToServer,
|
||||
enabled: false,
|
||||
CSSStyles: { 'max-width': '125px' }
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.connectToServerButton.onDidClick(async () => {
|
||||
let scale = this._postgresModel.config?.spec.scale;
|
||||
let nodes = (scale?.workers ?? scale?.shards ?? 0);
|
||||
if (this.title === loc.workerNodeParameters && nodes === 0) {
|
||||
vscode.window.showInformationMessage(loc.noWorkerPods);
|
||||
return;
|
||||
}
|
||||
|
||||
this.connectToServerButton!.enabled = false;
|
||||
if (!vscode.extensions.getExtension(loc.postgresExtension)) {
|
||||
const response = await vscode.window.showErrorMessage(loc.missingExtension('PostgreSQL'), loc.yes, loc.no);
|
||||
if (response !== loc.yes) {
|
||||
this.connectToServerButton!.enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.installingExtension(loc.postgresExtension),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(loc.extensionInstallationFailed(loc.postgresExtension));
|
||||
this.connectToServerButton!.enabled = true;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
);
|
||||
vscode.window.showInformationMessage(loc.extensionInstalled(loc.postgresExtension));
|
||||
}
|
||||
|
||||
this.parametersTableLoading!.loading = true;
|
||||
await this.callGetEngineSettings().finally(() => this.parametersTableLoading!.loading = false);
|
||||
this.searchBox.enabled = true;
|
||||
this.resetAllButton.enabled = true;
|
||||
this.parameterContainer.clearItems();
|
||||
this.parameterContainer.addItem(this._parametersTable);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private selectComponent(): void {
|
||||
if (!this._postgresModel.engineSettingsLastUpdated) {
|
||||
this.parameterContainer.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.connectToPostgresDescription,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component());
|
||||
this.initializeConnectButton();
|
||||
this.parameterContainer.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
|
||||
this.parametersTableLoading = this.modelView.modelBuilder.loadingComponent().component();
|
||||
this.parameterContainer.addItem(this.parametersTableLoading);
|
||||
} else {
|
||||
this.searchBox.enabled = true;
|
||||
this.resetAllButton.enabled = true;
|
||||
this.parameterContainer.addItem(this._parametersTable!);
|
||||
this.refreshParametersTableValues();
|
||||
}
|
||||
}
|
||||
|
||||
private async callGetEngineSettings(): Promise<void> {
|
||||
try {
|
||||
await this._postgresModel.getEngineSettings().then(() => {
|
||||
if (this._parametersTable.data?.length !== 0) {
|
||||
this.refreshParametersTableValues();
|
||||
} else {
|
||||
this.populateParametersTable();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
vscode.window.showWarningMessage(loc.pgConnectionRequired);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
this.connectToServerButton!.enabled = true;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
protected initializeSearchBox(): void {
|
||||
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
enabled: false,
|
||||
placeHolder: loc.searchToFilter
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.searchBox.onTextChanged(() => {
|
||||
this.onSearchFilter();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
private onSearchFilter(): void {
|
||||
if (!this.searchBox.value) {
|
||||
this._parametersTable.setFilter(undefined);
|
||||
} else {
|
||||
this.filterParameters(this.searchBox.value);
|
||||
}
|
||||
}
|
||||
|
||||
private filterParameters(search: string): void {
|
||||
const filteredRowIndexes: number[] = [];
|
||||
this._parametersTable.data?.forEach((row, index) => {
|
||||
if (row[0].toUpperCase()?.search(search.toUpperCase()) !== -1 || row[2].toUpperCase()?.search(search.toUpperCase()) !== -1) {
|
||||
filteredRowIndexes.push(index);
|
||||
}
|
||||
});
|
||||
this._parametersTable.setFilter(filteredRowIndexes);
|
||||
}
|
||||
|
||||
private handleOnTextChanged(component: azdata.InputBoxComponent, name: string, currentValue: string | undefined): boolean {
|
||||
if (!component.valid) {
|
||||
// If invalid value return false and enable discard button
|
||||
this.discardButton.enabled = true;
|
||||
this.collectChangedComponents(name);
|
||||
return false;
|
||||
} else if (component.value === currentValue) {
|
||||
this.removeFromChangedComponents(name);
|
||||
return false;
|
||||
} else {
|
||||
/* If a valid value has been entered into the input box, enable save and discard buttons
|
||||
so that user could choose to either edit instance or clear all inputs
|
||||
return true */
|
||||
this.saveButton.enabled = true;
|
||||
this.discardButton.enabled = true;
|
||||
this.collectChangedComponents(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected createParameterComponents(engineSetting: EngineSettingsModel): ParametersModel {
|
||||
|
||||
// Can reset individual parameter
|
||||
const resetParameterButton = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.reset,
|
||||
title: loc.resetToDefault,
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
enabled: true
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
resetParameterButton.onDidClick(async () => {
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
await this.callGetEngineSettings();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
}
|
||||
);
|
||||
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let valueComponent: azdata.Component;
|
||||
if (engineSetting.type === 'enum') {
|
||||
// If type is enum, component should be drop down menu
|
||||
let values: string[] = [];
|
||||
if (typeof engineSetting.options === 'string') {
|
||||
let options = engineSetting.options?.slice(1, -1).split(',');
|
||||
values = options.map(option => option.slice(option.indexOf('"') + 1, -1));
|
||||
} else if (engineSetting.options) {
|
||||
values = engineSetting.options;
|
||||
}
|
||||
|
||||
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
|
||||
values: values,
|
||||
value: engineSetting.value,
|
||||
width: '150px'
|
||||
}).component();
|
||||
valueComponent = valueBox;
|
||||
|
||||
this.disposables.push(
|
||||
valueBox.onValueChanged(() => {
|
||||
if (engineSetting.value !== String(valueBox.value)) {
|
||||
this.parameterUpdates.set(engineSetting.parameterName!, String(valueBox.value));
|
||||
this.collectChangedComponents(engineSetting.parameterName!);
|
||||
this.saveButton.enabled = true;
|
||||
this.discardButton.enabled = true;
|
||||
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else if (engineSetting.type === 'bool') {
|
||||
// If type is bool, component should be checkbox to turn on or off
|
||||
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
|
||||
label: loc.on,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
valueComponent = valueBox;
|
||||
|
||||
if (engineSetting.value === 'on') {
|
||||
valueBox.checked = true;
|
||||
} else {
|
||||
valueBox.checked = false;
|
||||
}
|
||||
|
||||
this.disposables.push(
|
||||
valueBox.onChanged(() => {
|
||||
if (valueBox.checked && engineSetting.value === 'off') {
|
||||
this.parameterUpdates.set(engineSetting.parameterName!, loc.on);
|
||||
this.collectChangedComponents(engineSetting.parameterName!);
|
||||
this.saveButton.enabled = true;
|
||||
this.discardButton.enabled = true;
|
||||
} else if (!valueBox.checked && engineSetting.value === 'on') {
|
||||
this.parameterUpdates.set(engineSetting.parameterName!, loc.off);
|
||||
this.collectChangedComponents(engineSetting.parameterName!);
|
||||
this.saveButton.enabled = true;
|
||||
this.discardButton.enabled = true;
|
||||
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else if (engineSetting.type === 'string') {
|
||||
// If type is string, component should be text inputbox
|
||||
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: engineSetting.value,
|
||||
width: '150px'
|
||||
}).component();
|
||||
valueComponent = valueBox;
|
||||
|
||||
this.disposables.push(
|
||||
valueBox.onTextChanged(() => {
|
||||
if ((this.handleOnTextChanged(valueBox, engineSetting.parameterName!, engineSetting.value))) {
|
||||
this.parameterUpdates.set(engineSetting.parameterName!, `"${valueBox.value!}"`);
|
||||
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
|
||||
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
required: true,
|
||||
readOnly: false,
|
||||
min: parseInt(engineSetting.min!),
|
||||
max: parseInt(engineSetting.max!),
|
||||
inputType: 'number',
|
||||
value: engineSetting.value,
|
||||
width: '150px'
|
||||
}).component();
|
||||
valueComponent = valueBox;
|
||||
|
||||
this.disposables.push(
|
||||
valueBox.onTextChanged(() => {
|
||||
if ((this.handleOnTextChanged(valueBox, engineSetting.parameterName!, engineSetting.value))) {
|
||||
this.parameterUpdates.set(engineSetting.parameterName!, valueBox.value!);
|
||||
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Information bubble title to show allowed values
|
||||
let information = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.information,
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
description: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
|
||||
}).component();
|
||||
|
||||
return {
|
||||
parameterName: engineSetting.parameterName!,
|
||||
originalValue: engineSetting.value!,
|
||||
valueComponent: valueComponent,
|
||||
information: information,
|
||||
description: engineSetting.description!,
|
||||
resetButton: resetParameterButton
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
parameterName: engineSetting.parameterName!,
|
||||
originalValue: engineSetting.value!,
|
||||
valueComponent: valueComponent,
|
||||
description: engineSetting.description!,
|
||||
resetButton: resetParameterButton
|
||||
};
|
||||
}
|
||||
|
||||
private collectChangedComponents(name: string): void {
|
||||
if (!this.changedComponentValues.has(name)) {
|
||||
this.changedComponentValues.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
private removeFromChangedComponents(name: string): void {
|
||||
if (this.changedComponentValues.has(name)) {
|
||||
this.changedComponentValues.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
private discardParametersTableChanges(): void {
|
||||
this.changedComponentValues.forEach(v => {
|
||||
let param = this._parameters.find(p => p.parameterName === v);
|
||||
if (instanceOfCheckBox(param!.valueComponent)) {
|
||||
if (param!.originalValue === 'on') {
|
||||
param!.valueComponent.checked = true;
|
||||
} else {
|
||||
param!.valueComponent.checked = false;
|
||||
}
|
||||
} else {
|
||||
param!.valueComponent.value = param!.originalValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private populateParametersTable(): void {
|
||||
this._parameters = this.engineSettings.map(parameter => this.createParameterComponents(parameter));
|
||||
|
||||
this._parametersTable.data = this._parameters.map(p => {
|
||||
if (p.information) {
|
||||
// Container to hold input component and information bubble
|
||||
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
valueContainer.addItem(p.valueComponent, { CSSStyles: { 'margin-right': '0px' } });
|
||||
valueContainer.addItem(p.information, { CSSStyles: { 'margin-left': '5px' } });
|
||||
return [p.parameterName, valueContainer, p.description, p.resetButton];
|
||||
} else {
|
||||
return [p.parameterName, p.valueComponent, p.description, p.resetButton];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if exisiting parameter values needs to be updated.
|
||||
* Only updates exisiting parameters, will not add/remove parameters from the table.
|
||||
*/
|
||||
private refreshParametersTableValues(): void {
|
||||
this.engineSettings.map(parameter => {
|
||||
let param = this._parameters.find(p => p.parameterName === parameter.parameterName);
|
||||
if (param) {
|
||||
if (parameter.value !== param.originalValue) {
|
||||
param.originalValue = parameter.value!;
|
||||
|
||||
if (instanceOfCheckBox(param.valueComponent)) {
|
||||
if (param.originalValue === 'on') {
|
||||
param.valueComponent.checked = true;
|
||||
} else {
|
||||
param.valueComponent.checked = false;
|
||||
}
|
||||
} else {
|
||||
param.valueComponent.value = parameter.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async handleServiceUpdated(): Promise<void> {
|
||||
if (this._postgresModel.configLastUpdated && !this._postgresModel.engineSettingsLastUpdated) {
|
||||
this.connectToServerButton!.enabled = true;
|
||||
this.parametersTableLoading!.loading = false;
|
||||
} else if (this._postgresModel.engineSettingsLastUpdated) {
|
||||
await this.callGetEngineSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +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 * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper } from '../../../constants';
|
||||
import { PostgresParametersPage } from './postgresParameters';
|
||||
import { PostgresModel, EngineSettingsModel } from '../../../models/postgresModel';
|
||||
|
||||
export class PostgresWorkerNodeParametersPage extends PostgresParametersPage {
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard, postgresModel);
|
||||
}
|
||||
|
||||
protected get title(): string {
|
||||
return loc.workerNodeParameters;
|
||||
}
|
||||
|
||||
protected get id(): string {
|
||||
return 'postgres-worker-node-parameters';
|
||||
}
|
||||
|
||||
protected get icon(): { dark: string; light: string; } {
|
||||
return IconPathHelper.gearBlue;
|
||||
}
|
||||
|
||||
protected get description(): string {
|
||||
return loc.workerNodesParametersDescription;
|
||||
}
|
||||
|
||||
|
||||
protected get engineSettings(): EngineSettingsModel[] {
|
||||
return this._postgresModel.workerNodesEngineSettings;
|
||||
}
|
||||
|
||||
}
|
||||
31
extensions/azcli/src/typings/az-ext.d.ts
vendored
31
extensions/azcli/src/typings/az-ext.d.ts
vendored
@@ -450,43 +450,12 @@ declare module 'az-ext' {
|
||||
uid: string, // "26d0f5bb-0c0b-4225-a6b5-5be2bf6feac0"
|
||||
},
|
||||
spec: {
|
||||
engine: {
|
||||
extensions: {
|
||||
name: string // "citus"
|
||||
}[],
|
||||
settings: {
|
||||
default: { [key: string]: string }, // { "max_connections": "101", "work_mem": "4MB" }
|
||||
roles: {
|
||||
coordinator: { [key: string]: string },
|
||||
worker: { [key: string]: string }
|
||||
}
|
||||
},
|
||||
version: string // "12"
|
||||
},
|
||||
scale: {
|
||||
shards: number, // 1 (shards was renamed to workers, kept here for backwards compatibility)
|
||||
workers: number // 1
|
||||
},
|
||||
scheduling: { // If no roles are specified, settings will apply to all nodes of the PostgreSQL Hyperscale server group.
|
||||
default: {
|
||||
resources: {
|
||||
requests: SchedulingOptions,
|
||||
limits: SchedulingOptions
|
||||
}
|
||||
},
|
||||
roles: {
|
||||
coordinator: {
|
||||
resources: {
|
||||
requests: SchedulingOptions,
|
||||
limits: SchedulingOptions
|
||||
}
|
||||
},
|
||||
worker: {
|
||||
resources: {
|
||||
requests: SchedulingOptions,
|
||||
limits: SchedulingOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
services: {
|
||||
|
||||
Reference in New Issue
Block a user