diff --git a/extensions/arc/images/postgres.svg b/extensions/arc/images/postgres.svg
index e247e4e717..8c4cb91c17 100644
--- a/extensions/arc/images/postgres.svg
+++ b/extensions/arc/images/postgres.svg
@@ -1 +1,21 @@
-
\ No newline at end of file
+
diff --git a/extensions/arc/src/common/utils.ts b/extensions/arc/src/common/utils.ts
index 19954753a5..a915321cf1 100644
--- a/extensions/arc/src/common/utils.ts
+++ b/extensions/arc/src/common/utils.ts
@@ -107,24 +107,40 @@ export function getDatabaseStateDisplayText(state: string): string {
* @param name The name of the resource to delete
* @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason
*/
-export async function promptForResourceDeletion(namespace: string, name: string): Promise {
+
+/**
+ * Opens an input box prompting and validating the user's input.
+ * @param options Options for the input box
+ * @param title An optional title for the input box
+ * @returns Promise resolving to the user's input if it passed validation,
+ * or undefined if the input box was closed for any other reason
+ */
+async function promptInputBox(title: string, options: vscode.InputBoxOptions): Promise {
const inputBox = vscode.window.createInputBox();
- inputBox.title = loc.resourceDeletionWarning(namespace, name);
- inputBox.placeholder = name;
+ inputBox.title = title;
+ inputBox.prompt = options.prompt;
+ inputBox.placeholder = options.placeHolder;
+ inputBox.password = options.password ?? false;
+ inputBox.value = options.value ?? '';
+ inputBox.ignoreFocusOut = options.ignoreFocusOut ?? false;
+
return new Promise(resolve => {
let valueAccepted = false;
- inputBox.onDidAccept(() => {
- if (inputBox.value === name) {
- valueAccepted = true;
- inputBox.hide();
- resolve(true);
- } else {
- inputBox.validationMessage = loc.invalidResourceDeletionName(inputBox.value);
+ inputBox.onDidAccept(async () => {
+ if (options.validateInput) {
+ const errorMessage = await options.validateInput(inputBox.value);
+ if (errorMessage) {
+ inputBox.validationMessage = errorMessage;
+ return;
+ }
}
+ valueAccepted = true;
+ inputBox.hide();
+ resolve(inputBox.value);
});
inputBox.onDidHide(() => {
if (!valueAccepted) {
- resolve(false);
+ resolve(undefined);
}
inputBox.dispose();
});
@@ -135,6 +151,46 @@ export async function promptForResourceDeletion(namespace: string, name: string)
});
}
+/**
+ * Opens an input box prompting the user to enter in the name of a resource to delete
+ * @param namespace The namespace of the resource to delete
+ * @param name The name of the resource to delete
+ * @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason
+ */
+export async function promptForResourceDeletion(namespace: string, name: string): Promise {
+ const title = loc.resourceDeletionWarning(namespace, name);
+ const options: vscode.InputBoxOptions = {
+ placeHolder: name,
+ validateInput: input => input !== name ? loc.invalidResourceDeletionName(name) : ''
+ };
+
+ return await promptInputBox(title, options) !== undefined;
+}
+
+/**
+ * Opens an input box prompting the user to enter and confirm a password
+ * @param validate A function that accepts the password and returns an error message if it's invalid
+ * @returns Promise resolving to the password if it passed validation,
+ * or false if the input box was closed for any other reason
+ */
+export async function promptAndConfirmPassword(validate: (input: string) => string): Promise {
+ const title = loc.resetPassword;
+ const options: vscode.InputBoxOptions = {
+ prompt: loc.enterNewPassword,
+ password: true,
+ validateInput: input => validate(input)
+ };
+
+ const password = await promptInputBox(title, options);
+ if (password) {
+ options.prompt = loc.confirmNewPassword;
+ options.validateInput = input => input !== password ? loc.thePasswordsDoNotMatch : '';
+ return promptInputBox(title, options);
+ }
+
+ return false;
+}
+
/**
* Gets the message to display for a given error object that may be a variety of types.
* @param error The error object
diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts
index d994530d6f..19a8ce90fe 100644
--- a/extensions/arc/src/localizedConstants.ts
+++ b/extensions/arc/src/localizedConstants.ts
@@ -98,15 +98,19 @@ export const worker = localize('arc.worker', "Worker");
export const monitor = localize('arc.monitor', "Monitor");
export const newDatabase = localize('arc.newDatabase', "New Database");
export const databaseName = localize('arc.databaseName', "Database name");
-export const newPassword = localize('arc.newPassword', "New password");
+export const enterNewPassword = localize('arc.enterNewPassword', "Enter a new password");
+export const confirmNewPassword = localize('arc.confirmNewPassword', "Confirm the new password");
export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients', "Learn more about Azure PostgreSQL Hyperscale client interfaces");
export const node = localize('arc.node', "node");
export const nodes = localize('arc.nodes', "nodes");
export const storagePerNode = localize('arc.storagePerNode', "storage per node");
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
+export const enterANonEmptyPassword = localize('arc.enterANonEmptyPassword', "Enter a non empty password or press escape to exit.");
+export const thePasswordsDoNotMatch = localize('arc.thePasswordsDoNotMatch', "The passwords do not match. Confirm the password or press escape to exit.");
+export const passwordReset = localize('arc.passwordReset', "Password reset successfully");
+export const passwordResetFailed = localize('arc.passwordResetFailed', "Failed to reset password");
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
-export function passwordReset(name: string): string { return localize('arc.passwordReset', "Password reset for service {0}", name); }
export function resourceDeleted(name: string): string { return localize('arc.resourceDeleted', "Resource '{0}' deleted", 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); }
@@ -127,6 +131,5 @@ export function invalidResourceDeletionName(name: string): string { return local
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, getErrorMessage(error)); }
-export function passwordResetFailed(name: string, error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password for service {0}. {1}", name, getErrorMessage(error)); }
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
diff --git a/extensions/arc/src/models/postgresModel.ts b/extensions/arc/src/models/postgresModel.ts
index 539470a789..262e4f61bf 100644
--- a/extensions/arc/src/models/postgresModel.ts
+++ b/extensions/arc/src/models/postgresModel.ts
@@ -17,16 +17,12 @@ export enum PodRole {
export class PostgresModel {
private _databaseRouter: DatabaseRouterApi;
private _service?: DuskyObjectModelsDatabaseService;
- private _password?: string;
private _pods?: V1Pod[];
private readonly _onServiceUpdated = new vscode.EventEmitter();
- private readonly _onPasswordUpdated = new vscode.EventEmitter();
private readonly _onPodsUpdated = new vscode.EventEmitter();
public onServiceUpdated = this._onServiceUpdated.event;
- public onPasswordUpdated = this._onPasswordUpdated.event;
public onPodsUpdated = this._onPodsUpdated.event;
public serviceLastUpdated?: Date;
- public passwordLastUpdated?: Date;
public podsLastUpdated?: Date;
constructor(controllerUrl: string, auth: Authentication, private _namespace: string, private _name: string) {
@@ -54,11 +50,6 @@ export class PostgresModel {
return this._service;
}
- /** Returns the service's password */
- public get password(): string | undefined {
- return this._password;
- }
-
/** Returns the service's pods */
public get pods(): V1Pod[] | undefined {
return this._pods;
@@ -72,11 +63,6 @@ export class PostgresModel {
this.serviceLastUpdated = new Date();
this._onServiceUpdated.fire(this._service);
}),
- this._databaseRouter.getDuskyPassword(this._namespace, this._name).then(response => {
- this._password = response.body;
- this.passwordLastUpdated = new Date();
- this._onPasswordUpdated.fire(this._password!);
- }),
this._databaseRouter.getDuskyPods(this._namespace, this._name).then(response => {
this._pods = response.body;
this.podsLastUpdated = new Date();
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
index 05fa9f0b00..1ca1772a39 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresConnectionStringsPage.ts
@@ -17,7 +17,6 @@ export class PostgresConnectionStringsPage extends DashboardPage {
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
super(modelView);
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
- this._postgresModel.onPasswordUpdated(() => this.eventuallyRunOnInitialized(() => this.refresh()));
}
protected get title(): string {
@@ -43,18 +42,19 @@ export class PostgresConnectionStringsPage extends DashboardPage {
}).component());
const info = this.modelView.modelBuilder.text().withProperties({
- value: `${loc.selectConnectionString}. `,
+ value: loc.selectConnectionString,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const link = this.modelView.modelBuilder.hyperlink().withProperties({
label: loc.learnAboutPostgresClients,
- url: 'http://example.com', // TODO link to documentation
+ url: 'https://docs.microsoft.com/azure/postgresql/concepts-connection-libraries',
}).component();
- content.addItem(
- this.modelView.modelBuilder.flexContainer().withItems([info, link]).withLayout({ flexWrap: 'wrap' }).component(),
- { CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } });
+ 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': '25px' } });
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
content.addItem(this.keyValueContainer.container);
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
index 0bb2b326c1..b3753a485b 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts
@@ -9,11 +9,8 @@ import * as loc from '../../../localizedConstants';
import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel';
import { PostgresOverviewPage } from './postgresOverviewPage';
-import { PostgresComputeStoragePage } from './postgresComputeStoragePage';
import { PostgresConnectionStringsPage } from './postgresConnectionStringsPage';
-import { PostgresBackupPage } from './postgresBackupPage';
import { PostgresPropertiesPage } from './postgresPropertiesPage';
-import { PostgresNetworkingPage } from './postgresNetworkingPage';
import { Dashboard } from '../../components/dashboard';
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
@@ -25,11 +22,8 @@ export class PostgresDashboard extends Dashboard {
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
- const computeStoragePage = new PostgresComputeStoragePage(modelView);
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
- const backupPage = new PostgresBackupPage(modelView);
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
- const networkingPage = new PostgresNetworkingPage(modelView);
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);
@@ -38,16 +32,9 @@ export class PostgresDashboard extends Dashboard {
{
title: loc.settings,
tabs: [
- computeStoragePage.tab,
connectionStringsPage.tab,
- backupPage.tab,
propertiesPage.tab
]
- }, {
- title: loc.security,
- tabs: [
- networkingPage.tab
- ]
},
{
title: loc.supportAndTroubleshooting,
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresDiagnoseAndSolveProblemsPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresDiagnoseAndSolveProblemsPage.ts
index 120b16f5f0..8137f9cc0d 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresDiagnoseAndSolveProblemsPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresDiagnoseAndSolveProblemsPage.ts
@@ -51,7 +51,7 @@ export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
troubleshootButton.onDidClick(() => {
process.env['POSTGRES_SERVER_NAMESPACE'] = this._postgresModel.namespace;
process.env['POSTGRES_SERVER_NAME'] = this._postgresModel.name;
- vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arc'), true, 'postgres/tsg100-troubleshoot-postgres');
+ vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arcDataServices'), true, 'postgres/tsg100-troubleshoot-postgres');
});
content.addItem(troubleshootButton);
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
index 5ef316c396..809bc5675c 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts
@@ -7,11 +7,11 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles, ResourceType } from '../../../constants';
-import { DuskyObjectModelsDatabase, DuskyObjectModelsDatabaseServiceArcPayload, V1Pod } from '../../../controller/generated/dusky/api';
+import { DuskyObjectModelsDatabase, V1Pod, DuskyObjectModelsDatabaseServiceArcPayload } from '../../../controller/generated/dusky/api';
import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel, PodRole } from '../../../models/postgresModel';
-import { promptForResourceDeletion } from '../../../common/utils';
+import { promptForResourceDeletion, promptAndConfirmPassword } from '../../../common/utils';
export class PostgresOverviewPage extends DashboardPage {
private propertiesLoading?: azdata.LoadingComponent;
@@ -28,7 +28,6 @@ export class PostgresOverviewPage extends DashboardPage {
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();
@@ -178,10 +177,11 @@ export class PostgresOverviewPage extends DashboardPage {
let name;
try {
name = await vscode.window.showInputBox({ prompt: loc.databaseName });
- if (name === undefined) { return; }
- const db: DuskyObjectModelsDatabase = { name: name }; // TODO support other options (sharded, owner)
- await this._postgresModel.createDatabase(db);
- vscode.window.showInformationMessage(loc.databaseCreated(db.name ?? ''));
+ if (name) {
+ const db: DuskyObjectModelsDatabase = { name: name }; // TODO support other options (sharded, owner)
+ await this._postgresModel.createDatabase(db);
+ vscode.window.showInformationMessage(loc.databaseCreated(db.name ?? ''));
+ }
} catch (error) {
vscode.window.showErrorMessage(loc.databaseCreationFailed(name ?? '', error));
} finally {
@@ -198,15 +198,16 @@ export class PostgresOverviewPage extends DashboardPage {
resetPasswordButton.onDidClick(async () => {
resetPasswordButton.enabled = false;
try {
- const password = await vscode.window.showInputBox({ prompt: loc.newPassword, password: true });
- if (password === undefined) { return; }
- await this._postgresModel.update(s => {
- s.arc = s.arc ?? new DuskyObjectModelsDatabaseServiceArcPayload();
- s.arc.servicePassword = password;
- });
- vscode.window.showInformationMessage(loc.passwordReset(this._postgresModel.fullName));
+ const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
+ if (password) {
+ await this._postgresModel.update(s => {
+ s.arc = s.arc ?? new DuskyObjectModelsDatabaseServiceArcPayload();
+ s.arc.servicePassword = password;
+ });
+ vscode.window.showInformationMessage(loc.passwordReset);
+ }
} catch (error) {
- vscode.window.showErrorMessage(loc.passwordResetFailed(this._postgresModel.fullName, error));
+ vscode.window.showErrorMessage(loc.passwordResetFailed);
} finally {
resetPasswordButton.enabled = true;
}
@@ -289,7 +290,7 @@ export class PostgresOverviewPage extends DashboardPage {
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.coordinatorEndpoint, value: `postgresql://postgres@${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 ?? '' },
diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
index 5271209bc8..ad154fd27f 100644
--- a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
+++ b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts
@@ -18,7 +18,6 @@ export class PostgresPropertiesPage extends DashboardPage {
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()));
}
@@ -78,7 +77,7 @@ export class PostgresPropertiesPage extends DashboardPage {
private refresh() {
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
- const connectionString = `postgresql://postgres:${this._postgresModel.password}@${endpoint.ip}:${endpoint.port}`;
+ const connectionString = `postgresql://postgres@${endpoint.ip}:${endpoint.port}`;
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
this.keyValueContainer?.refresh([
@@ -86,7 +85,7 @@ export class PostgresPropertiesPage extends DashboardPage {
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.nodeConfiguration, this._postgresModel.configuration),
new TextKeyValue(loc.postgresVersion, this._postgresModel.service?.spec?.engine?.version?.toString() ?? ''),
new TextKeyValue(loc.resourceGroup, registration?.resourceGroupName ?? ''),
new TextKeyValue(loc.subscriptionId, registration?.subscriptionId ?? '')