mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Use custom dialog for prompting MIAA connection info (#12316)
* Use custom dialog for prompting MIAA connection info * disable inputs * Update strings
This commit is contained in:
@@ -208,3 +208,7 @@ export function parseIpAndPort(address: string): { ip: string, port: string } {
|
|||||||
port: sections[1]
|
port: sections[1]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createCredentialId(controllerId: string, resourceType: string, instanceName: string): string {
|
||||||
|
return `${controllerId}::${resourceType}::${instanceName}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import * as vscode from 'vscode';
|
|||||||
|
|
||||||
export const refreshActionId = 'arc.refresh';
|
export const refreshActionId = 'arc.refresh';
|
||||||
|
|
||||||
|
export const credentialNamespace = 'arcCredentials';
|
||||||
|
|
||||||
export interface IconPath {
|
export interface IconPath {
|
||||||
dark: string;
|
dark: string;
|
||||||
light: string;
|
light: string;
|
||||||
|
|||||||
@@ -73,8 +73,10 @@ export const indirect = localize('arc.indirect', "Indirect");
|
|||||||
export const loading = localize('arc.loading', "Loading...");
|
export const loading = localize('arc.loading', "Loading...");
|
||||||
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
||||||
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
||||||
|
export function connectToSql(name: string): string { return localize('arc.connectToSql', "Connect to SQL instance - Azure Arc ({0})", name); }
|
||||||
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
||||||
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
||||||
|
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
|
||||||
export const controllerName = localize('arc.controllerName', "Name");
|
export const controllerName = localize('arc.controllerName', "Name");
|
||||||
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
||||||
export const username = localize('arc.username', "Username");
|
export const username = localize('arc.username', "Username");
|
||||||
@@ -149,6 +151,7 @@ export function openDashboardFailed(error: any): string { return localize('arc.o
|
|||||||
export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, getErrorMessage(error)); }
|
export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, getErrorMessage(error)); }
|
||||||
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
|
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
|
||||||
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
|
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
|
||||||
|
export function connectToSqlFailed(serverName: string, error: any): string { return localize('arc.connectToSqlFailed', "Could not connect to MIAA Instance {0}. {1}", serverName, getErrorMessage(error)); }
|
||||||
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import * as azdata from 'azdata';
|
|||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { Deferred } from '../common/promise';
|
import { Deferred } from '../common/promise';
|
||||||
import { UserCancelledError } from '../common/utils';
|
import { createCredentialId, parseIpAndPort, UserCancelledError } from '../common/utils';
|
||||||
|
import { credentialNamespace } from '../constants';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
import { ConnectToSqlDialog } from '../ui/dialogs/connectSqlDialog';
|
||||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerModel, Registration } from './controllerModel';
|
import { ControllerModel, Registration } from './controllerModel';
|
||||||
import { ResourceModel } from './resourceModel';
|
import { ResourceModel } from './resourceModel';
|
||||||
@@ -155,83 +157,55 @@ export class MiaaModel extends ResourceModel {
|
|||||||
if (this._connectionProfile) {
|
if (this._connectionProfile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let connection: azdata.connection.ConnectionProfile | azdata.connection.Connection | undefined;
|
|
||||||
|
|
||||||
|
const ipAndPort = parseIpAndPort(this.config?.status.externalEndpoint || '');
|
||||||
|
let connectionProfile: azdata.IConnectionProfile | undefined = {
|
||||||
|
serverName: `${ipAndPort.ip},${ipAndPort.port}`,
|
||||||
|
databaseName: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
connectionName: '',
|
||||||
|
userName: 'sa',
|
||||||
|
password: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: undefined,
|
||||||
|
saveProfile: true,
|
||||||
|
id: '',
|
||||||
|
groupId: undefined,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have the ID stored then try to retrieve the password from previous connections
|
||||||
if (this.info.connectionId) {
|
if (this.info.connectionId) {
|
||||||
try {
|
try {
|
||||||
const connections = await azdata.connection.getConnections();
|
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
|
||||||
const existingConnection = connections.find(conn => conn.connectionId === this.info.connectionId);
|
const credentials = await credentialProvider.readCredential(createCredentialId(this._controllerModel.info.id, this.info.resourceType, this.info.name));
|
||||||
if (existingConnection) {
|
if (credentials.password) {
|
||||||
const credentials = await azdata.connection.getCredentials(this.info.connectionId);
|
// Try to connect to verify credentials are still valid
|
||||||
if (credentials) {
|
connectionProfile.password = credentials.password;
|
||||||
existingConnection.options['password'] = credentials.password;
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
connection = existingConnection;
|
if (!result.connected) {
|
||||||
} else {
|
vscode.window.showErrorMessage(loc.connectToSqlFailed(connectionProfile.serverName, result.errorMessage));
|
||||||
// We need the password so prompt the user for it
|
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
|
||||||
const connectionProfile: azdata.IConnectionProfile = {
|
connectToSqlDialog.showDialog(connectionProfile);
|
||||||
serverName: existingConnection.options['serverName'],
|
connectionProfile = await connectToSqlDialog.waitForClose();
|
||||||
databaseName: existingConnection.options['databaseName'],
|
|
||||||
authenticationType: existingConnection.options['authenticationType'],
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
connectionName: '',
|
|
||||||
userName: existingConnection.options['user'],
|
|
||||||
password: '',
|
|
||||||
savePassword: false,
|
|
||||||
groupFullName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
id: '',
|
|
||||||
groupId: undefined,
|
|
||||||
options: existingConnection.options
|
|
||||||
};
|
|
||||||
connection = await azdata.connection.openConnectionDialog(['MSSQL'], connectionProfile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore - the connection may not necessarily exist anymore and in that case we'll just reprompt for a connection
|
console.warn(`Unexpected error fetching password for MIAA instance ${err}`);
|
||||||
|
// ignore - something happened fetching the password so just reprompt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connection) {
|
if (!connectionProfile?.password) {
|
||||||
// We need the password so prompt the user for it
|
// Need to prompt user for password since we don't have one stored
|
||||||
const connectionProfile: azdata.IConnectionProfile = {
|
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
|
||||||
// TODO chgagnon fill in external IP and port
|
connectToSqlDialog.showDialog(connectionProfile);
|
||||||
// serverName: (this.registration.externalIp && this.registration.externalPort) ? `${this.registration.externalIp},${this.registration.externalPort}` : '',
|
connectionProfile = await connectToSqlDialog.waitForClose();
|
||||||
serverName: '',
|
|
||||||
databaseName: '',
|
|
||||||
authenticationType: 'SqlLogin',
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
connectionName: '',
|
|
||||||
userName: 'sa',
|
|
||||||
password: '',
|
|
||||||
savePassword: true,
|
|
||||||
groupFullName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
id: '',
|
|
||||||
groupId: undefined,
|
|
||||||
options: {}
|
|
||||||
};
|
|
||||||
// Weren't able to load the existing connection so prompt user for new one
|
|
||||||
connection = await azdata.connection.openConnectionDialog(['MSSQL'], connectionProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection) {
|
if (connectionProfile) {
|
||||||
const profile = {
|
this.updateConnectionProfile(connectionProfile);
|
||||||
// The option name might be different here based on where it came from
|
|
||||||
serverName: connection.options['serverName'] || connection.options['server'],
|
|
||||||
databaseName: connection.options['databaseName'] || connection.options['database'],
|
|
||||||
authenticationType: connection.options['authenticationType'],
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
connectionName: '',
|
|
||||||
userName: connection.options['user'],
|
|
||||||
password: connection.options['password'],
|
|
||||||
savePassword: false,
|
|
||||||
groupFullName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
id: connection.connectionId,
|
|
||||||
groupId: undefined,
|
|
||||||
options: connection.options
|
|
||||||
};
|
|
||||||
this.updateConnectionProfile(profile);
|
|
||||||
} else {
|
} else {
|
||||||
throw new UserCancelledError();
|
throw new UserCancelledError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ControllerInfo } from 'arc';
|
import { ControllerInfo, ResourceInfo } from 'arc';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@@ -74,6 +74,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
|
|
||||||
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
||||||
protected id!: string;
|
protected id!: string;
|
||||||
|
protected resources: ResourceInfo[] = [];
|
||||||
|
|
||||||
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
||||||
super();
|
super();
|
||||||
@@ -82,6 +83,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
|
|
||||||
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||||
this.id = controllerInfo?.id ?? uuid();
|
this.id = controllerInfo?.id ?? uuid();
|
||||||
|
this.resources = controllerInfo?.resources ?? [];
|
||||||
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
this.dialog.registerContent(async (view) => {
|
this.dialog.registerContent(async (view) => {
|
||||||
this.modelBuilder = view.modelBuilder;
|
this.modelBuilder = view.modelBuilder;
|
||||||
@@ -168,7 +170,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
|||||||
name: this.nameInputBox.value ?? '',
|
name: this.nameInputBox.value ?? '',
|
||||||
username: this.usernameInputBox.value,
|
username: this.usernameInputBox.value,
|
||||||
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
||||||
resources: []
|
resources: this.resources
|
||||||
};
|
};
|
||||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
try {
|
try {
|
||||||
|
|||||||
136
extensions/arc/src/ui/dialogs/connectSqlDialog.ts
Normal file
136
extensions/arc/src/ui/dialogs/connectSqlDialog.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 vscode from 'vscode';
|
||||||
|
import { Deferred } from '../../common/promise';
|
||||||
|
import { createCredentialId } from '../../common/utils';
|
||||||
|
import { credentialNamespace } from '../../constants';
|
||||||
|
import * as loc from '../../localizedConstants';
|
||||||
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
|
import { MiaaModel } from '../../models/miaaModel';
|
||||||
|
import { InitializingComponent } from '../components/initializingComponent';
|
||||||
|
|
||||||
|
export class ConnectToSqlDialog extends InitializingComponent {
|
||||||
|
private modelBuilder!: azdata.ModelBuilder;
|
||||||
|
|
||||||
|
private serverNameInputBox!: azdata.InputBoxComponent;
|
||||||
|
private usernameInputBox!: azdata.InputBoxComponent;
|
||||||
|
private passwordInputBox!: azdata.InputBoxComponent;
|
||||||
|
private rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
|
||||||
|
private _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
|
||||||
|
|
||||||
|
constructor(private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDialog(connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
|
||||||
|
const dialog = azdata.window.createModelViewDialog(loc.connectToSql(this._miaaModel.info.name));
|
||||||
|
dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
|
dialog.registerContent(async view => {
|
||||||
|
this.modelBuilder = view.modelBuilder;
|
||||||
|
|
||||||
|
this.serverNameInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
value: connectionProfile?.serverName,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
value: connectionProfile?.userName,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
inputType: 'password',
|
||||||
|
value: connectionProfile?.password
|
||||||
|
})
|
||||||
|
.component();
|
||||||
|
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||||
|
.withProperties<azdata.CheckBoxProperties>({
|
||||||
|
label: loc.rememberPassword,
|
||||||
|
checked: connectionProfile?.savePassword
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let formModel = this.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
component: this.serverNameInputBox,
|
||||||
|
title: loc.serverEndpoint,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.usernameInputBox,
|
||||||
|
title: loc.username,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.passwordInputBox,
|
||||||
|
title: loc.password,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.rememberPwCheckBox,
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: ''
|
||||||
|
}]).withLayout({ width: '100%' }).component();
|
||||||
|
await view.initializeModel(formModel);
|
||||||
|
this.serverNameInputBox.focus();
|
||||||
|
this.initialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.registerCloseValidator(async () => await this.validate());
|
||||||
|
dialog.okButton.label = loc.connect;
|
||||||
|
dialog.cancelButton.label = loc.cancel;
|
||||||
|
azdata.window.openDialog(dialog);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(): Promise<boolean> {
|
||||||
|
if (!this.serverNameInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const connectionProfile: azdata.IConnectionProfile = {
|
||||||
|
serverName: this.serverNameInputBox.value,
|
||||||
|
databaseName: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
connectionName: '',
|
||||||
|
userName: this.usernameInputBox.value,
|
||||||
|
password: this.passwordInputBox.value,
|
||||||
|
savePassword: !!this.rememberPwCheckBox.checked,
|
||||||
|
groupFullName: undefined,
|
||||||
|
saveProfile: true,
|
||||||
|
id: '',
|
||||||
|
groupId: undefined,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
|
if (result.connected) {
|
||||||
|
connectionProfile.id = result.connectionId;
|
||||||
|
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
|
||||||
|
if (connectionProfile.savePassword) {
|
||||||
|
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name), connectionProfile.password);
|
||||||
|
} else {
|
||||||
|
await credentialProvider.deleteCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name));
|
||||||
|
}
|
||||||
|
this._completionPromise.resolve(connectionProfile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vscode.window.showErrorMessage(loc.connectToSqlFailed(this.serverNameInputBox.value, result.errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCancel(): void {
|
||||||
|
this._completionPromise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public waitForClose(): Promise<azdata.IConnectionProfile | undefined> {
|
||||||
|
return this._completionPromise.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user