mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
* Addition: properties page with link to dashboard * Include new page * Initial Parameter page start * Include new changes from merged PRs * Including new constants * Git errors * Add parameter commands and help * Reset command * Added chart * git fix * Fixed string issues * connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection * Initial start to adding connect to sql for postgres instance * Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message * Miaa models provides dialog title * Updated failed message parameters * completionPromise.reject * Fixed connect to MSSql * Messy dialog showing from button * removed this._completionPromise.reject * Cleaning up code * Set connectSqlDialog to be an abstract class. Separated out Miaa and Postgres. (#13532) * connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection * Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message * Miaa models provides dialog title * Updated failed message parameters * completionPromise.reject * Fixed connect to MSSql * removed this._completionPromise.reject * Connect button clean up * Format * Format doc * Fixed compile errors * Cleaning up page * Clean up * clean up refresh * Format doc * Removed ellipse * Cleaning up problems * Updating localized constants * Missing username update * Get connection profile added to Resource model, abstract method created for calling connection dialog * Added createConnectionProfile * took out import * Pulled in new changes, fixed usercancellederror * Getting engine settings * Git errors * Corrected names of icons and constants, Fixed Miaa dialog title * Changed gear svg, made postgres extension a loc constant, fixed formatting * Fixed controller model name * Put connection profile and id in resource model, changed back controller model in base class
507 lines
17 KiB
TypeScript
507 lines
17 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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 azdataExt from 'azdata-ext';
|
|
import * as loc from '../../../localizedConstants';
|
|
import { UserCancelledError } from '../../../common/api';
|
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
|
import { DashboardPage } from '../../components/dashboardPage';
|
|
import { PostgresModel } from '../../../models/postgresModel';
|
|
|
|
export class PostgresParametersPage extends DashboardPage {
|
|
private searchBox?: azdata.InputBoxComponent;
|
|
private parametersTable!: azdata.DeclarativeTableComponent;
|
|
private parameterContainer?: azdata.DivContainer;
|
|
private _parametersTableLoading!: azdata.LoadingComponent;
|
|
|
|
private discardButton?: azdata.ButtonComponent;
|
|
private saveButton?: azdata.ButtonComponent;
|
|
private resetButton?: azdata.ButtonComponent;
|
|
private connectToServerButton?: azdata.ButtonComponent;
|
|
|
|
private engineSettings = `'`;
|
|
private engineSettingUpdates?: Map<string, string>;
|
|
|
|
private readonly _azdataApi: azdataExt.IExtension;
|
|
|
|
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
|
super(modelView);
|
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
|
|
|
this.initializeConnectButton();
|
|
this.initializeSearchBox();
|
|
|
|
this.engineSettingUpdates = new Map();
|
|
|
|
this.disposables.push(
|
|
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
|
|
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEngineSettingsUpdated())));
|
|
}
|
|
|
|
protected get title(): string {
|
|
return loc.nodeParameters;
|
|
}
|
|
|
|
protected get id(): string {
|
|
return 'postgres-node-parameters';
|
|
}
|
|
|
|
protected get icon(): { dark: string; light: string; } {
|
|
return IconPathHelper.gear;
|
|
}
|
|
|
|
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: loc.nodeParameters,
|
|
CSSStyles: { ...cssStyles.title }
|
|
}).component());
|
|
|
|
const info = this.modelView.modelBuilder.text().withProps({
|
|
value: loc.nodeParametersDescription,
|
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
}).component();
|
|
|
|
const link = this.modelView.modelBuilder.hyperlink().withProps({
|
|
label: loc.learnAboutNodeParameters,
|
|
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale',
|
|
}).component();
|
|
|
|
const infoAndLink = this.modelView.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).component();
|
|
infoAndLink.addItem(info, { CSSStyles: { 'margin-right': '5px' } });
|
|
infoAndLink.addItem(link);
|
|
content.addItem(infoAndLink, { 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: 'Parameter Name',
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: '20%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
},
|
|
{
|
|
displayName: 'Value',
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: false,
|
|
width: '20%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: {
|
|
...cssStyles.tableRow,
|
|
'overflow': 'hidden',
|
|
'text-overflow': 'ellipsis',
|
|
'white-space': 'nowrap',
|
|
'max-width': '0'
|
|
}
|
|
},
|
|
{
|
|
displayName: 'Description',
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: '50%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: {
|
|
...cssStyles.tableRow,
|
|
'overflow': 'hidden',
|
|
'text-overflow': 'ellipsis',
|
|
'white-space': 'nowrap',
|
|
'max-width': '0'
|
|
}
|
|
},
|
|
{
|
|
displayName: 'Reset To Default',
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: false,
|
|
width: '10%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
}
|
|
],
|
|
data: [
|
|
this.parameterComponents('TEST NAME', 'string'),
|
|
this.parameterComponents('TEST NAME 2', 'real'),
|
|
this.createParametersTable()]
|
|
|
|
}).component();
|
|
|
|
this._parametersTableLoading = this.modelView.modelBuilder.loadingComponent().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();
|
|
|
|
this.disposables.push(
|
|
this.saveButton.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> => {
|
|
//Edit multiple
|
|
// azdata arc postgres server edit -n <server group name> -e '<parameter name>=<parameter value>, <parameter name>=<parameter value>,...'
|
|
try {
|
|
this.engineSettingUpdates!.forEach((value: string) => {
|
|
this.engineSettings += value + ', ';
|
|
});
|
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
|
this._postgresModel.info.name, { engineSettings: this.engineSettings + `'` });
|
|
} 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;
|
|
}
|
|
await this._postgresModel.refresh();
|
|
}
|
|
);
|
|
|
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
|
|
|
this.engineSettings = `'`;
|
|
this.engineSettingUpdates!.clear();
|
|
this.discardButton!.enabled = false;
|
|
|
|
} 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 {
|
|
// TODO
|
|
// this.parametersTable.data = [];
|
|
this.engineSettingUpdates!.clear();
|
|
} catch (error) {
|
|
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
|
} finally {
|
|
this.saveButton!.enabled = false;
|
|
}
|
|
}));
|
|
|
|
// Reset
|
|
this.resetButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.resetAllToDefault,
|
|
iconPath: IconPathHelper.reset,
|
|
enabled: true
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
this.resetButton.onDidClick(async () => {
|
|
this.resetButton!.enabled = false;
|
|
this.discardButton!.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> => {
|
|
//all
|
|
// azdata arc postgres server edit -n <server group name> -e '' -re
|
|
try {
|
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
|
this._postgresModel.info.name, { engineSettings: `'' -re` });
|
|
} catch (err) {
|
|
// If an error occurs while resetting the instance then re-enable the reset button since
|
|
// the edit wasn't successfully applied
|
|
this.resetButton!.enabled = true;
|
|
throw err;
|
|
}
|
|
await this._postgresModel.refresh();
|
|
}
|
|
|
|
);
|
|
} catch (error) {
|
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
|
}
|
|
}));
|
|
|
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
|
{ component: this.saveButton },
|
|
{ component: this.discardButton },
|
|
{ component: this.resetButton }
|
|
]).component();
|
|
}
|
|
|
|
private initializeConnectButton() {
|
|
|
|
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.connectToServer,
|
|
enabled: false,
|
|
CSSStyles: { 'max-width': '125px' }
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
this.connectToServerButton!.onDidClick(async () => {
|
|
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.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
|
|
}
|
|
|
|
await this._postgresModel.getEngineSettings().catch(err => {
|
|
// If an error occurs show a message so the user knows something failed but still
|
|
// fire the event so callers can know to update (e.g. so dashboards don't show the
|
|
// loading icon forever)
|
|
if (err instanceof UserCancelledError) {
|
|
vscode.window.showWarningMessage(loc.pgConnectionRequired);
|
|
} else {
|
|
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, err));
|
|
}
|
|
this._postgresModel.engineSettingsLastUpdated = new Date();
|
|
this._postgresModel._onEngineSettingsUpdated.fire(this._postgresModel._engineSettings);
|
|
this.connectToServerButton!.enabled = true;
|
|
throw err;
|
|
});
|
|
|
|
this.parameterContainer!.clearItems();
|
|
this.parameterContainer!.addItem(this.parametersTable);
|
|
|
|
}));
|
|
}
|
|
|
|
private initializeSearchBox() {
|
|
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
|
|
readOnly: false,
|
|
placeHolder: loc.searchToFilter
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
this.searchBox.onTextChanged(() => {
|
|
this.filterParameters();
|
|
})
|
|
);
|
|
}
|
|
|
|
private filterParameters() {
|
|
//TODO
|
|
}
|
|
|
|
private createParametersTable(): any[] {
|
|
/*Define server settings that shouldn't be modified. we block archive_*, restore_*, and synchronous_commit to prevent the user
|
|
from messing up our backups. (we rely on synchronous_commit to ensure WAL changes are written immediately.)
|
|
we block log_* to protect our logging. we block wal_level because Citus needs a particular wal_Level to rebalance shards
|
|
TODO: Review list of blacklisted parameters. wal_level should only be blacklisted if sharding is enabled
|
|
To not be modified
|
|
"archive_command", "archive_timeout", "log_directory", "log_file_mode", "log_filename", "restore_command",
|
|
"shared_preload_libraries", "synchronous_commit", "ssl", "unix_socket_permissions", "wal_level" */
|
|
|
|
// For ev in this._postgresModel._engineSettings
|
|
// create row
|
|
// return rows
|
|
this.parameterComponents('engineSetting', '');
|
|
return [];
|
|
}
|
|
|
|
private parameterComponents(name: string, type: string): any[] {
|
|
let data = [];
|
|
|
|
// Set parameter name
|
|
const parameterName = this.modelView.modelBuilder.text().withProps({
|
|
value: name,
|
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
}).component();
|
|
data.push(parameterName);
|
|
|
|
// Container to hold input component and information bubble
|
|
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
|
|
|
// Information bubble title to be set depening on type of input
|
|
let information = this.modelView.modelBuilder.button().withProps({
|
|
iconPath: IconPathHelper.information,
|
|
width: '12px',
|
|
height: '12px',
|
|
enabled: false
|
|
}).component();
|
|
|
|
if (type === 'enum') {
|
|
// If type is enum, component should be drop down menu
|
|
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
|
|
values: [], //TODO,
|
|
value: '', //TODO
|
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
}).component();
|
|
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
|
|
|
this.disposables.push(
|
|
valueBox.onValueChanged(() => {
|
|
this.engineSettingUpdates!.set(name, String(valueBox.value));
|
|
|
|
})
|
|
);
|
|
|
|
information.updateProperty('title', loc.allowedValue('enums')); //TODO
|
|
} else if (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,
|
|
checked: true, //TODO
|
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
}).component();
|
|
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
|
|
|
this.disposables.push(
|
|
valueBox.onChanged(() => {
|
|
if (valueBox.checked) {
|
|
this.engineSettingUpdates!.set(name, loc.on);
|
|
} else {
|
|
this.engineSettingUpdates!.set(name, loc.off);
|
|
}
|
|
})
|
|
);
|
|
|
|
information.updateProperty('title', loc.allowedValue('on,off')); //TODO
|
|
} else if (type === 'string') {
|
|
// If type is string, component should be text inputbox
|
|
// How to add validation: .withValidation(component => component.value?.search('[0-9]') == -1)
|
|
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
|
readOnly: false,
|
|
value: '', //TODO
|
|
CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '200px' }
|
|
}).component();
|
|
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
|
|
|
this.disposables.push(
|
|
valueBox.onTextChanged(() => {
|
|
this.engineSettingUpdates!.set(name, valueBox.value!);
|
|
})
|
|
);
|
|
|
|
information.updateProperty('title', loc.allowedValue(loc.allowedValue('[A-Za-z._]+'))); //TODO
|
|
} 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({
|
|
readOnly: false,
|
|
min: 0, //TODO
|
|
max: 10000,
|
|
validationErrorMessage: loc.outOfRange('min', 'max'), //TODO
|
|
inputType: 'number',
|
|
value: '0', //TODO
|
|
CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '200px' }
|
|
}).component();
|
|
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
|
|
|
this.disposables.push(
|
|
valueBox.onTextChanged(() => {
|
|
this.engineSettingUpdates!.set(name, valueBox.value!);
|
|
})
|
|
);
|
|
|
|
information.updateProperty('title', loc.allowedValue(loc.rangeSetting('min', 'max'))); //TODO
|
|
}
|
|
|
|
valueContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
|
|
data.push(valueContainer);
|
|
|
|
const parameterDescription = this.modelView.modelBuilder.text().withProps({
|
|
value: 'TEST DESCRIPTION HERE ...............................ytgbyugvtyvctyrcvytjv ycrtctyv tyfty ftyuvuyvuy', // TODO
|
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
}).component();
|
|
data.push(parameterDescription);
|
|
|
|
// Can reset individual component
|
|
const resetParameter = this.modelView.modelBuilder.button().withProps({
|
|
iconPath: IconPathHelper.reset,
|
|
title: loc.resetToDefault,
|
|
width: '20px',
|
|
height: '20px',
|
|
enabled: true
|
|
}).component();
|
|
data.push(resetParameter);
|
|
|
|
// azdata arc postgres server edit -n postgres01 -e shared_buffers=
|
|
this.disposables.push(
|
|
resetParameter.onDidClick(async () => {
|
|
try {
|
|
await vscode.window.withProgress(
|
|
{
|
|
location: vscode.ProgressLocation.Notification,
|
|
title: loc.updatingInstance(this._postgresModel.info.name),
|
|
cancellable: false
|
|
},
|
|
(_progress, _token) => {
|
|
return this._azdataApi.azdata.arc.postgres.server.edit(
|
|
this._postgresModel.info.name, { engineSettings: name + '=' });
|
|
}
|
|
);
|
|
|
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
|
|
|
} catch (error) {
|
|
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
|
}
|
|
}));
|
|
|
|
return data;
|
|
}
|
|
|
|
private selectComponent() {
|
|
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.parameterContainer!.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
|
|
this.parameterContainer!.addItem(this._parametersTableLoading!);
|
|
} else {
|
|
this.parameterContainer!.addItem(this.parametersTable!);
|
|
}
|
|
}
|
|
|
|
private handleEngineSettingsUpdated(): void {
|
|
//TODO
|
|
}
|
|
|
|
private handleServiceUpdated() {
|
|
// TODO
|
|
if (this._postgresModel.configLastUpdated) {
|
|
this.connectToServerButton!.enabled = true;
|
|
this._parametersTableLoading!.loading = false;
|
|
}
|
|
}
|
|
}
|