Added Big Data Cluster Viewlet to ADS (#6204)

This commit is contained in:
Gene Lee
2019-07-03 21:52:19 -06:00
committed by GitHub
parent 1404133283
commit cf4dd48784
53 changed files with 3628 additions and 3774 deletions

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* 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 nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { TreeNode } from './treeNode';
import { BdcItemType } from '../constants';
const localize = nls.loadMessageBundle();
export class AddControllerNode extends TreeNode {
private readonly nodeType: string;
constructor() {
super(localize('textBigDataClusterControllerWithDots', 'Add Big Data Cluster Controller...'));
this.nodeType = BdcItemType.addController;
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): vscode.TreeItem {
let item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.None);
item.command = {
title: localize('textConnectToController', 'Connect to Controller'),
command: 'bigDataClusters.command.addController',
arguments: [this]
};
item.contextValue = this.nodeType;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this.nodeType,
iconType: this.nodeType,
nodeSubType: undefined
};
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* 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 { TreeNode } from './treeNode';
export interface IControllerTreeChangeHandler {
notifyNodeChanged(node?: TreeNode): void;
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode';
import * as azdata from 'azdata';
import { TreeNode } from './treeNode';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { AddControllerNode } from './addControllerTreeNode';
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
import { IEndPoint } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const ConfigNamespace = 'clusterControllers';
const CredentialNamespace = 'clusterControllerCredentials';
export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeNode>, IControllerTreeChangeHandler {
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode> = new vscode.EventEmitter<TreeNode>();
public readonly onDidChangeTreeData: vscode.Event<TreeNode> = this._onDidChangeTreeData.event;
private root: ControllerRootNode;
private credentialProvider: azdata.CredentialProvider;
constructor() {
this.root = new ControllerRootNode(this);
this.loadSavedControllers();
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren();
}
if (this.root.hasChildren) {
return this.root.getChildren();
} else {
return [new AddControllerNode()];
}
}
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element.getTreeItem();
}
public addController(
url: string,
username: string,
password: string,
rememberPassword: boolean,
masterInstance?: IEndPoint
): void {
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
this.notifyNodeChanged();
}
public deleteController(url: string, username: string): ControllerNode {
let deleted = this.root.deleteControllerNode(url, username);
if (deleted) {
this.notifyNodeChanged();
}
return deleted;
}
public notifyNodeChanged(node?: TreeNode): void {
this._onDidChangeTreeData.fire(node);
}
public async loadSavedControllers(): Promise<void> {
let config = vscode.workspace.getConfiguration(ConfigNamespace);
if (config && config.controllers) {
let controllers = config.controllers;
this.root.clearChildren();
for (let c of controllers) {
let password = undefined;
if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username);
}
this.root.addChild(new ControllerNode(
c.url, c.username, password, c.rememberPassword,
undefined, this.root, this, undefined
));
}
this.notifyNodeChanged();
}
}
public async saveControllers(): Promise<void> {
let controllers = this.root.children.map(e => {
let controller = e as ControllerNode;
return {
url: controller.url,
username: controller.username,
password: controller.password,
rememberPassword: !!controller.rememberPassword
};
});
let controllersWithoutPassword = controllers.map(e => {
return {
url: e.url,
username: e.username,
rememberPassword: e.rememberPassword
};
});
try {
await vscode.workspace.getConfiguration(ConfigNamespace).update('controllers', controllersWithoutPassword, true);
} catch (error) {
showErrorMessage(error);
}
for (let e of controllers) {
if (e.rememberPassword) {
await this.savePassword(e.url, e.username, e.password);
} else {
await this.deletePassword(e.url, e.username);
}
}
}
private async savePassword(url: string, username: string, password: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.saveCredential(id, password);
return result;
}
private async deletePassword(url: string, username: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.deleteCredential(id);
return result;
}
private async getPassword(url: string, username: string): Promise<string> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let credential = await provider.readCredential(id);
return credential ? credential.password : undefined;
}
private async getCredentialProvider(): Promise<azdata.CredentialProvider> {
if (!this.credentialProvider) {
this.credentialProvider = await azdata.credentials.getProvider(CredentialNamespace);
}
return this.credentialProvider;
}
private createId(url: string, username: string): string {
return `${url}::${username}`;
}
}

View File

