mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 18:46:36 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
89
src/sql/parts/connection/common/connection.contribution.ts
Normal file
89
src/sql/parts/connection/common/connection.contribution.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
|
||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { DashboardEditor } from 'sql/parts/dashboard/dashboardEditor';
|
||||
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
|
||||
import { ClearRecentConnectionsAction } from 'sql/parts/connection/common/connectionActions';
|
||||
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { AddServerGroupAction, AddServerAction } from 'sql/parts/registeredServer/viewlet/connectionTreeAction';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionGalleryService, ExtensionGalleryService);
|
||||
registerSingleton(IExtensionTipsService, ExtensionTipsService);
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
|
||||
// Connection Dashboard registration
|
||||
const dashboardEditorDescriptor = new EditorDescriptor(
|
||||
DashboardEditor.ID,
|
||||
'Dashboard',
|
||||
'sql/parts/dashboard/dashboardEditor',
|
||||
'DashboardEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(dashboardEditorDescriptor, [new SyncDescriptor(DashboardInput)]);
|
||||
|
||||
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
|
||||
|
||||
// Connection Actions
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
ClearRecentConnectionsAction,
|
||||
ClearRecentConnectionsAction.ID,
|
||||
ClearRecentConnectionsAction.LABEL
|
||||
),
|
||||
ClearRecentConnectionsAction.LABEL
|
||||
);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
AddServerGroupAction,
|
||||
AddServerGroupAction.ID,
|
||||
AddServerGroupAction.LABEL
|
||||
),
|
||||
AddServerGroupAction.LABEL
|
||||
);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
AddServerAction,
|
||||
AddServerAction.ID,
|
||||
AddServerAction.LABEL
|
||||
),
|
||||
AddServerAction.LABEL
|
||||
);
|
||||
|
||||
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': 'connection',
|
||||
'title': 'Connection',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'sql.maxRecentConnections': {
|
||||
'type': 'number',
|
||||
'default': 25,
|
||||
'description': localize('sql.maxRecentConnectionsDescription', 'The maximum number of recently used connections to store in the connection list.')
|
||||
},
|
||||
'sql.defaultEngine': {
|
||||
'type': 'string',
|
||||
'description': localize('sql.defaultEngineDescription', 'Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection.'),
|
||||
'default': 'MSSQL'
|
||||
},
|
||||
}
|
||||
});
|
||||
27
src/sql/parts/connection/common/connection.ts
Normal file
27
src/sql/parts/connection/common/connection.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// ------------------------------- < Cancel Connect Request > ---------------------------------------
|
||||
|
||||
/**
|
||||
* Cancel connect request message format
|
||||
*/
|
||||
export class CancelConnectParams {
|
||||
/**
|
||||
* URI identifying the owner of the connection
|
||||
*/
|
||||
public ownerUri: string;
|
||||
}
|
||||
|
||||
// ------------------------------- </ Cancel Connect Request > --------------------------------------
|
||||
|
||||
// ------------------------------- < Disconnect Request > -------------------------------------------
|
||||
|
||||
// Disconnect request message format
|
||||
export class DisconnectParams {
|
||||
// URI identifying the owner of the connection
|
||||
public ownerUri: string;
|
||||
}
|
||||
|
||||
// ------------------------------- </ Disconnect Request > ------------------------------------------
|
||||
59
src/sql/parts/connection/common/connectionActions.ts
Normal file
59
src/sql/parts/connection/common/connectionActions.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
|
||||
/**
|
||||
* Locates the active editor and calls runQuery() on the editor if it is a QueryEditor.
|
||||
*/
|
||||
export class ClearRecentConnectionsAction extends Action {
|
||||
|
||||
public static ID = 'clearRecentConnectionsAction';
|
||||
public static LABEL = nls.localize('ClearRecentlyUsedLabel', 'Clear Recent Connections List');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IQuickOpenService private _quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let self = this;
|
||||
return self.promptToClearRecentConnectionsList().then(result => {
|
||||
if (result) {
|
||||
self._connectionManagementService.clearRecentConnectionsList();
|
||||
self._messageService.show(Severity.Info, nls.localize('ClearedRecentConnections', 'Recent connections list cleared'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private promptToClearRecentConnectionsList(): TPromise<boolean> {
|
||||
const self = this;
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
let choices: { key, value }[] = [
|
||||
{ key: nls.localize('yes', 'Yes'), value: true },
|
||||
{ key: nls.localize('no', 'No'), value: false }
|
||||
];
|
||||
|
||||
self._quickOpenService.pick(choices.map(x => x.key), { placeHolder: nls.localize('ClearRecentlyUsedLabel', 'Clear Recent Connections List'), ignoreFocusLost: true }).then((choice) => {
|
||||
let confirm = choices.find(x => x.key === choice);
|
||||
resolve(confirm && confirm.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
543
src/sql/parts/connection/common/connectionConfig.ts
Normal file
543
src/sql/parts/connection/common/connectionConfig.ts
Normal file
@@ -0,0 +1,543 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as Constants from './constants';
|
||||
import * as Utils from './utils';
|
||||
import { IConnectionProfile, IConnectionProfileStore } from './interfaces';
|
||||
import { IConnectionConfig } from './iconnectionConfig';
|
||||
import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup';
|
||||
import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IConfigurationValue as TConfigurationValue } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConnectionProfile } from './connectionProfile';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import * as data from 'data';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export interface ISaveGroupResult {
|
||||
groups: IConnectionProfileGroup[];
|
||||
newGroupId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements connection profile file storage.
|
||||
*/
|
||||
export class ConnectionConfig implements IConnectionConfig {
|
||||
|
||||
private _providerCapabilitiesMap: { [providerName: string]: data.DataProtocolServerCapabilities };
|
||||
private _providerCachedCapabilitiesMap: { [providerName: string]: data.DataProtocolServerCapabilities };
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public constructor(
|
||||
private _configurationEditService: IConfigurationEditingService,
|
||||
private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
private _capabilitiesService: ICapabilitiesService,
|
||||
private _cachedMetadata?: data.DataProtocolServerCapabilities[]
|
||||
) {
|
||||
this._providerCapabilitiesMap = {};
|
||||
this._providerCachedCapabilitiesMap = {};
|
||||
}
|
||||
|
||||
public setCachedMetadata(cachedMetaData: data.DataProtocolServerCapabilities[]): void {
|
||||
this._cachedMetadata = cachedMetaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns connection groups from user and workspace settings.
|
||||
*/
|
||||
public getAllGroups(): IConnectionProfileGroup[] {
|
||||
|
||||
let allGroups: IConnectionProfileGroup[] = [];
|
||||
let userGroups = this.getConfiguration(Constants.connectionGroupsArrayName).user as IConnectionProfileGroup[];
|
||||
let workspaceGroups = this.getConfiguration(Constants.connectionGroupsArrayName).workspace as IConnectionProfileGroup[];
|
||||
|
||||
if (userGroups) {
|
||||
|
||||
if (workspaceGroups) {
|
||||
userGroups = userGroups.filter(x => workspaceGroups.find(f => this.isSameGroupName(f, x)) === undefined);
|
||||
allGroups = allGroups.concat(workspaceGroups);
|
||||
}
|
||||
allGroups = allGroups.concat(userGroups);
|
||||
}
|
||||
allGroups = allGroups.map(g => {
|
||||
if (g.parentId === '' || !g.parentId) {
|
||||
g.parentId = undefined;
|
||||
}
|
||||
return g;
|
||||
});
|
||||
return allGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the capabilities for given provider name. First tries to get it from capabilitiesService and if it's not registered yet,
|
||||
* Gets the data from the metadata stored in the config
|
||||
* @param providerName Provider Name
|
||||
*/
|
||||
public getCapabilities(providerName: string): data.DataProtocolServerCapabilities {
|
||||
let result: data.DataProtocolServerCapabilities;
|
||||
|
||||
if (providerName in this._providerCapabilitiesMap) {
|
||||
result = this._providerCapabilitiesMap[providerName];
|
||||
} else {
|
||||
let capabilities = this._capabilitiesService.getCapabilities();
|
||||
if (capabilities) {
|
||||
let providerCapabilities = capabilities.find(c => c.providerName === providerName);
|
||||
if (providerCapabilities) {
|
||||
this._providerCapabilitiesMap[providerName] = providerCapabilities;
|
||||
result = providerCapabilities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result && this._cachedMetadata) {
|
||||
if (providerName in this._providerCachedCapabilitiesMap) {
|
||||
result = this._providerCachedCapabilitiesMap[providerName];
|
||||
} else {
|
||||
let metaDataFromConfig = this._cachedMetadata;
|
||||
if (metaDataFromConfig) {
|
||||
let providerCapabilities = metaDataFromConfig.find(m => m.providerName === providerName);
|
||||
this._providerCachedCapabilitiesMap[providerName] = providerCapabilities;
|
||||
result = providerCapabilities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
/**
|
||||
* Add a new connection to the connection config.
|
||||
*/
|
||||
public addConnection(profile: IConnectionProfile): Promise<IConnectionProfile> {
|
||||
return new Promise<IConnectionProfile>((resolve, reject) => {
|
||||
if (profile.saveProfile) {
|
||||
this.addGroupFromProfile(profile).then(groupId => {
|
||||
let profiles = this._workspaceConfigurationService.lookup<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
|
||||
if (!profiles) {
|
||||
profiles = [];
|
||||
}
|
||||
|
||||
let providerCapabilities = this.getCapabilities(profile.providerName);
|
||||
let connectionProfile = this.getConnectionProfileInstance(profile, groupId);
|
||||
let newProfile = ConnectionProfile.convertToProfileStore(providerCapabilities, connectionProfile);
|
||||
|
||||
// Remove the profile if already set
|
||||
var sameProfileInList = profiles.find(value => {
|
||||
let providerCapabilities = this.getCapabilities(value.providerName);
|
||||
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, providerCapabilities);
|
||||
return providerConnectionProfile.matches(connectionProfile);
|
||||
});
|
||||
if (sameProfileInList) {
|
||||
profiles = profiles.filter(value => value !== sameProfileInList);
|
||||
newProfile.id = sameProfileInList.id;
|
||||
connectionProfile.id = sameProfileInList.id;
|
||||
}
|
||||
|
||||
profiles.push(newProfile);
|
||||
|
||||
this.writeConfiguration(Constants.connectionsArrayName, profiles).then(() => {
|
||||
resolve(connectionProfile);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getConnectionProfileInstance(profile: IConnectionProfile, groupId: string): ConnectionProfile {
|
||||
let connectionProfile = profile as ConnectionProfile;
|
||||
let providerCapabilities = this.getCapabilities(profile.providerName);
|
||||
if (connectionProfile === undefined) {
|
||||
connectionProfile = new ConnectionProfile(providerCapabilities, profile);
|
||||
}
|
||||
connectionProfile.groupId = groupId;
|
||||
return connectionProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
*Returns group id
|
||||
* @param groupName
|
||||
*/
|
||||
public addGroupFromProfile(profile: IConnectionProfile): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (profile.groupId && profile.groupId !== Utils.defaultGroupId) {
|
||||
resolve(profile.groupId);
|
||||
} else {
|
||||
let groups = this._workspaceConfigurationService.lookup<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
|
||||
let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined);
|
||||
groups = result.groups;
|
||||
|
||||
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
|
||||
resolve(result.newGroupId);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*Returns group id
|
||||
* @param groupName
|
||||
*/
|
||||
public addGroup(profileGroup: IConnectionProfileGroup): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (profileGroup.id) {
|
||||
resolve(profileGroup.id);
|
||||
} else {
|
||||
let groups = this._workspaceConfigurationService.lookup<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
|
||||
let sameNameGroup = groups ? groups.find(group => group.name === profileGroup.name) : undefined;
|
||||
if (sameNameGroup) {
|
||||
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
|
||||
reject(errMessage);
|
||||
} else {
|
||||
let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description);
|
||||
groups = result.groups;
|
||||
|
||||
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
|
||||
resolve(result.newGroupId);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getConnectionProfilesForTarget(configTarget: ConfigurationTarget): IConnectionProfileStore[] {
|
||||
let configs = this.getConfiguration(Constants.connectionsArrayName);
|
||||
let profiles: IConnectionProfileStore[];
|
||||
if (configs) {
|
||||
if (configTarget === ConfigurationTarget.USER) {
|
||||
profiles = <IConnectionProfileStore[]>configs.user;
|
||||
} else if (configTarget === ConfigurationTarget.WORKSPACE) {
|
||||
profiles = <IConnectionProfileStore[]>configs.workspace;
|
||||
}
|
||||
if (profiles) {
|
||||
if(this.fixConnectionIds(profiles)) {
|
||||
this.writeConfiguration(Constants.connectionsArrayName, profiles, configTarget);
|
||||
}
|
||||
} else {
|
||||
profiles = [];
|
||||
}
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace duplicate ids with new ones. Sets id for the profiles without id
|
||||
* @param profiles
|
||||
*/
|
||||
public fixConnectionIds(profiles: IConnectionProfileStore[]): boolean {
|
||||
let idsCache: { [label: string]: boolean } = {};
|
||||
let changed: boolean = false;
|
||||
for (var index = 0; index < profiles.length; index++) {
|
||||
var profile = profiles[index];
|
||||
if (!profile.id) {
|
||||
profile.id = generateUuid();
|
||||
changed = true;
|
||||
}
|
||||
if (profile.id in idsCache) {
|
||||
profile.id = generateUuid();
|
||||
changed = true;
|
||||
}
|
||||
idsCache[profile.id] = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all connections in the connection config. Connections returned
|
||||
* are sorted first by whether they were found in the user/workspace settings,
|
||||
* and next alphabetically by profile/server name.
|
||||
*/
|
||||
public getConnections(getWorkspaceConnections: boolean): ConnectionProfile[] {
|
||||
let profiles: IConnectionProfileStore[] = [];
|
||||
//TODO: have to figure out how to sort connections for all provider
|
||||
// Read from user settings
|
||||
|
||||
let userProfiles: IConnectionProfileStore[] = this.getConnectionProfilesForTarget(ConfigurationTarget.USER);
|
||||
if (userProfiles !== undefined) {
|
||||
profiles = profiles.concat(userProfiles);
|
||||
}
|
||||
|
||||
if (getWorkspaceConnections) {
|
||||
// Read from workspace settings
|
||||
|
||||
let workspaceProfiles: IConnectionProfileStore[] = this.getConnectionProfilesForTarget(ConfigurationTarget.WORKSPACE);
|
||||
if (workspaceProfiles !== undefined) {
|
||||
profiles = profiles.concat(workspaceProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
let connectionProfiles = profiles.map(p => {
|
||||
let capabilitiesForProvider = this.getCapabilities(p.providerName);
|
||||
|
||||
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(p, capabilitiesForProvider);
|
||||
providerConnectionProfile.setServerCapabilities(capabilitiesForProvider);
|
||||
this._capabilitiesService.onProviderRegisteredEvent((serverCapabilities) => {
|
||||
providerConnectionProfile.onProviderRegistered(serverCapabilities);
|
||||
});
|
||||
|
||||
return providerConnectionProfile;
|
||||
});
|
||||
|
||||
return connectionProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a connection profile from settings.
|
||||
*/
|
||||
public deleteConnection(profile: ConnectionProfile): Promise<void> {
|
||||
// Get all connections in the settings
|
||||
let profiles = this._workspaceConfigurationService.lookup<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
|
||||
// Remove the profile from the connections
|
||||
profiles = profiles.filter(value => {
|
||||
let providerCapabilities = this.getCapabilities(value.providerName);
|
||||
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, providerCapabilities);
|
||||
return providerConnectionProfile.getOptionsKey() !== profile.getOptionsKey();
|
||||
});
|
||||
|
||||
// Write connections back to settings
|
||||
return this.writeConfiguration(Constants.connectionsArrayName, profiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a group and all its child connections and groups from settings.
|
||||
* Fails if writing to settings fails.
|
||||
*/
|
||||
public deleteGroup(group: ConnectionProfileGroup): Promise<void> {
|
||||
let connections = ConnectionProfileGroup.getConnectionsInGroup(group);
|
||||
let subgroups = ConnectionProfileGroup.getSubgroups(group);
|
||||
// Add selected group to subgroups list
|
||||
subgroups.push(group);
|
||||
// Get all connections in the settings
|
||||
let profiles = this._workspaceConfigurationService.lookup<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
|
||||
// Remove the profiles from the connections
|
||||
profiles = profiles.filter(value => {
|
||||
let providerCapabilities = this.getCapabilities(value.providerName);
|
||||
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, providerCapabilities);
|
||||
return !connections.some((val) => val.getOptionsKey() === providerConnectionProfile.getOptionsKey());
|
||||
});
|
||||
|
||||
// Get all groups in the settings
|
||||
let groups = this._workspaceConfigurationService.lookup<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
|
||||
// Remove subgroups in the settings
|
||||
groups = groups.filter((grp) => {
|
||||
return !subgroups.some((item) => item.id === grp.id);
|
||||
});
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.writeConfiguration(Constants.connectionsArrayName, profiles).then(() => {
|
||||
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
|
||||
resolve();
|
||||
}).catch(() => reject());
|
||||
}).catch(() => reject());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the source group under the target group.
|
||||
*/
|
||||
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
|
||||
let groups = this._workspaceConfigurationService.lookup<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
|
||||
groups = groups.map(g => {
|
||||
if (g.id === source.id) {
|
||||
g.parentId = target.id;
|
||||
}
|
||||
return g;
|
||||
});
|
||||
return this.writeConfiguration(Constants.connectionGroupsArrayName, groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if connection can be moved to another group
|
||||
*/
|
||||
public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean {
|
||||
let profiles = this.getConnections(true);
|
||||
let existingProfile = profiles.find(p => p.getConnectionInfoId() === profile.getConnectionInfoId()
|
||||
&& p.groupId === newGroupID);
|
||||
return existingProfile === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the connection under the target group with the new ID.
|
||||
*/
|
||||
private changeGroupIdForConnectionInSettings(profile: ConnectionProfile, newGroupID: string, target: ConfigurationTarget = ConfigurationTarget.USER): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let profiles = target === ConfigurationTarget.USER ? this._workspaceConfigurationService.lookup<IConnectionProfileStore[]>(Constants.connectionsArrayName).user :
|
||||
this._workspaceConfigurationService.lookup<IConnectionProfileStore[]>(Constants.connectionsArrayName).workspace;
|
||||
if (profiles) {
|
||||
let providerCapabilities = this.getCapabilities(profile.providerName);
|
||||
if (profile.parent && profile.parent.id === Constants.unsavedGroupId) {
|
||||
profile.groupId = newGroupID;
|
||||
profiles.push(ConnectionProfile.convertToProfileStore(providerCapabilities, profile));
|
||||
} else {
|
||||
profiles.forEach((value) => {
|
||||
let configProf = ConnectionProfile.createFromStoredProfile(value, providerCapabilities);
|
||||
if (configProf.getOptionsKey() === profile.getOptionsKey()) {
|
||||
value.groupId = newGroupID;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.writeConfiguration(Constants.connectionsArrayName, profiles, target).then(result => {
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the connection under the target group with the new ID.
|
||||
*/
|
||||
public changeGroupIdForConnection(profile: ConnectionProfile, newGroupID: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this.canChangeConnectionConfig(profile, newGroupID)) {
|
||||
// Same connection already exists in this group
|
||||
reject('Same connection already exists in the group');
|
||||
} else {
|
||||
this.changeGroupIdForConnectionInSettings(profile, newGroupID, ConfigurationTarget.USER).then(result1 => {
|
||||
this.changeGroupIdForConnectionInSettings(profile, newGroupID, ConfigurationTarget.WORKSPACE).then(result2 => {
|
||||
resolve();
|
||||
}).catch(error2 => {
|
||||
reject(error2);
|
||||
});
|
||||
}).catch(error1 => {
|
||||
reject(error1);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public saveGroup(groups: IConnectionProfileGroup[], groupFullName: string, color: string, description: string): ISaveGroupResult {
|
||||
let result: ISaveGroupResult;
|
||||
let groupNames = ConnectionProfileGroup.getGroupFullNameParts(groupFullName);
|
||||
result = this.saveGroupInTree(groups, undefined, groupNames, color, description, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public editGroup(source: ConnectionProfileGroup): Promise<void> {
|
||||
let groups = this._workspaceConfigurationService.lookup<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
|
||||
let sameNameGroup = groups ? groups.find(group => group.name === source.name && group.id !== source.id) : undefined;
|
||||
if (sameNameGroup) {
|
||||
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
|
||||
return Promise.reject(errMessage);
|
||||
}
|
||||
groups = groups.map(g => {
|
||||
if (g.id === source.id) {
|
||||
g.name = source.name;
|
||||
g.description = source.description;
|
||||
g.color = source.color;
|
||||
source.isRenamed = false;
|
||||
}
|
||||
return g;
|
||||
});
|
||||
return this.writeConfiguration(Constants.connectionGroupsArrayName, groups);
|
||||
}
|
||||
|
||||
private isSameGroupName(group1: IConnectionProfileGroup, group2: IConnectionProfileGroup): boolean {
|
||||
let sameGroupName: boolean = false;
|
||||
if (group1 && group2) {
|
||||
sameGroupName = ((!group1.name && !group2.name) || group1.name.toUpperCase() === group2.name.toUpperCase()) &&
|
||||
(group1.parentId === group2.parentId || (!group1.parentId && !group2.parentId));
|
||||
}
|
||||
return sameGroupName;
|
||||
}
|
||||
|
||||
private saveGroupInTree(groupTree: IConnectionProfileGroup[], parentId: string, groupNames: string[], color: string, description: string, index: number): ISaveGroupResult {
|
||||
if (!groupTree) {
|
||||
groupTree = [];
|
||||
}
|
||||
let newGroupId: string;
|
||||
|
||||
if (index < groupNames.length) {
|
||||
let groupName: string = groupNames[index];
|
||||
let newGroup: IConnectionProfileGroup = {
|
||||
name: groupName,
|
||||
id: undefined,
|
||||
parentId: parentId,
|
||||
color: color,
|
||||
description: description
|
||||
};
|
||||
let found = groupTree.find(group => this.isSameGroupName(group, newGroup));
|
||||
if (found) {
|
||||
if (index === groupNames.length - 1) {
|
||||
newGroupId = found.id;
|
||||
//Found the group full name
|
||||
} else {
|
||||
let result = this.saveGroupInTree(groupTree, found.id, groupNames, color, description, index + 1);
|
||||
groupTree = result.groups;
|
||||
newGroupId = result.newGroupId;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (ConnectionProfileGroup.isRoot(newGroup.name)) {
|
||||
newGroup.id = Utils.defaultGroupId;
|
||||
} else {
|
||||
newGroup.id = generateUuid();
|
||||
}
|
||||
let result = this.saveGroupInTree(groupTree, newGroup.id, groupNames, color, description, index + 1);
|
||||
newGroupId = result.newGroupId;
|
||||
groupTree = result.groups;
|
||||
groupTree.push(newGroup);
|
||||
if (index === groupNames.length - 1) {
|
||||
newGroupId = newGroup.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
let groupResult: ISaveGroupResult = {
|
||||
groups: groupTree,
|
||||
newGroupId: newGroupId
|
||||
};
|
||||
return groupResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all profiles from the parsed settings file.
|
||||
* This is public for testing only.
|
||||
* @param parsedSettingsFile an object representing the parsed contents of the settings file.
|
||||
* @returns the set of connection profiles found in the parsed settings file.
|
||||
*/
|
||||
private getConfiguration(key: string): TConfigurationValue<IConnectionProfileStore[] | IConnectionProfileGroup[] | data.DataProtocolServerCapabilities[]> {
|
||||
let configs: TConfigurationValue<IConnectionProfileStore[] | IConnectionProfileGroup[] | data.DataProtocolServerCapabilities[]>;
|
||||
|
||||
configs = this._workspaceConfigurationService.lookup<IConnectionProfileStore[] | IConnectionProfileGroup[] | data.DataProtocolServerCapabilities[]>(key);
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace existing profiles in the settings file with a new set of profiles.
|
||||
* @param parsedSettingsFile an object representing the parsed contents of the settings file.
|
||||
* @param profiles the set of profiles to insert into the settings file.
|
||||
*/
|
||||
private writeConfiguration(
|
||||
key: string,
|
||||
profiles: IConnectionProfileStore[] | IConnectionProfileGroup[] | data.DataProtocolServerCapabilities[],
|
||||
target: ConfigurationTarget = ConfigurationTarget.USER): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let configValue: IConfigurationValue = {
|
||||
key: key,
|
||||
value: profiles
|
||||
};
|
||||
this._configurationEditService.writeConfiguration(target, configValue).then(result => {
|
||||
this._workspaceConfigurationService.reloadConfiguration().then(() => {
|
||||
resolve();
|
||||
});
|
||||
}, (error => {
|
||||
reject(error);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
40
src/sql/parts/connection/common/connectionGlobalStatus.ts
Normal file
40
src/sql/parts/connection/common/connectionGlobalStatus.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { ConnectionSummary } from 'data';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import * as LocalizedConstants from 'sql/parts/connection/common/localizedConstants';
|
||||
|
||||
// Status when making connections from the viewlet
|
||||
export class ConnectionGlobalStatus {
|
||||
|
||||
private _displayTime: number = 5000; // (in ms)
|
||||
|
||||
constructor(
|
||||
@IStatusbarService private _statusBarService: IStatusbarService
|
||||
) {
|
||||
}
|
||||
|
||||
public setStatusToConnected(connectionSummary: ConnectionSummary): void {
|
||||
if (this._statusBarService) {
|
||||
let text: string;
|
||||
let connInfo: string = connectionSummary.serverName;
|
||||
if (connInfo) {
|
||||
if (connectionSummary.databaseName && connectionSummary.databaseName !== '') {
|
||||
connInfo = connInfo + ' : ' + connectionSummary.databaseName;
|
||||
} else {
|
||||
connInfo = connInfo + ' : ' + '<default>';
|
||||
}
|
||||
text = LocalizedConstants.onDidConnectMessage + ' ' + connInfo;
|
||||
}
|
||||
this._statusBarService.setStatusMessage(text, this._displayTime);
|
||||
}
|
||||
}
|
||||
|
||||
public setStatusToDisconnected(fileUri: string): void {
|
||||
if (this._statusBarService) {
|
||||
this._statusBarService.setStatusMessage(LocalizedConstants.onDidDisconnectMessage, this._displayTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/sql/parts/connection/common/connectionInfo.ts
Normal file
36
src/sql/parts/connection/common/connectionInfo.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Interfaces = require('./interfaces');
|
||||
|
||||
/**
|
||||
* Sets sensible defaults for key connection properties, especially
|
||||
* if connection to Azure
|
||||
*
|
||||
* @export connectionInfo/fixupConnectionCredentials
|
||||
* @param {Interfaces.IConnectionCredentials} connCreds connection to be fixed up
|
||||
* @returns {Interfaces.IConnectionCredentials} the updated connection
|
||||
*/
|
||||
export function fixupConnectionCredentials(connCreds: Interfaces.IConnectionProfile): Interfaces.IConnectionProfile {
|
||||
if (!connCreds.serverName) {
|
||||
connCreds.serverName = '';
|
||||
}
|
||||
|
||||
if (!connCreds.databaseName) {
|
||||
connCreds.databaseName = '';
|
||||
}
|
||||
|
||||
if (!connCreds.userName) {
|
||||
connCreds.userName = '';
|
||||
}
|
||||
|
||||
if (!connCreds.password) {
|
||||
connCreds.password = '';
|
||||
}
|
||||
return connCreds;
|
||||
}
|
||||
|
||||
341
src/sql/parts/connection/common/connectionManagement.ts
Normal file
341
src/sql/parts/connection/common/connectionManagement.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event from 'vs/base/common/event';
|
||||
import data = require('data');
|
||||
import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ISelectionData } from 'data';
|
||||
import { ConnectionManagementInfo } from './connectionManagementInfo';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.connections';
|
||||
|
||||
export interface IConnectionsViewlet extends IViewlet {
|
||||
search(text: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the actions that could happen after connecting is complete
|
||||
*/
|
||||
export interface IConnectionCompletionOptions {
|
||||
/**
|
||||
* save the connection to MRU and settings (only save to setting if profile.saveProfile is set to true)
|
||||
*/
|
||||
saveTheConnection: boolean;
|
||||
|
||||
/**
|
||||
* open the dashboard after connection is complete
|
||||
*/
|
||||
showDashboard: boolean;
|
||||
|
||||
/**
|
||||
* Parameters to be used if connecting from an editor
|
||||
*/
|
||||
params: INewConnectionParams;
|
||||
|
||||
/**
|
||||
* Open the connection dialog if connection fails
|
||||
*/
|
||||
showConnectionDialogOnError: boolean;
|
||||
|
||||
/**
|
||||
* Open the connection firewall rule dialog if connection fails
|
||||
*/
|
||||
showFirewallRuleOnError: boolean;
|
||||
}
|
||||
|
||||
export interface IConnectionResult {
|
||||
connected: boolean;
|
||||
errorMessage: string;
|
||||
errorCode: number;
|
||||
errorHandled?: boolean;
|
||||
}
|
||||
|
||||
export interface IConnectionCallbacks {
|
||||
onConnectStart(): void;
|
||||
onConnectReject(error?: string): void;
|
||||
onConnectSuccess(params?: INewConnectionParams): void;
|
||||
onDisconnect(): void;
|
||||
}
|
||||
|
||||
export const SERVICE_ID = 'connectionManagementService';
|
||||
|
||||
export const IConnectionManagementService = createDecorator<IConnectionManagementService>(SERVICE_ID);
|
||||
|
||||
export interface IConnectionManagementService {
|
||||
_serviceBrand: any;
|
||||
|
||||
// Event Emitters
|
||||
onAddConnectionProfile: Event<IConnectionProfile>;
|
||||
onDeleteConnectionProfile: Event<void>;
|
||||
onConnect: Event<IConnectionParams>;
|
||||
onDisconnect: Event<IConnectionParams>;
|
||||
onConnectionChanged: Event<IConnectionParams>;
|
||||
onLanguageFlavorChanged: Event<data.DidChangeLanguageFlavorParams>;
|
||||
|
||||
/**
|
||||
* Opens the connection dialog to create new connection
|
||||
*/
|
||||
showConnectionDialog(params?: INewConnectionParams, model?: IConnectionProfile, error?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Opens the add server group dialog
|
||||
*/
|
||||
showCreateServerGroupDialog(callbacks?: IServerGroupDialogCallbacks): Promise<void>;
|
||||
|
||||
/**
|
||||
* Opens the edit server group dialog
|
||||
*/
|
||||
showEditServerGroupDialog(group: ConnectionProfileGroup): Promise<void>;
|
||||
|
||||
/**
|
||||
* Load the password and opens a new connection
|
||||
*/
|
||||
connect(connection: IConnectionProfile, uri: string, options?: IConnectionCompletionOptions, callbacks?: IConnectionCallbacks): Promise<IConnectionResult>;
|
||||
|
||||
/**
|
||||
* Opens a new connection and save the profile in settings
|
||||
*/
|
||||
connectAndSaveProfile(connection: IConnectionProfile, uri: string, options?: IConnectionCompletionOptions, callbacks?: IConnectionCallbacks): Promise<IConnectionResult>;
|
||||
|
||||
/**
|
||||
* Finds existing connection for given profile and purpose is any exists.
|
||||
* The purpose is connection by default
|
||||
*/
|
||||
findExistingConnection(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): ConnectionProfile;
|
||||
|
||||
/**
|
||||
* If there's already a connection for given profile and purpose, returns the ownerUri for the connection
|
||||
* otherwise tries to make a connection and returns the owner uri when connection is complete
|
||||
* The purpose is connection by default
|
||||
*/
|
||||
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string>;
|
||||
|
||||
/**
|
||||
* Adds the successful connection to MRU and send the connection error back to the connection handler for failed connections
|
||||
*/
|
||||
onConnectionComplete(handle: number, connectionInfoSummary: data.ConnectionInfoSummary): void;
|
||||
|
||||
onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
||||
|
||||
onConnectionChangedNotification(handle: number, changedConnInfo: data.ChangedConnectionInfo);
|
||||
|
||||
getConnectionGroups(): ConnectionProfileGroup[];
|
||||
|
||||
getRecentConnections(): ConnectionProfile[];
|
||||
|
||||
clearRecentConnectionsList(): void;
|
||||
|
||||
getActiveConnections(): ConnectionProfile[];
|
||||
|
||||
saveProfileGroup(profile: IConnectionProfileGroup): Promise<string>;
|
||||
|
||||
changeGroupIdForConnectionGroup(source: IConnectionProfileGroup, target: IConnectionProfileGroup): Promise<void>;
|
||||
|
||||
changeGroupIdForConnection(source: ConnectionProfile, targetGroupName: string): Promise<void>;
|
||||
|
||||
deleteConnection(connection: ConnectionProfile): Promise<boolean>;
|
||||
|
||||
deleteConnectionGroup(group: ConnectionProfileGroup): Promise<boolean>;
|
||||
|
||||
getAdvancedProperties(): data.ConnectionOption[];
|
||||
|
||||
getConnectionId(connectionProfile: IConnectionProfile): string;
|
||||
|
||||
getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string;
|
||||
|
||||
isConnected(fileUri: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the connection profile is connected
|
||||
*/
|
||||
isProfileConnected(connectionProfile: IConnectionProfile): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the connection profile is connecting
|
||||
*/
|
||||
isProfileConnecting(connectionProfile: IConnectionProfile): boolean;
|
||||
|
||||
isRecent(connectionProfile: ConnectionProfile): boolean;
|
||||
|
||||
isConnected(fileUri: string, connectionProfile?: ConnectionProfile): boolean;
|
||||
|
||||
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
|
||||
|
||||
disconnect(connection: ConnectionProfile): Promise<void>;
|
||||
|
||||
disconnect(ownerUri: string): Promise<void>;
|
||||
|
||||
addSavedPassword(connectionProfile: IConnectionProfile): Promise<IConnectionProfile>;
|
||||
|
||||
listDatabases(connectionUri: string): Thenable<data.ListDatabasesResult>;
|
||||
|
||||
/**
|
||||
* Register a connection provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: data.ConnectionProvider): void;
|
||||
|
||||
editGroup(group: ConnectionProfileGroup): Promise<void>;
|
||||
|
||||
getConnectionProfile(fileUri: string): IConnectionProfile;
|
||||
|
||||
getConnectionInfo(fileUri: string): ConnectionManagementInfo;
|
||||
|
||||
/**
|
||||
* Cancels the connection
|
||||
*/
|
||||
cancelConnection(connection: IConnectionProfile): Thenable<boolean>;
|
||||
|
||||
/**
|
||||
* Changes the database for an active connection
|
||||
*/
|
||||
changeDatabase(connectionUri: string, databaseName: string): Thenable<boolean>;
|
||||
|
||||
/**
|
||||
* Cancels the connection for the editor
|
||||
*/
|
||||
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
|
||||
|
||||
showDashboard(connection: ConnectionProfile): Thenable<boolean>;
|
||||
|
||||
closeDashboard(uri: string): void;
|
||||
|
||||
getProviderIdFromUri(ownerUri: string): string;
|
||||
|
||||
hasRegisteredServers(): boolean;
|
||||
|
||||
getCapabilities(providerName: string): data.DataProtocolServerCapabilities;
|
||||
|
||||
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
|
||||
|
||||
/**
|
||||
* Sends a notification that the language flavor for a given URI has changed.
|
||||
* For SQL, this would be the specific SQL implementation being used.
|
||||
*
|
||||
* @param {string} uri the URI of the resource whose language has changed
|
||||
* @param {string} language the base language
|
||||
* @param {string} flavor the specific language flavor that's been set
|
||||
*
|
||||
* @memberof IConnectionManagementService
|
||||
*/
|
||||
doChangeLanguageFlavor(uri: string, language: string, flavor: string): void;
|
||||
|
||||
/**
|
||||
* Ensures that a default language flavor is set for a URI, if none has already been defined.
|
||||
* @param {string} uri document identifier
|
||||
* @memberof ConnectionManagementService
|
||||
*/
|
||||
ensureDefaultLanguageFlavor(uri: string): void;
|
||||
|
||||
/**
|
||||
* Gets an array of all known providers.
|
||||
*
|
||||
* @returns {string[]} An array of provider names
|
||||
* @memberof IConnectionManagementService
|
||||
*/
|
||||
getProviderNames(): string[];
|
||||
|
||||
/**
|
||||
* Refresh the IntelliSense cache for the connection with the given URI
|
||||
*/
|
||||
rebuildIntelliSenseCache(uri: string): Thenable<void>;
|
||||
}
|
||||
|
||||
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');
|
||||
export interface IConnectionDialogService {
|
||||
_serviceBrand: any;
|
||||
showDialog(connectionManagementService: IConnectionManagementService, params: INewConnectionParams, model: IConnectionProfile, error?: string): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface IServerGroupDialogCallbacks {
|
||||
onAddGroup(groupName: string): void;
|
||||
onClose(): void;
|
||||
}
|
||||
export const IServerGroupController = createDecorator<IServerGroupController>('serverGroupController');
|
||||
export interface IServerGroupController {
|
||||
_serviceBrand: any;
|
||||
showCreateGroupDialog(connectionManagementService: IConnectionManagementService, callbacks?: IServerGroupDialogCallbacks): TPromise<void>;
|
||||
showEditGroupDialog(connectionManagementService: IConnectionManagementService, group: ConnectionProfileGroup): TPromise<void>;
|
||||
}
|
||||
|
||||
export const IErrorMessageService = createDecorator<IErrorMessageService>('errorMessageService');
|
||||
export interface IErrorMessageService {
|
||||
_serviceBrand: any;
|
||||
showDialog(severity: Severity, headerTitle: string, message: string): void;
|
||||
}
|
||||
|
||||
export enum ServiceOptionType {
|
||||
string = 0,
|
||||
multistring = 1,
|
||||
password = 2,
|
||||
number = 3,
|
||||
category = 4,
|
||||
boolean = 5
|
||||
}
|
||||
|
||||
export enum ConnectionOptionSpecialType {
|
||||
serverName = 0,
|
||||
databaseName = 1,
|
||||
authType = 2,
|
||||
userName = 3,
|
||||
password = 4,
|
||||
appName = 5
|
||||
}
|
||||
|
||||
export enum RunQueryOnConnectionMode {
|
||||
none = 0,
|
||||
executeQuery = 1,
|
||||
executeCurrentQuery = 2,
|
||||
estimatedQueryPlan = 3
|
||||
}
|
||||
|
||||
export interface INewConnectionParams {
|
||||
connectionType: ConnectionType;
|
||||
input?: IConnectableInput;
|
||||
runQueryOnCompletion?: RunQueryOnConnectionMode;
|
||||
querySelection?: ISelectionData;
|
||||
showDashboard?: boolean;
|
||||
}
|
||||
|
||||
export interface IConnectableInput {
|
||||
uri: string;
|
||||
onConnectStart(): void;
|
||||
onConnectReject(error?: string): void;
|
||||
onConnectSuccess(params?: INewConnectionParams): void;
|
||||
onDisconnect(): void;
|
||||
}
|
||||
|
||||
export enum ConnectionType {
|
||||
default = 0,
|
||||
editor = 1
|
||||
}
|
||||
|
||||
export enum MetadataType {
|
||||
Table = 0,
|
||||
View = 1,
|
||||
SProc = 2,
|
||||
Function = 3
|
||||
}
|
||||
|
||||
export enum TaskStatus {
|
||||
notStarted = 0,
|
||||
inProgress = 1,
|
||||
succeeded = 2,
|
||||
succeededWithWarning = 3,
|
||||
failed = 4,
|
||||
canceled = 5
|
||||
}
|
||||
|
||||
export interface IConnectionParams {
|
||||
connectionUri: string;
|
||||
connectionProfile: IConnectionProfile;
|
||||
}
|
||||
73
src/sql/parts/connection/common/connectionManagementInfo.ts
Normal file
73
src/sql/parts/connection/common/connectionManagementInfo.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||
import * as data from 'data';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
/**
|
||||
* Information for a document's connection. Exported for testing purposes.
|
||||
*/
|
||||
export class ConnectionManagementInfo {
|
||||
/**
|
||||
* Connection GUID returned from the service host
|
||||
*/
|
||||
public connectionId: string;
|
||||
|
||||
|
||||
public providerId: string;
|
||||
|
||||
/**
|
||||
* Credentials used to connect
|
||||
*/
|
||||
public connectionProfile: ConnectionProfile;
|
||||
|
||||
/**
|
||||
* Callback for when a connection notification is received.
|
||||
*/
|
||||
public connectHandler: (result: boolean, errorMessage?: string, errorCode?: number) => void;
|
||||
|
||||
/**
|
||||
* Information about the SQL Server instance.
|
||||
*/
|
||||
//public serverInfo: ConnectionContracts.ServerInfo;
|
||||
|
||||
/**
|
||||
* Timer for tracking extension connection time.
|
||||
*/
|
||||
public extensionTimer: StopWatch;
|
||||
|
||||
/**
|
||||
* Timer for tracking service connection time.
|
||||
*/
|
||||
public serviceTimer: StopWatch;
|
||||
|
||||
/**
|
||||
* Timer for tracking intelliSense activation time.
|
||||
*/
|
||||
public intelliSenseTimer: StopWatch;
|
||||
|
||||
/**
|
||||
* Whether the connection is in the process of connecting.
|
||||
*/
|
||||
public connecting: boolean;
|
||||
|
||||
/**
|
||||
* Whether the connection should be deleted after connection is complete.
|
||||
*/
|
||||
public deleted: boolean;
|
||||
|
||||
/**
|
||||
* Information about the connected server.
|
||||
*/
|
||||
serverInfo: data.ServerInfo;
|
||||
|
||||
/**
|
||||
* Owner uri assigned to the connection
|
||||
*/
|
||||
public ownerUri: string;
|
||||
}
|
||||
1311
src/sql/parts/connection/common/connectionManagementService.ts
Normal file
1311
src/sql/parts/connection/common/connectionManagementService.ts
Normal file
File diff suppressed because it is too large
Load Diff
227
src/sql/parts/connection/common/connectionProfile.ts
Normal file
227
src/sql/parts/connection/common/connectionProfile.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ConnectionProfileGroup } from './connectionProfileGroup';
|
||||
import * as data from 'data';
|
||||
import { ProviderConnectionInfo } from 'sql/parts/connection/common/providerConnectionInfo';
|
||||
import * as interfaces from 'sql/parts/connection/common/interfaces';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
// Concrete implementation of the IConnectionProfile interface
|
||||
|
||||
/**
|
||||
* A concrete implementation of an IConnectionProfile with support for profile creation and validation
|
||||
*/
|
||||
export class ConnectionProfile extends ProviderConnectionInfo implements interfaces.IConnectionProfile {
|
||||
|
||||
public parent: ConnectionProfileGroup = null;
|
||||
private _id: string;
|
||||
public savePassword: boolean;
|
||||
private _groupName: string;
|
||||
public groupId: string;
|
||||
public saveProfile: boolean;
|
||||
|
||||
public isDisconnecting: boolean = false;
|
||||
public constructor(serverCapabilities?: data.DataProtocolServerCapabilities, model?: interfaces.IConnectionProfile) {
|
||||
super(serverCapabilities, model);
|
||||
if (model) {
|
||||
this.groupId = model.groupId;
|
||||
this.groupFullName = model.groupFullName;
|
||||
this.savePassword = model.savePassword;
|
||||
this.saveProfile = model.saveProfile;
|
||||
this._id = model.id;
|
||||
} else {
|
||||
//Default for a new connection
|
||||
this.savePassword = false;
|
||||
this.saveProfile = true;
|
||||
this._groupName = ConnectionProfile.RootGroupName;
|
||||
this._id = generateUuid();
|
||||
}
|
||||
|
||||
this.options['groupId'] = this.groupId;
|
||||
this.options['databaseDisplayName'] = this.databaseName;
|
||||
}
|
||||
|
||||
public matches(other: interfaces.IConnectionProfile): boolean {
|
||||
return other
|
||||
&& this.providerName === other.providerName
|
||||
&& equalsIgnoreCase(this.serverName, other.serverName)
|
||||
&& equalsIgnoreCase(this.databaseName, other.databaseName)
|
||||
&& equalsIgnoreCase(this.userName, other.userName)
|
||||
&& equalsIgnoreCase(this.options['databaseDisplayName'], other.options['databaseDisplayName'])
|
||||
&& this.authenticationType === other.authenticationType
|
||||
&& this.groupId === other.groupId;
|
||||
}
|
||||
|
||||
public generateNewId() {
|
||||
this._id = generateUuid();
|
||||
}
|
||||
|
||||
public getParent(): ConnectionProfileGroup {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
if (!this._id) {
|
||||
this._id = generateUuid();
|
||||
}
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public set id(value: string) {
|
||||
this._id = value;
|
||||
}
|
||||
|
||||
public get groupFullName(): string {
|
||||
return this._groupName;
|
||||
}
|
||||
|
||||
public set groupFullName(value: string) {
|
||||
this._groupName = value;
|
||||
}
|
||||
|
||||
public get isAddedToRootGroup(): boolean {
|
||||
return (this._groupName === ConnectionProfile.RootGroupName);
|
||||
}
|
||||
|
||||
public clone(): ConnectionProfile {
|
||||
let instance = new ConnectionProfile(this._serverCapabilities, this);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public cloneWithNewId(): ConnectionProfile {
|
||||
let instance = this.clone();
|
||||
instance.generateNewId();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public cloneWithDatabase(databaseName: string): ConnectionProfile {
|
||||
let instance = this.cloneWithNewId();
|
||||
instance.databaseName = databaseName;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static readonly RootGroupName: string = '/';
|
||||
|
||||
public withoutPassword(): ConnectionProfile {
|
||||
let clone = this.clone();
|
||||
clone.password = '';
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||
* This key uniquely identifies a connection in a group
|
||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
*/
|
||||
public getOptionsKey(): string {
|
||||
let id = super.getOptionsKey();
|
||||
let databaseDisplayName: string = this.options['databaseDisplayName'];
|
||||
if (databaseDisplayName) {
|
||||
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
|
||||
}
|
||||
|
||||
return id + ProviderConnectionInfo.idSeparator + 'group' + ProviderConnectionInfo.nameValueSeparator + this.groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique id for the connection that doesn't include the group name
|
||||
*/
|
||||
public getConnectionInfoId(): string {
|
||||
return super.getOptionsKey();
|
||||
}
|
||||
|
||||
public onProviderRegistered(serverCapabilities: data.DataProtocolServerCapabilities): void {
|
||||
if (serverCapabilities.providerName === this.providerName) {
|
||||
this.setServerCapabilities(serverCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
public toIConnectionProfile(): interfaces.IConnectionProfile {
|
||||
let result: interfaces.IConnectionProfile = {
|
||||
serverName: this.serverName,
|
||||
databaseName: this.databaseName,
|
||||
authenticationType: this.authenticationType,
|
||||
getOptionsKey: undefined,
|
||||
matches: undefined,
|
||||
groupId: this.groupId,
|
||||
groupFullName: this.groupFullName,
|
||||
password: this.password,
|
||||
providerName: this.providerName,
|
||||
savePassword: this.savePassword,
|
||||
userName: this.userName,
|
||||
options: this.options,
|
||||
saveProfile: this.saveProfile,
|
||||
id: this.id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public toConnectionInfo(): data.ConnectionInfo {
|
||||
return {
|
||||
options: this.options
|
||||
};
|
||||
}
|
||||
|
||||
public static createFromStoredProfile(profile: interfaces.IConnectionProfileStore, serverCapabilities: data.DataProtocolServerCapabilities): ConnectionProfile {
|
||||
let connectionInfo = new ConnectionProfile(serverCapabilities, undefined);
|
||||
connectionInfo.options = profile.options;
|
||||
|
||||
// append group ID and original display name to build unique OE session ID
|
||||
connectionInfo.options = profile.options;
|
||||
connectionInfo.options['groupId'] = connectionInfo.groupId;
|
||||
connectionInfo.options['databaseDisplayName'] = connectionInfo.databaseName;
|
||||
|
||||
connectionInfo.groupId = profile.groupId;
|
||||
connectionInfo.providerName = profile.providerName;
|
||||
connectionInfo.saveProfile = true;
|
||||
connectionInfo.savePassword = profile.savePassword;
|
||||
connectionInfo.id = profile.id || generateUuid();
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
public static convertToConnectionProfile(serverCapabilities: data.DataProtocolServerCapabilities, conn: interfaces.IConnectionProfile): ConnectionProfile {
|
||||
if (conn) {
|
||||
let connectionProfile: ConnectionProfile = undefined;
|
||||
let connectionProfileInstance = conn as ConnectionProfile;
|
||||
if (connectionProfileInstance && conn instanceof ConnectionProfile) {
|
||||
connectionProfile = connectionProfileInstance;
|
||||
connectionProfile.setServerCapabilities(serverCapabilities);
|
||||
} else {
|
||||
connectionProfile = new ConnectionProfile(serverCapabilities, conn);
|
||||
}
|
||||
|
||||
return connectionProfile;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public static convertToProfileStore(
|
||||
serverCapabilities: data.DataProtocolServerCapabilities,
|
||||
connectionProfile: interfaces.IConnectionProfile): interfaces.IConnectionProfileStore {
|
||||
if (connectionProfile) {
|
||||
let connectionInfo = ConnectionProfile.convertToConnectionProfile(serverCapabilities, connectionProfile);
|
||||
let profile: interfaces.IConnectionProfileStore = {
|
||||
options: {},
|
||||
groupId: connectionProfile.groupId,
|
||||
providerName: connectionInfo.providerName,
|
||||
savePassword: connectionInfo.savePassword,
|
||||
id: connectionInfo.id
|
||||
};
|
||||
|
||||
profile.options = connectionInfo.options;
|
||||
|
||||
return profile;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
183
src/sql/parts/connection/common/connectionProfileGroup.ts
Normal file
183
src/sql/parts/connection/common/connectionProfileGroup.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ConnectionProfile } from './connectionProfile';
|
||||
|
||||
export interface IConnectionProfileGroup {
|
||||
id: string;
|
||||
parentId: string;
|
||||
name: string;
|
||||
color: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export class ConnectionProfileGroup implements IConnectionProfileGroup {
|
||||
|
||||
public children: ConnectionProfileGroup[];
|
||||
public connections: ConnectionProfile[];
|
||||
public parentId: string;
|
||||
private _isRenamed: boolean;
|
||||
public constructor(
|
||||
public name: string,
|
||||
public parent: ConnectionProfileGroup,
|
||||
public id: string,
|
||||
public color: string,
|
||||
public description: string
|
||||
) {
|
||||
this.parentId = parent ? parent.id : undefined;
|
||||
if (this.name === ConnectionProfileGroup.RootGroupName) {
|
||||
this.name = '';
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupNameSeparator: string = '/';
|
||||
public static RootGroupName: string = 'ROOT';
|
||||
|
||||
public toObject(): IConnectionProfileGroup {
|
||||
let subgroups = undefined;
|
||||
if (this.children) {
|
||||
subgroups = [];
|
||||
this.children.forEach((group) => {
|
||||
subgroups.push(group.toObject());
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, { name: this.name, id: this.id, parentId: this.parentId, children: subgroups, color: this.color, description: this.description });
|
||||
}
|
||||
|
||||
public get groupName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public get fullName(): string {
|
||||
let fullName: string = (this.id === 'root') ? undefined : this.name;
|
||||
if (this.parent) {
|
||||
let parentFullName = this.parent.fullName;
|
||||
if (parentFullName) {
|
||||
fullName = parentFullName + ConnectionProfileGroup.GroupNameSeparator + this.name;
|
||||
}
|
||||
}
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public get isRenamed(): boolean {
|
||||
return this._isRenamed;
|
||||
}
|
||||
|
||||
public set isRenamed(val: boolean) {
|
||||
this._isRenamed = val;
|
||||
}
|
||||
|
||||
public hasChildren(): boolean {
|
||||
if ((this.children && this.children.length > 0) || (this.connections && this.connections.length > 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getChildren(): any {
|
||||
let allChildren = [];
|
||||
|
||||
if (this.connections) {
|
||||
this.connections.forEach((conn) => {
|
||||
allChildren.push(conn);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.children) {
|
||||
this.children.forEach((group) => {
|
||||
allChildren.push(group);
|
||||
});
|
||||
}
|
||||
return allChildren;
|
||||
}
|
||||
|
||||
public equals(other: any): boolean {
|
||||
if (!(other instanceof ConnectionProfileGroup)) {
|
||||
return false;
|
||||
}
|
||||
return other.id === this.id;
|
||||
}
|
||||
|
||||
public addConnections(connections: ConnectionProfile[]): void {
|
||||
if (!this.connections) {
|
||||
this.connections = [];
|
||||
}
|
||||
connections.forEach((conn) => {
|
||||
this.connections = this.connections.filter((curConn) => { return curConn.id !== conn.id; });
|
||||
conn.parent = this;
|
||||
this.connections.push(conn);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public addGroups(groups: ConnectionProfileGroup[]): void {
|
||||
if (!this.children) {
|
||||
this.children = [];
|
||||
}
|
||||
groups.forEach((group) => {
|
||||
this.children = this.children.filter((grp) => { return group.id !== grp.id; });
|
||||
group.parent = this;
|
||||
this.children.push(group);
|
||||
});
|
||||
}
|
||||
|
||||
public getParent(): ConnectionProfileGroup {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public static getGroupFullNameParts(groupFullName: string): string[] {
|
||||
groupFullName = groupFullName ? groupFullName : '';
|
||||
let groupNames: string[] = groupFullName.split(ConnectionProfileGroup.GroupNameSeparator);
|
||||
groupNames = groupNames.filter(g => !!g);
|
||||
if (groupNames.length === 0) {
|
||||
groupNames.push('ROOT');
|
||||
} else if (groupNames[0].toUpperCase() !== 'ROOT') {
|
||||
groupNames.unshift('ROOT');
|
||||
}
|
||||
groupNames[0] = 'ROOT';
|
||||
return groupNames;
|
||||
}
|
||||
|
||||
public static isRoot(name: string): boolean {
|
||||
return (!name || name.toUpperCase() === ConnectionProfileGroup.RootGroupName ||
|
||||
name === ConnectionProfileGroup.GroupNameSeparator);
|
||||
}
|
||||
|
||||
public static sameGroupName(name1: string, name2: string): boolean {
|
||||
let sameGroupName: boolean =
|
||||
(!name1 && !name2) ||
|
||||
name1.toUpperCase() === name2.toUpperCase() ||
|
||||
(ConnectionProfileGroup.isRoot(name1) && ConnectionProfileGroup.isRoot(name2));
|
||||
|
||||
return sameGroupName;
|
||||
}
|
||||
|
||||
public static getConnectionsInGroup(group: ConnectionProfileGroup): ConnectionProfile[] {
|
||||
let connections = [];
|
||||
if (group.connections) {
|
||||
group.connections.forEach((con) => connections.push(con));
|
||||
}
|
||||
if (group.children) {
|
||||
group.children.forEach((subgroup) => {
|
||||
connections = connections.concat(this.getConnectionsInGroup(subgroup));
|
||||
});
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
|
||||
public static getSubgroups(group: ConnectionProfileGroup): ConnectionProfileGroup[] {
|
||||
let subgroups = [];
|
||||
if (group && group.children) {
|
||||
group.children.forEach((grp) => subgroups.push(grp));
|
||||
group.children.forEach((subgroup) => {
|
||||
subgroups = subgroups.concat(this.getSubgroups(subgroup));
|
||||
});
|
||||
}
|
||||
return subgroups;
|
||||
}
|
||||
}
|
||||
160
src/sql/parts/connection/common/connectionStatus.ts
Normal file
160
src/sql/parts/connection/common/connectionStatus.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { $, append, show, hide } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IConnectionManagementService, IConnectionParams } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { ConnectionStatusManager } from 'sql/parts/connection/common/connectionStatusManager';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
|
||||
enum ConnectionActivityStatus {
|
||||
Connected,
|
||||
Disconnected
|
||||
}
|
||||
|
||||
// Contains connection status for each editor
|
||||
class ConnectionStatusEditor {
|
||||
public connectionActivityStatus: ConnectionActivityStatus;
|
||||
public connectionProfile: IConnectionProfile;
|
||||
|
||||
constructor() {
|
||||
this.connectionActivityStatus = ConnectionActivityStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
// Connection status bar for editor
|
||||
export class ConnectionStatusbarItem implements IStatusbarItem {
|
||||
|
||||
private _element: HTMLElement;
|
||||
private _connectionElement: HTMLElement;
|
||||
private _connectionStatusEditors: { [connectionUri: string]: ConnectionStatusEditor };
|
||||
private _toDispose: IDisposable[];
|
||||
private _connectionStatusManager: ConnectionStatusManager;
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
) {
|
||||
this._connectionStatusEditors = {};
|
||||
this._connectionStatusManager = new ConnectionStatusManager(this._capabilitiesService);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
this._element = append(container, $('.connection-statusbar-item'));
|
||||
this._connectionElement = append(this._element, $('div.connection-statusbar-conninfo'));
|
||||
hide(this._connectionElement);
|
||||
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(
|
||||
this._connectionManagementService.onConnect((connectionUri: IConnectionParams) => this._onConnect(connectionUri)),
|
||||
this._connectionManagementService.onConnectionChanged((connectionUri: IConnectionParams) => this._onConnect(connectionUri)),
|
||||
this._connectionManagementService.onDisconnect((connectionUri: IConnectionParams) => this._onDisconnect(connectionUri)),
|
||||
this._editorGroupService.onEditorsChanged(() => this._onEditorsChanged()),
|
||||
this._editorGroupService.getStacksModel().onEditorClosed(event => this._onEditorClosed(event))
|
||||
);
|
||||
|
||||
return combinedDisposable(this._toDispose);
|
||||
}
|
||||
|
||||
private _onEditorClosed(event: IEditorCloseEvent): void {
|
||||
let uri = WorkbenchUtils.getEditorUri(event.editor);
|
||||
if (uri && uri in this._connectionStatusEditors) {
|
||||
this._updateStatus(uri, ConnectionActivityStatus.Disconnected, undefined);
|
||||
delete this._connectionStatusEditors[uri];
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorsChanged(): void {
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
|
||||
// Show active editor's query status
|
||||
if (uri && uri in this._connectionStatusEditors) {
|
||||
this._showStatus(uri);
|
||||
} else {
|
||||
hide(this._connectionElement);
|
||||
}
|
||||
} else {
|
||||
hide(this._connectionElement);
|
||||
}
|
||||
}
|
||||
|
||||
private _onConnect(connectionParams: IConnectionParams): void {
|
||||
if (!this._connectionStatusManager.isDefaultTypeUri(connectionParams.connectionUri)) {
|
||||
this._updateStatus(connectionParams.connectionUri, ConnectionActivityStatus.Connected, connectionParams.connectionProfile);
|
||||
}
|
||||
}
|
||||
|
||||
private _onDisconnect(connectionUri: IConnectionParams): void {
|
||||
if (!this._connectionStatusManager.isDefaultTypeUri(connectionUri.connectionUri)) {
|
||||
this._updateStatus(connectionUri.connectionUri, ConnectionActivityStatus.Disconnected, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Update connection status for the editor
|
||||
private _updateStatus(uri: string, newStatus: ConnectionActivityStatus, connectionProfile: IConnectionProfile) {
|
||||
if (uri) {
|
||||
if (!(uri in this._connectionStatusEditors)) {
|
||||
this._connectionStatusEditors[uri] = new ConnectionStatusEditor();
|
||||
}
|
||||
this._connectionStatusEditors[uri].connectionActivityStatus = newStatus;
|
||||
this._connectionStatusEditors[uri].connectionProfile = connectionProfile;
|
||||
this._showStatus(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide query status for active editor
|
||||
private _showStatus(uri: string): void {
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
switch (this._connectionStatusEditors[uri].connectionActivityStatus) {
|
||||
case ConnectionActivityStatus.Connected:
|
||||
this._setConnectionText(this._connectionStatusEditors[uri].connectionProfile);
|
||||
show(this._connectionElement);
|
||||
break;
|
||||
case ConnectionActivityStatus.Disconnected:
|
||||
hide(this._connectionElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set connection info to connection status bar
|
||||
private _setConnectionText(connectionProfile: IConnectionProfile): void {
|
||||
let text: string = connectionProfile.serverName;
|
||||
if (text) {
|
||||
if (connectionProfile.databaseName && connectionProfile.databaseName !== '') {
|
||||
text = text + ' : ' + connectionProfile.databaseName;
|
||||
} else {
|
||||
text = text + ' : ' + '<default>';
|
||||
}
|
||||
}
|
||||
|
||||
let tooltip: string =
|
||||
'Server name: ' + connectionProfile.serverName + '\r\n' +
|
||||
'Database name: ' + (connectionProfile.databaseName ? connectionProfile.databaseName : '<default>') + '\r\n';
|
||||
|
||||
if (connectionProfile.userName && connectionProfile.userName !== '') {
|
||||
tooltip = tooltip + 'Login name: ' + connectionProfile.userName + '\r\n';
|
||||
}
|
||||
|
||||
this._connectionElement.textContent = text;
|
||||
this._connectionElement.title = tooltip;
|
||||
}
|
||||
}
|
||||
212
src/sql/parts/connection/common/connectionStatusManager.ts
Normal file
212
src/sql/parts/connection/common/connectionStatusManager.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ConnectionManagementInfo } from './connectionManagementInfo';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||
import { IConnectionProfile } from './interfaces';
|
||||
import * as Utils from './utils';
|
||||
import * as data from 'data';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
export class ConnectionStatusManager {
|
||||
|
||||
private _connections: { [id: string]: ConnectionManagementInfo };
|
||||
private _providerCapabilitiesMap: { [providerName: string]: data.DataProtocolServerCapabilities };
|
||||
|
||||
constructor( @ICapabilitiesService private _capabilitiesService: ICapabilitiesService) {
|
||||
this._connections = {};
|
||||
this._providerCapabilitiesMap = {};
|
||||
}
|
||||
|
||||
public getCapabilities(providerName: string): data.DataProtocolServerCapabilities {
|
||||
let result: data.DataProtocolServerCapabilities;
|
||||
|
||||
if (providerName in this._providerCapabilitiesMap) {
|
||||
result = this._providerCapabilitiesMap[providerName];
|
||||
} else {
|
||||
let capabilities = this._capabilitiesService.getCapabilities();
|
||||
if (capabilities) {
|
||||
let providerCapabilities = capabilities.find(c => c.providerName === providerName);
|
||||
if (providerCapabilities) {
|
||||
this._providerCapabilitiesMap[providerName] = providerCapabilities;
|
||||
result = providerCapabilities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public findConnection(id: string): ConnectionManagementInfo {
|
||||
if (id in this._connections) {
|
||||
return this._connections[id];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public findConnectionProfile(connectionProfile: IConnectionProfile): ConnectionManagementInfo {
|
||||
let id = Utils.generateUri(connectionProfile);
|
||||
return this.findConnection(id);
|
||||
}
|
||||
|
||||
public hasConnection(id: string): Boolean {
|
||||
return !!this.findConnection(id);
|
||||
}
|
||||
|
||||
public deleteConnection(id: string): void {
|
||||
let info = this.findConnection(id);
|
||||
if (info) {
|
||||
for (let key in this._connections) {
|
||||
if (this._connections[key].connectionId === info.connectionId) {
|
||||
if (this._connections[key].connecting) {
|
||||
this._connections[key].deleted = true;
|
||||
} else {
|
||||
delete this._connections[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getConnectionProfile(id: string): ConnectionProfile {
|
||||
let connectionInfoForId = this.findConnection(id);
|
||||
return connectionInfoForId ? connectionInfoForId.connectionProfile : undefined;
|
||||
}
|
||||
|
||||
public addConnection(connection: IConnectionProfile, id: string): ConnectionManagementInfo {
|
||||
// Always create a copy and save that in the list
|
||||
let connectionProfile = new ConnectionProfile(this.getCapabilities(connection.providerName), connection);
|
||||
const self = this;
|
||||
let connectionInfo: ConnectionManagementInfo = new ConnectionManagementInfo();
|
||||
connectionInfo.providerId = connection.providerName;
|
||||
connectionInfo.extensionTimer = StopWatch.create();
|
||||
connectionInfo.intelliSenseTimer = StopWatch.create();
|
||||
connectionInfo.connectionProfile = connectionProfile;
|
||||
connectionInfo.connecting = true;
|
||||
self._connections[id] = connectionInfo;
|
||||
connectionInfo.serviceTimer = StopWatch.create();
|
||||
connectionInfo.ownerUri = id;
|
||||
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uri Remove connection from list of active connections
|
||||
*/
|
||||
public removeConnection(uri: string) {
|
||||
delete this._connections[uri];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after a connection is saved to settings. It's only for default url connections
|
||||
* which their id is generated from connection options. The group id is used in the generated id.
|
||||
* when the connection is stored, the group id get assigned to the profile and it can change the id
|
||||
* So for those kind of connections, we need to add the new id and the connection
|
||||
*/
|
||||
public updateConnectionProfile(connection: IConnectionProfile, id: string): string {
|
||||
let newId: string = id;
|
||||
let connectionInfo: ConnectionManagementInfo = this._connections[id];
|
||||
if (connectionInfo && connection) {
|
||||
if (this.isDefaultTypeUri(id)) {
|
||||
connectionInfo.connectionProfile.groupId = connection.groupId;
|
||||
newId = Utils.generateUri(connection);
|
||||
if (newId !== id) {
|
||||
this.deleteConnection(id);
|
||||
this._connections[newId] = connectionInfo;
|
||||
}
|
||||
}
|
||||
connectionInfo.connectionProfile.id = connection.id;
|
||||
}
|
||||
return newId;
|
||||
}
|
||||
|
||||
public onConnectionComplete(summary: data.ConnectionInfoSummary): ConnectionManagementInfo {
|
||||
let connection = this._connections[summary.ownerUri];
|
||||
connection.serviceTimer.stop();
|
||||
connection.connecting = false;
|
||||
connection.connectionId = summary.connectionId;
|
||||
connection.serverInfo = summary.serverInfo;
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates database name after connection is complete
|
||||
* @param summary connection summary
|
||||
*/
|
||||
public updateDatabaseName(summary: data.ConnectionInfoSummary): void {
|
||||
let connection = this._connections[summary.ownerUri];
|
||||
|
||||
//Check if the existing connection database name is different the one in the summary
|
||||
if (connection.connectionProfile.databaseName !== summary.connectionSummary.databaseName) {
|
||||
//Add the ownerUri with database name to the map if not already exists
|
||||
connection.connectionProfile.databaseName = summary.connectionSummary.databaseName;
|
||||
let prefix = Utils.getUriPrefix(summary.ownerUri);
|
||||
let ownerUriWithDbName = Utils.generateUriWithPrefix(connection.connectionProfile, prefix);
|
||||
if (!(ownerUriWithDbName in this._connections)) {
|
||||
this._connections[ownerUriWithDbName] = connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find an existing connection that's mapped with given the ownerUri
|
||||
* The purpose for this method is to find the connection given the ownerUri and find the original uri assigned to it. most of the times should be the same.
|
||||
* Only if the db name in the original uri is different than when connection is complete, we need to use the original uri
|
||||
* Returns the generated ownerUri for the connection profile if not existing connection found
|
||||
* @param ownerUri connection owner uri to find an existing connection
|
||||
* @param purpose purpose for the connection
|
||||
*/
|
||||
public getOriginalOwnerUri(ownerUri: string): string {
|
||||
let ownerUriToReturn: string = ownerUri;
|
||||
|
||||
let connectionStatusInfo = this.findConnection(ownerUriToReturn);
|
||||
if (connectionStatusInfo && connectionStatusInfo.ownerUri) {
|
||||
//The ownerUri in the connection status is the one service knows about so use that
|
||||
//To call the service for any operation
|
||||
ownerUriToReturn = connectionStatusInfo.ownerUri;
|
||||
}
|
||||
return ownerUriToReturn;
|
||||
}
|
||||
|
||||
public onConnectionChanged(changedConnInfo: data.ChangedConnectionInfo): IConnectionProfile {
|
||||
let connection = this._connections[changedConnInfo.connectionUri];
|
||||
if (connection && connection.connectionProfile) {
|
||||
connection.connectionProfile.serverName = changedConnInfo.connection.serverName;
|
||||
connection.connectionProfile.databaseName = changedConnInfo.connection.databaseName;
|
||||
connection.connectionProfile.userName = changedConnInfo.connection.userName;
|
||||
return connection.connectionProfile;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public isConnected(id: string): boolean {
|
||||
return (id in this._connections && this._connections[id].connectionId && !!this._connections[id].connectionId);
|
||||
}
|
||||
|
||||
public isConnecting(id: string): boolean {
|
||||
return (id in this._connections && this._connections[id].connecting);
|
||||
}
|
||||
|
||||
public isDefaultTypeUri(uri: string): boolean {
|
||||
return uri && uri.startsWith(Utils.uriPrefixes.default);
|
||||
}
|
||||
|
||||
public getProviderIdFromUri(ownerUri: string): string {
|
||||
let providerId: string = '';
|
||||
let connection = this.findConnection(ownerUri);
|
||||
if (connection) {
|
||||
providerId = connection.connectionProfile.providerName;
|
||||
}
|
||||
if (!providerId && this.isDefaultTypeUri(ownerUri)) {
|
||||
let optionsKey = ownerUri.replace(Utils.uriPrefixes.default, '');
|
||||
providerId = ConnectionProfile.getProviderFromOptionsKey(optionsKey);
|
||||
}
|
||||
return providerId;
|
||||
}
|
||||
}
|
||||
616
src/sql/parts/connection/common/connectionStore.ts
Normal file
616
src/sql/parts/connection/common/connectionStore.ts
Normal file
@@ -0,0 +1,616 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as Constants from './constants';
|
||||
import * as ConnInfo from './connectionInfo';
|
||||
import { ConnectionProfile } from '../common/connectionProfile';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { ICredentialsService } from 'sql/services/credentials/credentialsService';
|
||||
import { IConnectionConfig } from './iconnectionConfig';
|
||||
import { ConnectionConfig } from './connectionConfig';
|
||||
import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup';
|
||||
import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import * as data from 'data';
|
||||
|
||||
const MAX_CONNECTIONS_DEFAULT = 25;
|
||||
|
||||
/**
|
||||
* Manages the connections list including saved profiles and the most recently used connections
|
||||
*
|
||||
* @export
|
||||
* @class ConnectionStore
|
||||
*/
|
||||
export class ConnectionStore {
|
||||
private _memento: any;
|
||||
private _groupIdToFullNameMap: { [groupId: string]: string };
|
||||
private _groupFullNameToIdMap: { [groupId: string]: string };
|
||||
|
||||
constructor(
|
||||
private _storageService: IStorageService,
|
||||
private _context: Memento,
|
||||
private _configurationEditService: IConfigurationEditingService,
|
||||
private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
private _credentialService: ICredentialsService,
|
||||
private _capabilitiesService: ICapabilitiesService,
|
||||
private _connectionConfig?: IConnectionConfig
|
||||
) {
|
||||
|
||||
if (_context) {
|
||||
this._memento = this._context.getMemento(this._storageService, MementoScope.GLOBAL);
|
||||
}
|
||||
this._groupIdToFullNameMap = {};
|
||||
this._groupFullNameToIdMap = {};
|
||||
|
||||
if (!this._connectionConfig) {
|
||||
let cachedServerCapabilities = this.getCachedServerCapabilities();
|
||||
this._connectionConfig = new ConnectionConfig(this._configurationEditService,
|
||||
this._workspaceConfigurationService, this._capabilitiesService, cachedServerCapabilities);
|
||||
this._connectionConfig.setCachedMetadata(cachedServerCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
public static get CRED_PREFIX(): string { return 'Microsoft.SqlTools'; }
|
||||
public static get CRED_SEPARATOR(): string { return '|'; }
|
||||
public static get CRED_ID_PREFIX(): string { return 'id:'; }
|
||||
public static get CRED_ITEMTYPE_PREFIX(): string { return 'itemtype:'; }
|
||||
public static get CRED_PROFILE_USER(): string { return 'Profile'; }
|
||||
|
||||
public formatCredentialIdForCred(connectionProfile: IConnectionProfile): string {
|
||||
if (!connectionProfile) {
|
||||
throw new Error('Missing Connection which is required');
|
||||
}
|
||||
let itemTypeString: string = ConnectionStore.CRED_PROFILE_USER;
|
||||
return this.formatCredentialId(connectionProfile, itemTypeString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted credential usable for uniquely identifying a SQL Connection.
|
||||
* This string can be decoded but is not optimized for this.
|
||||
* @static
|
||||
* @param {IConnectionProfile} connectionProfile connection profile - require
|
||||
* @param {string} itemType type of the item (MRU or Profile) - optional
|
||||
* @returns {string} formatted string with server, DB and username
|
||||
*/
|
||||
public formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string {
|
||||
let connectionProfileInstance: ConnectionProfile = ConnectionProfile.convertToConnectionProfile(
|
||||
this._connectionConfig.getCapabilities(connectionProfile.providerName), connectionProfile);
|
||||
if (!connectionProfileInstance.getConnectionInfoId()) {
|
||||
throw new Error('Missing Id, which is required');
|
||||
}
|
||||
let cred: string[] = [ConnectionStore.CRED_PREFIX];
|
||||
if (!itemType) {
|
||||
itemType = ConnectionStore.CRED_PROFILE_USER;
|
||||
}
|
||||
|
||||
ConnectionStore.pushIfNonEmpty(itemType, ConnectionStore.CRED_ITEMTYPE_PREFIX, cred);
|
||||
ConnectionStore.pushIfNonEmpty(connectionProfileInstance.getConnectionInfoId(), ConnectionStore.CRED_ID_PREFIX, cred);
|
||||
return cred.join(ConnectionStore.CRED_SEPARATOR);
|
||||
}
|
||||
|
||||
private static pushIfNonEmpty(value: string, prefix: string, arr: string[]): void {
|
||||
if (value) {
|
||||
arr.push(prefix.concat(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the password is required
|
||||
* @param connection profile
|
||||
*/
|
||||
public isPasswordRequired(connection: IConnectionProfile): boolean {
|
||||
if (connection) {
|
||||
let connectionProfile = ConnectionProfile.convertToConnectionProfile(this._connectionConfig.getCapabilities(connection.providerName), connection);
|
||||
return connectionProfile.isPasswordRequired();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all connection profiles stored in the user settings
|
||||
* Profiles from workspace will be included if getWorkspaceProfiles is passed as true
|
||||
* Note: connections will not include password value
|
||||
*
|
||||
* @returns {IConnectionProfile[]}
|
||||
*/
|
||||
public getProfiles(getWorkspaceProfiles: boolean): IConnectionProfile[] {
|
||||
return this.loadProfiles(getWorkspaceProfiles);
|
||||
}
|
||||
|
||||
public addSavedPassword(credentialsItem: IConnectionProfile): Promise<{ profile: IConnectionProfile, savedCred: boolean }> {
|
||||
let self = this;
|
||||
return new Promise<{ profile: IConnectionProfile, savedCred: boolean }>((resolve, reject) => {
|
||||
if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem)
|
||||
&& !credentialsItem.password) {
|
||||
|
||||
let credentialId = this.formatCredentialIdForCred(credentialsItem);
|
||||
self._credentialService.readCredential(credentialId)
|
||||
.then(savedCred => {
|
||||
if (savedCred) {
|
||||
credentialsItem.password = savedCred.password;
|
||||
}
|
||||
resolve({ profile: credentialsItem, savedCred: !!savedCred });
|
||||
},
|
||||
reason => {
|
||||
reject(reason);
|
||||
});
|
||||
} else {
|
||||
// No need to look up the password
|
||||
resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a connection profile to the user settings.
|
||||
* Password values are stored to a separate credential store if the "savePassword" option is true
|
||||
*
|
||||
* @param {IConnectionProfile} profile the profile to save
|
||||
* @param {forceWritePlaintextPassword} whether the plaintext password should be written to the settings file
|
||||
* @returns {Promise<IConnectionProfile>} a Promise that returns the original profile, for help in chaining calls
|
||||
*/
|
||||
public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise<IConnectionProfile> {
|
||||
const self = this;
|
||||
return new Promise<IConnectionProfile>((resolve, reject) => {
|
||||
// Add the profile to the saved list, taking care to clear out the password field if necessary
|
||||
let savedProfile: IConnectionProfile;
|
||||
if (forceWritePlaintextPassword) {
|
||||
savedProfile = profile;
|
||||
} else {
|
||||
|
||||
savedProfile = this.getProfileWithoutPassword(profile);
|
||||
}
|
||||
self.saveProfileToConfig(savedProfile)
|
||||
.then(savedConnectionProfile => {
|
||||
profile.groupId = savedConnectionProfile.groupId;
|
||||
profile.id = savedConnectionProfile.id;
|
||||
// Only save if we successfully added the profile
|
||||
return self.saveProfilePasswordIfNeeded(profile);
|
||||
// And resolve / reject at the end of the process
|
||||
}, err => {
|
||||
reject(err);
|
||||
}).then(resolved => {
|
||||
// Add necessary default properties before returning
|
||||
// this is needed to support immediate connections
|
||||
ConnInfo.fixupConnectionCredentials(profile);
|
||||
this.saveCachedServerCapabilities();
|
||||
resolve(profile);
|
||||
}, err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a connection profile group to the user settings.
|
||||
*
|
||||
* @param {IConnectionProfileGroup} profile the profile group to save
|
||||
* @returns {Promise<string>} a Promise that returns the id of connection group
|
||||
*/
|
||||
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
|
||||
const self = this;
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
self._connectionConfig.addGroup(profile).then(groupId => {
|
||||
resolve(groupId);
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private saveProfileToConfig(profile: IConnectionProfile): Promise<IConnectionProfile> {
|
||||
const self = this;
|
||||
return new Promise<IConnectionProfile>((resolve, reject) => {
|
||||
if (profile.saveProfile) {
|
||||
self._connectionConfig.addConnection(profile).then(savedProfile => {
|
||||
resolve(savedProfile);
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(profile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getCachedServerCapabilities(): data.DataProtocolServerCapabilities[] {
|
||||
if (this._memento) {
|
||||
let metadata: data.DataProtocolServerCapabilities[] = this._memento[Constants.capabilitiesOptions];
|
||||
return metadata;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private saveCachedServerCapabilities(): void {
|
||||
if (this._memento) {
|
||||
let capabilities = this._capabilitiesService.getCapabilities();
|
||||
this._memento[Constants.capabilitiesOptions] = capabilities;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of recently used connections. These will not include the password - a separate call to
|
||||
* {addSavedPassword} is needed to fill that before connecting
|
||||
*
|
||||
* @returns {data.ConnectionInfo} the array of connections, empty if none are found
|
||||
*/
|
||||
public getRecentlyUsedConnections(): ConnectionProfile[] {
|
||||
let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections];
|
||||
if (!configValues) {
|
||||
configValues = [];
|
||||
}
|
||||
|
||||
configValues = configValues.filter(c => !!(c));
|
||||
return this.convertConfigValuesToConnectionProfiles(configValues);
|
||||
}
|
||||
|
||||
private convertConfigValuesToConnectionProfiles(configValues: IConnectionProfile[]): ConnectionProfile[] {
|
||||
return configValues.map(c => {
|
||||
if (c) {
|
||||
let capabilities = this._connectionConfig.getCapabilities(c.providerName);
|
||||
let connectionProfile = new ConnectionProfile(capabilities, c);
|
||||
this._capabilitiesService.onProviderRegisteredEvent((serverCapabilities) => {
|
||||
connectionProfile.onProviderRegistered(serverCapabilities);
|
||||
});
|
||||
if (connectionProfile.saveProfile) {
|
||||
if (!connectionProfile.groupFullName && connectionProfile.groupId) {
|
||||
connectionProfile.groupFullName = this.getGroupFullName(connectionProfile.groupId);
|
||||
}
|
||||
if (!connectionProfile.groupId && connectionProfile.groupFullName) {
|
||||
connectionProfile.groupId = this.getGroupId(connectionProfile.groupFullName);
|
||||
} else if (!connectionProfile.groupId && !connectionProfile.groupFullName) {
|
||||
connectionProfile.groupId = this.getGroupId('');
|
||||
}
|
||||
}
|
||||
return connectionProfile;
|
||||
} else {
|
||||
return undefined;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of active connections. These will not include the password - a separate call to
|
||||
* {addSavedPassword} is needed to fill that before connecting
|
||||
*
|
||||
* @returns {data.ConnectionInfo} the array of connections, empty if none are found
|
||||
*/
|
||||
public getActiveConnections(): ConnectionProfile[] {
|
||||
let configValues: IConnectionProfile[] = this._memento[Constants.activeConnections];
|
||||
if (!configValues) {
|
||||
configValues = [];
|
||||
}
|
||||
|
||||
return this.convertConfigValuesToConnectionProfiles(configValues);
|
||||
}
|
||||
|
||||
public getProfileWithoutPassword(conn: IConnectionProfile): ConnectionProfile {
|
||||
if (conn) {
|
||||
let savedConn: ConnectionProfile = ConnectionProfile.convertToConnectionProfile(this._connectionConfig.getCapabilities(conn.providerName), conn);
|
||||
savedConn = savedConn.withoutPassword();
|
||||
|
||||
return savedConn;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a connection to the active connections list.
|
||||
* Connection is only added if there are no other connections with the same connection ID in the list.
|
||||
* Password values are stored to a separate credential store if the "savePassword" option is true
|
||||
*
|
||||
* @param {IConnectionCredentials} conn the connection to add
|
||||
* @returns {Promise<void>} a Promise that returns when the connection was saved
|
||||
*/
|
||||
public addActiveConnection(conn: IConnectionProfile): Promise<void> {
|
||||
if(this.getActiveConnections().some(existingConn => existingConn.id === conn.id)) {
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
return this.addConnectionToMemento(conn, Constants.activeConnections, undefined, conn.savePassword).then(() => {
|
||||
let maxConnections = this.getMaxRecentConnectionsCount();
|
||||
return this.addConnectionToMemento(conn, Constants.recentConnections, maxConnections);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public addConnectionToMemento(conn: IConnectionProfile, mementoKey: string, maxConnections?: number, savePassword?: boolean): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Get all profiles
|
||||
let configValues = self.getConnectionsFromMemento(mementoKey);
|
||||
let configToSave: IConnectionProfile[] = this.addToConnectionList(conn, configValues, mementoKey === Constants.recentConnections);
|
||||
|
||||
if (maxConnections) {
|
||||
// Remove last element if needed
|
||||
if (configToSave.length > maxConnections) {
|
||||
configToSave = configToSave.slice(0, maxConnections);
|
||||
}
|
||||
}
|
||||
self._memento[mementoKey] = configToSave;
|
||||
if (savePassword) {
|
||||
self.doSavePassword(conn).then(result => {
|
||||
resolve(undefined);
|
||||
});
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private isSameConnectionProfileNoGroup(profile1: IConnectionProfile, profile2: IConnectionProfile): boolean {
|
||||
// both are undefined
|
||||
if (!profile1 && !profile2) {
|
||||
return true;
|
||||
}
|
||||
// only one is undefined
|
||||
if (!profile1 || !profile2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare all the connection's "identity" properties
|
||||
return equalsIgnoreCase(profile1.serverName, profile2.serverName) &&
|
||||
equalsIgnoreCase(profile1.databaseName, profile2.databaseName) &&
|
||||
equalsIgnoreCase(profile1.userName, profile2.userName) &&
|
||||
profile1.authenticationType === profile2.authenticationType &&
|
||||
profile1.providerName === profile2.providerName;
|
||||
}
|
||||
|
||||
public removeConnectionToMemento(conn: IConnectionProfile, mementoKey: string): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Get all profiles
|
||||
let configValues = self.getConnectionsFromMemento(mementoKey);
|
||||
let configToSave = this.removeFromConnectionList(conn, configValues);
|
||||
|
||||
self._memento[mementoKey] = configToSave;
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
public getConnectionsFromMemento(mementoKey: string): ConnectionProfile[] {
|
||||
let configValues: IConnectionProfile[] = this._memento[mementoKey];
|
||||
if (!configValues) {
|
||||
configValues = [];
|
||||
}
|
||||
|
||||
return this.convertConfigValuesToConnectionProfiles(configValues);
|
||||
}
|
||||
|
||||
private addToConnectionList(conn: IConnectionProfile, list: ConnectionProfile[], isRecentConnections: boolean): IConnectionProfile[] {
|
||||
let savedProfile: ConnectionProfile = this.getProfileWithoutPassword(conn);
|
||||
|
||||
// Remove the connection from the list if it already exists
|
||||
if (isRecentConnections) {
|
||||
// recent connections should use a different comparison the server viewlet for managing connection list
|
||||
list = list.filter(value => {
|
||||
return !(this.isSameConnectionProfileNoGroup(value, savedProfile));
|
||||
});
|
||||
} else {
|
||||
list = list.filter(value => {
|
||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
||||
if (equal && savedProfile.saveProfile) {
|
||||
equal = value.groupId === savedProfile.groupId ||
|
||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||
}
|
||||
return !equal;
|
||||
});
|
||||
}
|
||||
|
||||
list.unshift(savedProfile);
|
||||
|
||||
let newList = list.map(c => {
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
|
||||
return connectionProfile;
|
||||
});
|
||||
return newList.filter(n => n !== undefined);
|
||||
}
|
||||
|
||||
private removeFromConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] {
|
||||
let savedProfile: ConnectionProfile = this.getProfileWithoutPassword(conn);
|
||||
|
||||
// Remove the connection from the list if it already exists
|
||||
list = list.filter(value => {
|
||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
||||
if (equal && savedProfile.saveProfile) {
|
||||
equal = value.groupId === savedProfile.groupId ||
|
||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||
}
|
||||
return !equal;
|
||||
});
|
||||
|
||||
let newList = list.map(c => {
|
||||
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
|
||||
return connectionProfile;
|
||||
});
|
||||
return newList.filter(n => n !== undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all recently used connections from the MRU list.
|
||||
*/
|
||||
public clearRecentlyUsed(): void {
|
||||
this._memento[Constants.recentConnections] = [];
|
||||
}
|
||||
|
||||
public clearFromMemento(name: string): void {
|
||||
this._memento[name] = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear all active connections from the MRU list.
|
||||
*/
|
||||
public clearActiveConnections(): void {
|
||||
this._memento[Constants.activeConnections] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a connection profile from the active connections list.
|
||||
*/
|
||||
public removeActiveConnection(conn: IConnectionProfile): Promise<void> {
|
||||
return this.removeConnectionToMemento(conn, Constants.activeConnections);
|
||||
}
|
||||
|
||||
private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise<boolean> {
|
||||
if (!profile.savePassword) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return this.doSavePassword(profile);
|
||||
}
|
||||
|
||||
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
|
||||
let self = this;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
if (conn.password) {
|
||||
let credentialId = this.formatCredentialId(conn);
|
||||
self._credentialService.saveCredential(credentialId, conn.password)
|
||||
.then((result) => {
|
||||
resolve(result);
|
||||
}, reason => {
|
||||
// Bubble up error if there was a problem executing the set command
|
||||
reject(reason);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getConnectionProfileGroups(withoutConnections?: boolean): ConnectionProfileGroup[] {
|
||||
let profilesInConfiguration: ConnectionProfile[];
|
||||
if (!withoutConnections) {
|
||||
profilesInConfiguration = this._connectionConfig.getConnections(true);
|
||||
}
|
||||
let groups = this._connectionConfig.getAllGroups();
|
||||
|
||||
let connectionProfileGroups = this.convertToConnectionGroup(groups, profilesInConfiguration, undefined);
|
||||
return connectionProfileGroups;
|
||||
}
|
||||
|
||||
private convertToConnectionGroup(groups: IConnectionProfileGroup[], connections: ConnectionProfile[], parent: ConnectionProfileGroup = undefined): ConnectionProfileGroup[] {
|
||||
let result: ConnectionProfileGroup[] = [];
|
||||
let children = groups.filter(g => g.parentId === (parent ? parent.id : undefined));
|
||||
if (children) {
|
||||
children.map(group => {
|
||||
let connectionGroup = new ConnectionProfileGroup(group.name, parent, group.id, group.color, group.description);
|
||||
this.addGroupFullNameToMap(group.id, connectionGroup.fullName);
|
||||
if (connections) {
|
||||
let connectionsForGroup = connections.filter(conn => conn.groupId === connectionGroup.id);
|
||||
var conns = [];
|
||||
connectionsForGroup.forEach((conn) => {
|
||||
conn.groupFullName = connectionGroup.fullName;
|
||||
conns.push(conn);
|
||||
});
|
||||
connectionGroup.addConnections(conns);
|
||||
}
|
||||
|
||||
let childrenGroups = this.convertToConnectionGroup(groups, connections, connectionGroup);
|
||||
connectionGroup.addGroups(childrenGroups);
|
||||
result.push(connectionGroup);
|
||||
});
|
||||
if (parent) {
|
||||
parent.addGroups(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private loadProfiles(loadWorkspaceProfiles: boolean): IConnectionProfile[] {
|
||||
let connections: IConnectionProfile[] = this._connectionConfig.getConnections(loadWorkspaceProfiles);
|
||||
return connections;
|
||||
}
|
||||
|
||||
private getMaxRecentConnectionsCount(): number {
|
||||
let config = this._workspaceConfigurationService.getConfiguration(Constants.sqlConfigSectionName);
|
||||
|
||||
let maxConnections: number = config[Constants.configMaxRecentConnections];
|
||||
if (typeof (maxConnections) !== 'number' || maxConnections <= 0) {
|
||||
maxConnections = MAX_CONNECTIONS_DEFAULT;
|
||||
}
|
||||
return maxConnections;
|
||||
}
|
||||
|
||||
public editGroup(group: ConnectionProfileGroup): Promise<any> {
|
||||
const self = this;
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
self._connectionConfig.editGroup(group).then(() => {
|
||||
resolve(null);
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deleteConnectionFromConfiguration(connection: ConnectionProfile): Promise<void> {
|
||||
return this._connectionConfig.deleteConnection(connection);
|
||||
}
|
||||
|
||||
public deleteGroupFromConfiguration(group: ConnectionProfileGroup): Promise<void> {
|
||||
return this._connectionConfig.deleteGroup(group);
|
||||
}
|
||||
|
||||
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
|
||||
return this._connectionConfig.changeGroupIdForConnectionGroup(source, target);
|
||||
}
|
||||
|
||||
public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean {
|
||||
return this._connectionConfig.canChangeConnectionConfig(profile, newGroupID);
|
||||
}
|
||||
|
||||
public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this._connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(() => {
|
||||
resolve();
|
||||
}, (error => {
|
||||
reject(error);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private addGroupFullNameToMap(groupId: string, groupFullName: string): void {
|
||||
if (groupId) {
|
||||
this._groupIdToFullNameMap[groupId] = groupFullName;
|
||||
}
|
||||
if (groupFullName !== undefined) {
|
||||
this._groupFullNameToIdMap[groupFullName.toUpperCase()] = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
private getGroupFullName(groupId: string): string {
|
||||
if (groupId in this._groupIdToFullNameMap) {
|
||||
return this._groupIdToFullNameMap[groupId];
|
||||
} else {
|
||||
// Load the cache
|
||||
this.getConnectionProfileGroups(true);
|
||||
}
|
||||
return this._groupIdToFullNameMap[groupId];
|
||||
}
|
||||
|
||||
private getGroupId(groupFullName: string): string {
|
||||
if (groupFullName === ConnectionProfileGroup.GroupNameSeparator) {
|
||||
groupFullName = '';
|
||||
}
|
||||
let key = groupFullName.toUpperCase();
|
||||
let result: string = '';
|
||||
if (key in this._groupFullNameToIdMap) {
|
||||
result = this._groupFullNameToIdMap[key];
|
||||
} else {
|
||||
// Load the cache
|
||||
this.getConnectionProfileGroups(true);
|
||||
result = this._groupFullNameToIdMap[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
27
src/sql/parts/connection/common/constants.ts
Normal file
27
src/sql/parts/connection/common/constants.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// constants
|
||||
export const sqlConfigSectionName = 'sql';
|
||||
export const outputChannelName = 'MSSQL';
|
||||
|
||||
export const connectionsArrayName = 'datasource.connections';
|
||||
export const connectionGroupsArrayName = 'datasource.connectionGroups';
|
||||
|
||||
/**Unsaved connections Id */
|
||||
export const unsavedGroupId = 'unsaved';
|
||||
|
||||
/* Memento constants */
|
||||
export const activeConnections = 'ACTIVE_CONNECTIONS';
|
||||
export const recentConnections = 'RECENT_CONNECTIONS';
|
||||
export const capabilitiesOptions = 'OPTIONS_METADATA';
|
||||
|
||||
export const configMaxRecentConnections = 'maxRecentConnections';
|
||||
|
||||
export const mssqlProviderName = 'MSSQL';
|
||||
|
||||
export const applicationName = 'sqlops';
|
||||
|
||||
export const defaultEngine = 'defaultEngine';
|
||||
32
src/sql/parts/connection/common/iconnectionConfig.ts
Normal file
32
src/sql/parts/connection/common/iconnectionConfig.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IConnectionProfile } from './interfaces';
|
||||
import { IConnectionProfileGroup, ConnectionProfileGroup } from './connectionProfileGroup';
|
||||
import { ConnectionProfile } from './connectionProfile';
|
||||
import data = require('data');
|
||||
|
||||
/**
|
||||
* Interface for a configuration file that stores connection profiles.
|
||||
*
|
||||
* @export
|
||||
* @interface IConnectionConfig
|
||||
*/
|
||||
export interface IConnectionConfig {
|
||||
addConnection(profile: IConnectionProfile): Promise<IConnectionProfile>;
|
||||
addGroup(profileGroup: IConnectionProfileGroup): Promise<string>;
|
||||
getConnections(getWorkspaceConnections: boolean): ConnectionProfile[];
|
||||
getAllGroups(): IConnectionProfileGroup[];
|
||||
changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void>;
|
||||
changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void>;
|
||||
setCachedMetadata(cachedMetaData: data.DataProtocolServerCapabilities[]): void;
|
||||
getCapabilities(providerName: string): data.DataProtocolServerCapabilities;
|
||||
editGroup(group: ConnectionProfileGroup): Promise<void>;
|
||||
deleteConnection(profile: ConnectionProfile): Promise<void>;
|
||||
deleteGroup(group: ConnectionProfileGroup): Promise<void>;
|
||||
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
|
||||
}
|
||||
34
src/sql/parts/connection/common/interfaces.ts
Normal file
34
src/sql/parts/connection/common/interfaces.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as data from 'data';
|
||||
|
||||
// A Connection Profile contains all the properties of connection credentials, with additional
|
||||
// optional name and details on whether password should be saved
|
||||
export interface IConnectionProfile extends data.ConnectionInfo {
|
||||
serverName: string;
|
||||
databaseName: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
authenticationType: string;
|
||||
savePassword: boolean;
|
||||
groupFullName: string;
|
||||
groupId: string;
|
||||
getOptionsKey(): string;
|
||||
matches(profile: IConnectionProfile): boolean;
|
||||
providerName: string;
|
||||
saveProfile: boolean;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export interface IConnectionProfileStore {
|
||||
options: {};
|
||||
groupId: string;
|
||||
providerName: string;
|
||||
savePassword: boolean;
|
||||
id: string;
|
||||
};
|
||||
|
||||
10
src/sql/parts/connection/common/localizedConstants.ts
Normal file
10
src/sql/parts/connection/common/localizedConstants.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export const onDidConnectMessage = localize('onDidConnectMessage', 'Connected to');
|
||||
export const onDidDisconnectMessage = localize('onDidDisconnectMessage', 'Disconnected');
|
||||
export const unsavedGroupLabel = localize('unsavedGroupLabel', 'Unsaved Connections');
|
||||
241
src/sql/parts/connection/common/providerConnectionInfo.ts
Normal file
241
src/sql/parts/connection/common/providerConnectionInfo.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import data = require('data');
|
||||
import * as interfaces from 'sql/parts/connection/common/interfaces';
|
||||
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as Constants from 'sql/parts/connection/common/constants';
|
||||
|
||||
export class ProviderConnectionInfo implements data.ConnectionInfo {
|
||||
|
||||
options: { [name: string]: any };
|
||||
|
||||
public providerName: string;
|
||||
protected _serverCapabilities: data.DataProtocolServerCapabilities;
|
||||
private static readonly SqlAuthentication = 'SqlLogin';
|
||||
public static readonly ProviderPropertyName = 'providerName';
|
||||
|
||||
public constructor(serverCapabilities?: data.DataProtocolServerCapabilities, model?: interfaces.IConnectionProfile) {
|
||||
this.options = {};
|
||||
if (serverCapabilities) {
|
||||
this._serverCapabilities = serverCapabilities;
|
||||
this.providerName = serverCapabilities.providerName;
|
||||
}
|
||||
if (model) {
|
||||
if (model.options && this._serverCapabilities) {
|
||||
this._serverCapabilities.connectionProvider.options.forEach(option => {
|
||||
let value = model.options[option.name];
|
||||
this.options[option.name] = value;
|
||||
});
|
||||
}
|
||||
this.serverName = model.serverName;
|
||||
this.authenticationType = model.authenticationType;
|
||||
this.databaseName = model.databaseName;
|
||||
this.password = model.password;
|
||||
this.userName = model.userName;
|
||||
}
|
||||
}
|
||||
|
||||
public clone(): ProviderConnectionInfo {
|
||||
let instance = new ProviderConnectionInfo(this._serverCapabilities);
|
||||
instance.options = Object.assign({}, this.options);
|
||||
instance.providerName = this.providerName;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public get serverCapabilities(): data.DataProtocolServerCapabilities {
|
||||
return this._serverCapabilities;
|
||||
}
|
||||
|
||||
public setServerCapabilities(value: data.DataProtocolServerCapabilities) {
|
||||
this._serverCapabilities = value;
|
||||
}
|
||||
|
||||
public get serverName(): string {
|
||||
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.serverName);
|
||||
}
|
||||
|
||||
public get databaseName(): string {
|
||||
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.databaseName);
|
||||
}
|
||||
|
||||
public get userName(): string {
|
||||
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.userName);
|
||||
}
|
||||
|
||||
public get password(): string {
|
||||
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.password);
|
||||
}
|
||||
|
||||
public get authenticationType(): string {
|
||||
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.authType);
|
||||
}
|
||||
|
||||
public set serverName(value: string) {
|
||||
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.serverName, value);
|
||||
}
|
||||
|
||||
public set databaseName(value: string) {
|
||||
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.databaseName, value);
|
||||
}
|
||||
|
||||
public set userName(value: string) {
|
||||
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.userName, value);
|
||||
}
|
||||
|
||||
public set password(value: string) {
|
||||
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.password, value);
|
||||
}
|
||||
|
||||
public set authenticationType(value: string) {
|
||||
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.authType, value);
|
||||
}
|
||||
|
||||
public getOptionValue(name: string): any {
|
||||
return this.options[name];
|
||||
}
|
||||
|
||||
public setOptionValue(name: string, value: any): void {
|
||||
//TODO: validate
|
||||
this.options[name] = value;
|
||||
}
|
||||
|
||||
public isPasswordRequired(): boolean {
|
||||
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(
|
||||
option => option.specialValueType === ConnectionOptionSpecialType.password);
|
||||
let isPasswordRequired: boolean = optionMetadata.isRequired;
|
||||
if (this.providerName === Constants.mssqlProviderName) {
|
||||
isPasswordRequired = this.authenticationType === ProviderConnectionInfo.SqlAuthentication && optionMetadata.isRequired;
|
||||
}
|
||||
return isPasswordRequired;
|
||||
}
|
||||
|
||||
private getSpecialTypeOptionValue(type: number): string {
|
||||
let name = this.getSpecialTypeOptionName(type);
|
||||
if (name) {
|
||||
return this.options[name];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||
* This key uniquely identifies a connection in a group
|
||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
*/
|
||||
public getOptionsKey(): string {
|
||||
let idNames = [];
|
||||
if (this._serverCapabilities) {
|
||||
idNames = this._serverCapabilities.connectionProvider.options.map(o => {
|
||||
if ((o.specialValueType || o.isIdentity) && o.specialValueType !== ConnectionOptionSpecialType.password) {
|
||||
return o.name;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// This should never happen but just incase the serverCapabilities was not ready at this time
|
||||
idNames = ['authenticationType', 'database', 'server', 'user'];
|
||||
}
|
||||
|
||||
idNames = idNames.filter(x => x !== undefined);
|
||||
|
||||
//Sort to make sure using names in the same order every time otherwise the ids would be different
|
||||
idNames.sort();
|
||||
|
||||
let idValues: string[] = [];
|
||||
for (var index = 0; index < idNames.length; index++) {
|
||||
let value = this.options[idNames[index]];
|
||||
value = value ? value : '';
|
||||
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
|
||||
}
|
||||
|
||||
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
|
||||
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
|
||||
}
|
||||
|
||||
public static getProviderFromOptionsKey(optionsKey: string) {
|
||||
let providerId: string = '';
|
||||
if (optionsKey) {
|
||||
let ids: string[] = optionsKey.split(ProviderConnectionInfo.idSeparator);
|
||||
ids.forEach(id => {
|
||||
let idParts = id.split(ProviderConnectionInfo.nameValueSeparator);
|
||||
if (idParts.length >= 2 && idParts[0] === ProviderConnectionInfo.ProviderPropertyName) {
|
||||
providerId = idParts[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
return providerId;
|
||||
}
|
||||
|
||||
public getSpecialTypeOptionName(type: number): string {
|
||||
if (this._serverCapabilities) {
|
||||
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(o => o.specialValueType === type);
|
||||
return !!optionMetadata ? optionMetadata.name : undefined;
|
||||
} else {
|
||||
return type.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public setSpecialTypeOptionName(type: number, value: string): void {
|
||||
let name = this.getSpecialTypeOptionName(type);
|
||||
if (!!name) {
|
||||
this.options[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public get authenticationTypeDisplayName(): string {
|
||||
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(o => o.specialValueType === ConnectionOptionSpecialType.authType);
|
||||
let authType = this.authenticationType;
|
||||
let displayName: string = authType;
|
||||
|
||||
if (optionMetadata && optionMetadata.categoryValues) {
|
||||
optionMetadata.categoryValues.forEach(element => {
|
||||
if (element.name === authType) {
|
||||
displayName = element.displayName;
|
||||
}
|
||||
});
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public getProviderOptions(): data.ConnectionOption[] {
|
||||
return this._serverCapabilities.connectionProvider.options;
|
||||
}
|
||||
|
||||
public static get idSeparator(): string {
|
||||
return '|';
|
||||
}
|
||||
|
||||
public static get nameValueSeparator(): string {
|
||||
return ':';
|
||||
}
|
||||
|
||||
public get titleParts(): string[] {
|
||||
let parts: string[] = [];
|
||||
// Always put these three on top. TODO: maybe only for MSSQL?
|
||||
parts.push(this.serverName);
|
||||
parts.push(this.databaseName);
|
||||
parts.push(this.authenticationTypeDisplayName);
|
||||
|
||||
this._serverCapabilities.connectionProvider.options.forEach(element => {
|
||||
if (element.specialValueType !== ConnectionOptionSpecialType.serverName &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.authType &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.password &&
|
||||
element.isIdentity && element.valueType === ServiceOptionType.string) {
|
||||
let value = this.getOptionValue(element.name);
|
||||
if (value) {
|
||||
parts.push(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
|
||||
152
src/sql/parts/connection/common/utils.ts
Normal file
152
src/sql/parts/connection/common/utils.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IConnectionProfile } from './interfaces';
|
||||
import { ConnectionProfile } from './connectionProfile';
|
||||
import { ConnectionProfileGroup } from './connectionProfileGroup';
|
||||
|
||||
// CONSTANTS //////////////////////////////////////////////////////////////////////////////////////
|
||||
const msInH = 3.6e6;
|
||||
const msInM = 60000;
|
||||
const msInS = 1000;
|
||||
export const uriPrefixes = {
|
||||
default: 'connection://',
|
||||
connection: 'connection://',
|
||||
dashboard: 'dashboard://',
|
||||
insights: 'insights://'
|
||||
};
|
||||
|
||||
|
||||
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export const defaultGroupId = 'C777F06B-202E-4480-B475-FA416154D458';
|
||||
export const ConnectionUriBackupIdAttributeName = 'backupId';
|
||||
export const ConnectionUriRestoreIdAttributeName = 'restoreId';
|
||||
|
||||
/**
|
||||
* Takes a string in the format of HH:MM:SS.MS and returns a number representing the time in
|
||||
* miliseconds
|
||||
* @param value The string to convert to milliseconds
|
||||
* @return False is returned if the string is an invalid format,
|
||||
* the number of milliseconds in the time string is returned otherwise.
|
||||
*/
|
||||
export function parseTimeString(value: string): number | boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
let tempVal = value.split('.');
|
||||
|
||||
if (tempVal.length === 1) {
|
||||
// Ideally would handle more cleanly than this but for now handle case where ms not set
|
||||
tempVal = [tempVal[0], '0'];
|
||||
} else if (tempVal.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let msString = tempVal[1];
|
||||
let msStringEnd = msString.length < 3 ? msString.length : 3;
|
||||
let ms = parseInt(tempVal[1].substring(0, msStringEnd), 10);
|
||||
|
||||
tempVal = tempVal[0].split(':');
|
||||
|
||||
if (tempVal.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let h = parseInt(tempVal[0], 10);
|
||||
let m = parseInt(tempVal[1], 10);
|
||||
let s = parseInt(tempVal[2], 10);
|
||||
|
||||
return ms + (h * msInH) + (m * msInM) + (s * msInS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a number of milliseconds and converts it to a string like HH:MM:SS.fff
|
||||
* @param value The number of milliseconds to convert to a timespan string
|
||||
* @returns A properly formatted timespan string.
|
||||
*/
|
||||
export function parseNumAsTimeString(value: number): string {
|
||||
let tempVal = value;
|
||||
let h = Math.floor(tempVal / msInH);
|
||||
tempVal %= msInH;
|
||||
let m = Math.floor(tempVal / msInM);
|
||||
tempVal %= msInM;
|
||||
let s = Math.floor(tempVal / msInS);
|
||||
tempVal %= msInS;
|
||||
|
||||
let hs = h < 10 ? '0' + h : '' + h;
|
||||
let ms = m < 10 ? '0' + m : '' + m;
|
||||
let ss = s < 10 ? '0' + s : '' + s;
|
||||
let mss = tempVal < 10 ? '00' + tempVal : tempVal < 100 ? '0' + tempVal : '' + tempVal;
|
||||
|
||||
let rs = hs + ':' + ms + ':' + ss;
|
||||
|
||||
return tempVal > 0 ? rs + '.' + mss : rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts <, >, &, ", ', and any characters that are outside \u00A0 to numeric HTML entity values
|
||||
* like {
|
||||
* (Adapted from http://stackoverflow.com/a/18750001)
|
||||
* @param str String to convert
|
||||
* @return String with characters replaced.
|
||||
*/
|
||||
export function htmlEntities(str: string): string {
|
||||
return typeof (str) === 'string'
|
||||
? str.replace(/[\u00A0-\u9999<>\&"']/gim, (i) => { return `&#${i.charCodeAt(0)};`; })
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): string {
|
||||
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
|
||||
let uri = generateUriWithPrefix(connection, prefix);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
export function getUriPrefix(ownerUri: string): string {
|
||||
let prefix: string = '';
|
||||
if (ownerUri) {
|
||||
let index = ownerUri.indexOf('://');
|
||||
if (index > 0) {
|
||||
prefix = ownerUri.substring(0, index + 3);
|
||||
} else {
|
||||
return uriPrefixes.default;
|
||||
}
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
export function generateUriWithPrefix(connection: IConnectionProfile, prefix: string): string {
|
||||
let id = connection.getOptionsKey();
|
||||
let uri = prefix + (id ? id : connection.serverName + ':' + connection.databaseName);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
export function findProfileInGroup(og: IConnectionProfile, groups: ConnectionProfileGroup[]): ConnectionProfile {
|
||||
for (let group of groups) {
|
||||
for (let conn of group.connections) {
|
||||
if (conn.id === og.id) {
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
if (group.hasChildren()) {
|
||||
let potentialReturn = findProfileInGroup(og, group.children);
|
||||
if (potentialReturn) {
|
||||
return potentialReturn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isMaster(profile: IConnectionProfile): boolean {
|
||||
return profile.providerName.toLowerCase() === 'mssql' && profile.databaseName.toLowerCase() === 'master';
|
||||
}
|
||||
Reference in New Issue
Block a user