@@ -0,0 +1,392 @@
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { TreeNode } from './treeNode';
import { IconPath, BdcItemType } from '../constants';
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const localize = nls.loadMessageBundle();
export abstract class ControllerTreeNode extends TreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
private _treeChangeHandler: IControllerTreeChangeHandler,
private _description?: string,
private _nodeType?: string,
private _iconPath?: { dark: string, light: string }
) {
super(label, parent);
this._description = this._description || this.label;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
return this.children as ControllerTreeNode[];
}
public refresh(): void {
super.refresh();
this.treeChangeHandler.notifyNodeChanged(this);
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = {};
item.id = this.id;
item.label = this.label;
item.collapsibleState = this.isLeaf ?
vscode.TreeItemCollapsibleState.None :
vscode.TreeItemCollapsibleState.Collapsed;
item.iconPath = this._iconPath;
item.contextValue = this._nodeType;
item.tooltip = this._description;
item.iconPath = this._iconPath;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this._nodeType,
iconType: this._nodeType,
nodeSubType: undefined
};
}
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
}
public get nodeType(): string {
return this._nodeType;
}
public set nodeType(nodeType: string) {
this._nodeType = nodeType;
}
public set iconPath(iconPath: { dark: string, light: string }) {
this._iconPath = iconPath;
}
public get iconPath(): { dark: string, light: string } {
return this._iconPath;
}
public set treeChangeHandler(treeChangeHandler: IControllerTreeChangeHandler) {
this._treeChangeHandler = treeChangeHandler;
}
public get treeChangeHandler(): IControllerTreeChangeHandler {
return this._treeChangeHandler;
}
}
export class ControllerRootNode extends ControllerTreeNode {
private _masterNodeFactory: SqlMasterNodeFactory;
constructor(treeChangeHandler: IControllerTreeChangeHandler) {
super('root', undefined, treeChangeHandler, undefined, BdcItemType.controllerRoot);
this._masterNodeFactory = new SqlMasterNodeFactory();
}
public async getChildren(): Promise<ControllerNode[]> {
return this.children as ControllerNode[];
}
public addControllerNode(url: string, username: string, password: string, rememberPassword: boolean, masterInstance?: IEndPoint): void {
let controllerNode = this.getExistingControllerNode(url, username);
if (controllerNode) {
controllerNode.password = password;
controllerNode.rememberPassword = rememberPassword;
controllerNode.clearChildren();
} else {
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
this.addChild(controllerNode);
}
if (masterInstance) {
controllerNode.addSqlMasterNode(masterInstance.endpoint, masterInstance.description);
}
}
public deleteControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
let index = nodes.findIndex(e => e.url === url && e.username === username);
let deleted = undefined;
if (index >= 0) {
deleted = nodes.splice(index, 1);
}
return deleted;
}
private getExistingControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
return nodes.find(e => e.url === url && e.username === username);
}
public get sqlMasterNodeFactory(): SqlMasterNodeFactory {
return this._masterNodeFactory;
}
}
export class ControllerNode extends ControllerTreeNode {
constructor(
private _url: string,
private _username: string,
private _password: string,
private _rememberPassword: boolean,
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPath.controllerNode);
this.label = label;
this.description = description;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
if (this.children && this.children.length > 0) {
this.clearChildren();
}
if (!this._password) {
vscode.commands.executeCommand('bigDataClusters.command.addController', this);
return this.children as ControllerTreeNode[];
}
try {
let response = await getEndPoints(this._url, this._username, this._password, true);
if (response && response.endPoints) {
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
this.addSqlMasterNode(master.endpoint, master.description);
}
return this.children as ControllerTreeNode[];
} catch (error) {
showErrorMessage(error);
return this.children as ControllerTreeNode[];
}
}
private static toIpAndPort(url: string): string {
if (!url) {
return;
}
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '').replace(/:(\d+)$/, ',$1');
}
public addSqlMasterNode(endPointAddress: string, description: string): void {
let epFolder = this.getEndPointFolderNode();
let node = (this.root as ControllerRootNode).sqlMasterNodeFactory
.getSqlMasterNode(endPointAddress, epFolder, undefined, this.treeChangeHandler, description);
epFolder.addChild(node);
}
private getEndPointFolderNode(): FolderNode {
let label = localize('textSqlServers', 'SQL Servers');
let epFolderNode = this.children.find(e => e instanceof FolderNode && e.label === label);
if (!epFolderNode) {
epFolderNode = new FolderNode(label, this, this.treeChangeHandler);
this.addChild(epFolderNode);
}
return epFolderNode as FolderNode;
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = super.getTreeItem();
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
return item;
}
public get url() {
return this._url;
}
public set url(url: string) {
this._url = url;
}
public get username() {
return this._username;
}
public set username(username: string) {
this._username = username;
}
public get password() {
return this._password;
}
public set password(pw: string) {
this._password = pw;
}
public get rememberPassword() {
return this._rememberPassword;
}
public set rememberPassword(rememberPassword: boolean) {
this._rememberPassword = rememberPassword;
}
public set label(label: string) {
super.label = label || `controller: ${ControllerNode.toIpAndPort(this._url)} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class FolderNode extends ControllerTreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler
) {
super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPath.folderNode);
}
}
export class SqlMasterNode extends ControllerTreeNode {
private static readonly _role: string = 'sql-server-master';
private _username: string;
private _password: string;
constructor(
private _endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPath.sqlMasterNode);
this._username = 'sa';
this.label = label;
this.description = description;
}
private getControllerPassword(): string {
if (!this._password) {
let current: TreeNode = this;
while (current && !(current instanceof ControllerNode)) {
current = current.parent;
}
this._password = current && current instanceof ControllerNode ? current.password : undefined;
}
return this._password;
}
public getTreeItem(): vscode.TreeItem {
let item = super.getTreeItem();
let connectionProfile: azdata.IConnectionProfile = {
id: this.id,
connectionName: this.id,
serverName: this._endPointAddress,
databaseName: '',
userName: this._username,
password: this.getControllerPassword(),
authenticationType: 'SqlLogin',
savePassword: false,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: false,
options: {}
};
return Object.assign(item, { payload: connectionProfile, childProvider: 'MSSQL' });
}
public get role() {
return SqlMasterNode._role;
}
public get endPointAddress() {
return this._endPointAddress;
}
public set endPointAddress(endPointAddress: string) {
this._endPointAddress = endPointAddress;
}
public set label(label: string) {
super.label = label || `master: ${this._endPointAddress} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class SqlMasterNodeFactory {
private registry: {} = {};
public getSqlMasterNode(
endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string
): SqlMasterNode {
let id = this.createRegistryId(endPointAddress, 'sa');
if (!this.registry[id]) {
this.registry[id] = new SqlMasterNode(endPointAddress, parent, label, treeChangeHandler, description);
} else {
let node = this.registry[id] as SqlMasterNode;
node.parent = parent;
node.label = label;
node.treeChangeHandler = treeChangeHandler;
description = description;
}
return this.registry[id] as SqlMasterNode;
}
private createRegistryId(endPointAddress: string, username: string): string {
return `${endPointAddress}::${username}`;
}
}

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------------------------------------------
* 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 azdata from 'azdata';
import * as vscode from 'vscode';
import { generateGuid } from '../utils';
export abstract class TreeNode {
private _id: string;
private _children: TreeNode[];
private _isLeaf: boolean;
constructor(private _label: string, private _parent?: TreeNode) {
this.resetId();
}
public resetId(): void {
this._id = (this._label || '_') + `::${generateGuid()}`;
}
public get id(): string {
return this._id;
}
public set label(label: string) {
if (!this._label) {
this._label = label;
this.resetId();
} else {
this._label = label;
}
}
public get label(): string {
return this._label;
}
public set parent(parent: TreeNode) {
this._parent = parent;
}
public get parent(): TreeNode {
return this._parent;
}
public get children(): TreeNode[] {
if (!this._children) {
this._children = [];
}
return this._children;
}
public get hasChildren(): boolean {
return this.children && this.children.length > 0;
}
public set isLeaf(isLeaf: boolean) {
this._isLeaf = isLeaf;
}
public get isLeaf(): boolean {
return this._isLeaf;
}
public get root(): TreeNode {
return TreeNode.getRoot(this);
}
public equals(node: TreeNode): boolean {
if (!node) {
return undefined;
}
return this.nodePath === node.nodePath;
}
public refresh(): void {
this.resetId();
}
public static getRoot(node: TreeNode): TreeNode {
if (!node) {
return undefined;
}
let current: TreeNode = node;
while (current.parent) {
current = current.parent;
}
return current;
}
public get nodePath(): string {
return TreeNode.getNodePath(this);
}
public static getNodePath(node: TreeNode): string {
if (!node) {
return undefined;
}
let current: TreeNode = node;
let path = current._id;
while (current.parent) {
current = current.parent;
path = `${current._id}/${path}`;
}
return path;
}
public async findNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(this, condition, expandIfNeeded);
}
public static async findNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode = undefined;
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result = current;
break;
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async filterNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
return TreeNode.filterNode(this, condition, expandIfNeeded);
}
public static async filterNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode[] = [];
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result.push(current);
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async findNodeByPath(path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNodeByPath(this, path, expandIfNeeded);
}
public static async findNodeByPath(node: TreeNode, path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(node, node => {
return node.nodePath && (node.nodePath === path || node.nodePath.startsWith(path));
}, expandIfNeeded);
}
public addChild(node: TreeNode): void {
if (!this._children) {
this._children = [];
}
this._children.push(node);
}
public clearChildren(): void {
if (this._children) {
this._children = [];
}
}
public abstract async getChildren(): Promise<TreeNode[]>;
public abstract getTreeItem(): vscode.TreeItem;
public abstract getNodeInfo(): azdata.NodeInfo;
}