mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
* Fix #6477 controller login + fix dashboard layout - Service endpoints shoudl be on own column, cut off smaller screen - Controller login not working due to 404 error This is due to a breaking API change We have requested fixes to help mitigate need for cluster name, but for now have a default value for this Finally, modified code so it's easier to update swagger API and also added instructions on how to update in future
This commit is contained in:
@@ -97,7 +97,8 @@ const indentationFilter = [
|
|||||||
'!extensions/import/flatfileimportservice/**',
|
'!extensions/import/flatfileimportservice/**',
|
||||||
'!extensions/admin-tool-ext-win/ssmsmin/**',
|
'!extensions/admin-tool-ext-win/ssmsmin/**',
|
||||||
'!extensions/resource-deployment/notebooks/**',
|
'!extensions/resource-deployment/notebooks/**',
|
||||||
'!extensions/mssql/notebooks/**'
|
'!extensions/mssql/notebooks/**',
|
||||||
|
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts'
|
||||||
];
|
];
|
||||||
|
|
||||||
const copyrightFilter = [
|
const copyrightFilter = [
|
||||||
@@ -184,7 +185,9 @@ const tslintFilter = [
|
|||||||
'!extensions/vscode-api-tests/testWorkspace/**',
|
'!extensions/vscode-api-tests/testWorkspace/**',
|
||||||
'!extensions/vscode-api-tests/testWorkspace2/**',
|
'!extensions/vscode-api-tests/testWorkspace2/**',
|
||||||
'!extensions/**/*.test.ts',
|
'!extensions/**/*.test.ts',
|
||||||
'!extensions/html-language-features/server/lib/jquery.d.ts'
|
'!extensions/html-language-features/server/lib/jquery.d.ts',
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts'
|
||||||
];
|
];
|
||||||
|
|
||||||
// {{SQL CARBON EDIT}}
|
// {{SQL CARBON EDIT}}
|
||||||
|
|||||||
12
extensions/big-data-cluster/instructions.txt
Normal file
12
extensions/big-data-cluster/instructions.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
How to update the Swagger-generated API to contact the controller
|
||||||
|
|
||||||
|
1. You need to get the API specification. Long-term you should be able to get from the server,
|
||||||
|
but for now go to the internal repository and find the checked in SwaggerClient.yaml there.
|
||||||
|
|
||||||
|
2. Copy the content from there, and add into https://editor.swagger.io/
|
||||||
|
3. Choose Generate Client, and choose Typescript-Node as the client to generate
|
||||||
|
4. This will download a zip file. Open it and copy contents of api.ts
|
||||||
|
5. Copy this content to apiGenerated.ts
|
||||||
|
- keep the copyright header and everything above the let defaultBasePath = xyz line,
|
||||||
|
- Override the rest of the file
|
||||||
|
6. Format the apiGenerated.ts file so it passes gulp hygiene
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,37 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { EndpointRouterApi } from './apiGenerated';
|
import * as request from 'request';
|
||||||
|
import { ClusterRouterApi, Authentication } from './apiGenerated';
|
||||||
|
|
||||||
|
|
||||||
|
class AuthConfiguration implements Authentication {
|
||||||
|
public username: string = '';
|
||||||
|
public password: string = '';
|
||||||
|
|
||||||
|
constructor(private _ignoreSslVerification: boolean) {
|
||||||
|
}
|
||||||
|
|
||||||
|
applyToRequest(requestOptions: request.Options): void {
|
||||||
|
requestOptions['agentOptions'] = {
|
||||||
|
rejectUnauthorized: !this._ignoreSslVerification
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClusterApiWrapper extends ClusterRouterApi {
|
||||||
|
constructor(basePathOrUsername: string, password?: string, basePath?: string, ignoreSslVerification?: boolean) {
|
||||||
|
super(basePathOrUsername, password, basePath);
|
||||||
|
this.authentications.default = new AuthConfiguration(!!ignoreSslVerification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getEndPoints(
|
export async function getEndPoints(
|
||||||
url: string, username: string, password: string, ignoreSslVerification?: boolean
|
clusterName: string,
|
||||||
|
url: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
ignoreSslVerification?: boolean
|
||||||
): Promise<IEndPointsResponse> {
|
): Promise<IEndPointsResponse> {
|
||||||
|
|
||||||
if (!url || !username || !password) {
|
if (!url || !username || !password) {
|
||||||
@@ -14,8 +41,7 @@ export async function getEndPoints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
url = adjustUrl(url);
|
url = adjustUrl(url);
|
||||||
let endPointApi = new EndpointRouterApi(username, password, url);
|
let endPointApi = new ClusterApiWrapper(username, password, url, !!ignoreSslVerification);
|
||||||
endPointApi.ignoreSslVerification = !!ignoreSslVerification;
|
|
||||||
|
|
||||||
let controllerResponse: IEndPointsResponse = undefined;
|
let controllerResponse: IEndPointsResponse = undefined;
|
||||||
let controllerError: IControllerError = undefined;
|
let controllerError: IControllerError = undefined;
|
||||||
@@ -27,7 +53,7 @@ export async function getEndPoints(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result = await endPointApi.endpointsGet();
|
let result = await endPointApi.endpointsGet(clusterName);
|
||||||
controllerResponse = <IEndPointsResponse>{
|
controllerResponse = <IEndPointsResponse>{
|
||||||
response: result.response as IHttpResponse,
|
response: result.response as IHttpResponse,
|
||||||
endPoints: result.body as IEndPoint[],
|
endPoints: result.body as IEndPoint[],
|
||||||
|
|||||||
@@ -19,25 +19,27 @@ export class AddControllerDialogModel {
|
|||||||
constructor(
|
constructor(
|
||||||
public treeDataProvider: ControllerTreeDataProvider,
|
public treeDataProvider: ControllerTreeDataProvider,
|
||||||
public node?: TreeNode,
|
public node?: TreeNode,
|
||||||
|
public prefilledClusterName?: string,
|
||||||
public prefilledUrl?: string,
|
public prefilledUrl?: string,
|
||||||
public prefilledUsername?: string,
|
public prefilledUsername?: string,
|
||||||
public prefilledPassword?: string,
|
public prefilledPassword?: string,
|
||||||
public prefilledRememberPassword?: boolean
|
public prefilledRememberPassword?: boolean
|
||||||
) {
|
) {
|
||||||
|
this.prefilledClusterName = prefilledClusterName || (node && node['clusterName']);
|
||||||
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
||||||
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
||||||
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
||||||
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
public async onComplete(clusterName: string, url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||||
let response = await getEndPoints(url, username, password, true);
|
let response = await getEndPoints(clusterName, url, username, password, true);
|
||||||
if (response && response.endPoints) {
|
if (response && response.endPoints) {
|
||||||
let masterInstance: IEndPoint = undefined;
|
let masterInstance: IEndPoint = undefined;
|
||||||
if (response.endPoints) {
|
if (response.endPoints) {
|
||||||
masterInstance = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
masterInstance = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
||||||
}
|
}
|
||||||
this.treeDataProvider.addController(url, username, password, rememberPassword, masterInstance);
|
this.treeDataProvider.addController(clusterName, url, username, password, rememberPassword, masterInstance);
|
||||||
await this.treeDataProvider.saveControllers();
|
await this.treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,7 @@ export class AddControllerDialog {
|
|||||||
private dialog: azdata.window.Dialog;
|
private dialog: azdata.window.Dialog;
|
||||||
private uiModelBuilder: azdata.ModelBuilder;
|
private uiModelBuilder: azdata.ModelBuilder;
|
||||||
|
|
||||||
|
private clusterNameInputBox: azdata.InputBoxComponent;
|
||||||
private urlInputBox: azdata.InputBoxComponent;
|
private urlInputBox: azdata.InputBoxComponent;
|
||||||
private usernameInputBox: azdata.InputBoxComponent;
|
private usernameInputBox: azdata.InputBoxComponent;
|
||||||
private passwordInputBox: azdata.InputBoxComponent;
|
private passwordInputBox: azdata.InputBoxComponent;
|
||||||
@@ -76,6 +79,11 @@ export class AddControllerDialog {
|
|||||||
this.dialog.registerContent(async view => {
|
this.dialog.registerContent(async view => {
|
||||||
this.uiModelBuilder = view.modelBuilder;
|
this.uiModelBuilder = view.modelBuilder;
|
||||||
|
|
||||||
|
this.clusterNameInputBox = this.uiModelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
placeHolder: localize('textClusterNameLower', 'mssql-cluster'),
|
||||||
|
value: this.model.prefilledUrl || 'mssql-cluster'
|
||||||
|
}).component();
|
||||||
this.urlInputBox = this.uiModelBuilder.inputBox()
|
this.urlInputBox = this.uiModelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
placeHolder: localize('textUrlLower', 'url'),
|
placeHolder: localize('textUrlLower', 'url'),
|
||||||
@@ -101,7 +109,12 @@ export class AddControllerDialog {
|
|||||||
|
|
||||||
let formModel = this.uiModelBuilder.formContainer()
|
let formModel = this.uiModelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
components: [{
|
components: [
|
||||||
|
{
|
||||||
|
component: this.clusterNameInputBox,
|
||||||
|
title: localize('textClusterNameCapital', 'Cluster Name'),
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
component: this.urlInputBox,
|
component: this.urlInputBox,
|
||||||
title: localize('textUrlCapital', 'URL'),
|
title: localize('textUrlCapital', 'URL'),
|
||||||
required: true
|
required: true
|
||||||
@@ -131,13 +144,14 @@ export class AddControllerDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async validate(): Promise<boolean> {
|
private async validate(): Promise<boolean> {
|
||||||
|
let clusterName = this.clusterNameInputBox && this.clusterNameInputBox.value;
|
||||||
let url = this.urlInputBox && this.urlInputBox.value;
|
let url = this.urlInputBox && this.urlInputBox.value;
|
||||||
let username = this.usernameInputBox && this.usernameInputBox.value;
|
let username = this.usernameInputBox && this.usernameInputBox.value;
|
||||||
let password = this.passwordInputBox && this.passwordInputBox.value;
|
let password = this.passwordInputBox && this.passwordInputBox.value;
|
||||||
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
|
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.model.onComplete(url, username, password, rememberPassword);
|
await this.model.onComplete(clusterName, url, username, password, rememberPassword);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorMessage(error);
|
showErrorMessage(error);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { LoadingControllerNode } from './loadingControllerNode';
|
|||||||
const CredentialNamespace = 'clusterControllerCredentials';
|
const CredentialNamespace = 'clusterControllerCredentials';
|
||||||
|
|
||||||
interface IControllerInfoSlim {
|
interface IControllerInfoSlim {
|
||||||
|
clusterName: string;
|
||||||
url: string;
|
url: string;
|
||||||
username: string;
|
username: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
@@ -57,6 +58,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public addController(
|
public addController(
|
||||||
|
clusterName: string,
|
||||||
url: string,
|
url: string,
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
@@ -64,7 +66,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
|||||||
masterInstance?: IEndPoint
|
masterInstance?: IEndPoint
|
||||||
): void {
|
): void {
|
||||||
this.removeNonControllerNodes();
|
this.removeNonControllerNodes();
|
||||||
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
|
this.root.addControllerNode(clusterName, url, username, password, rememberPassword, masterInstance);
|
||||||
this.notifyNodeChanged();
|
this.notifyNodeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +120,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
|||||||
password = await this.getPassword(c.url, c.username);
|
password = await this.getPassword(c.url, c.username);
|
||||||
}
|
}
|
||||||
this.root.addChild(new ControllerNode(
|
this.root.addChild(new ControllerNode(
|
||||||
c.url, c.username, password, c.rememberPassword,
|
c.clusterName, c.url, c.username, password, c.rememberPassword,
|
||||||
undefined, this.root, this, undefined
|
undefined, this.root, this, undefined
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -136,6 +138,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
|||||||
let controllers = this.root.children.map(e => {
|
let controllers = this.root.children.map(e => {
|
||||||
let controller = e as ControllerNode;
|
let controller = e as ControllerNode;
|
||||||
return <IControllerInfoSlim>{
|
return <IControllerInfoSlim>{
|
||||||
|
clusterName: controller.clusterName,
|
||||||
url: controller.url,
|
url: controller.url,
|
||||||
username: controller.username,
|
username: controller.username,
|
||||||
password: controller.password,
|
password: controller.password,
|
||||||
@@ -145,6 +148,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
|||||||
|
|
||||||
let controllersWithoutPassword = controllers.map(e => {
|
let controllersWithoutPassword = controllers.map(e => {
|
||||||
return <IControllerInfoSlim>{
|
return <IControllerInfoSlim>{
|
||||||
|
clusterName: e.clusterName,
|
||||||
url: e.url,
|
url: e.url,
|
||||||
username: e.username,
|
username: e.username,
|
||||||
rememberPassword: e.rememberPassword
|
rememberPassword: e.rememberPassword
|
||||||
|
|||||||
@@ -113,14 +113,20 @@ export class ControllerRootNode extends ControllerTreeNode {
|
|||||||
return this.children as ControllerNode[];
|
return this.children as ControllerNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public addControllerNode(url: string, username: string, password: string, rememberPassword: boolean, masterInstance?: IEndPoint): void {
|
public addControllerNode(clusterName: string,
|
||||||
|
url: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
rememberPassword: boolean,
|
||||||
|
masterInstance?: IEndPoint
|
||||||
|
): void {
|
||||||
let controllerNode = this.getExistingControllerNode(url, username);
|
let controllerNode = this.getExistingControllerNode(url, username);
|
||||||
if (controllerNode) {
|
if (controllerNode) {
|
||||||
controllerNode.password = password;
|
controllerNode.password = password;
|
||||||
controllerNode.rememberPassword = rememberPassword;
|
controllerNode.rememberPassword = rememberPassword;
|
||||||
controllerNode.clearChildren();
|
controllerNode.clearChildren();
|
||||||
} else {
|
} else {
|
||||||
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
controllerNode = new ControllerNode(clusterName, url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
||||||
this.addChild(controllerNode);
|
this.addChild(controllerNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +164,7 @@ export class ControllerRootNode extends ControllerTreeNode {
|
|||||||
export class ControllerNode extends ControllerTreeNode {
|
export class ControllerNode extends ControllerTreeNode {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private _clusterName: string,
|
||||||
private _url: string,
|
private _url: string,
|
||||||
private _username: string,
|
private _username: string,
|
||||||
private _password: string,
|
private _password: string,
|
||||||
@@ -184,7 +191,7 @@ export class ControllerNode extends ControllerTreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await getEndPoints(this._url, this._username, this._password, true);
|
let response = await getEndPoints(this._clusterName, this._url, this._username, this._password, true);
|
||||||
if (response && response.endPoints) {
|
if (response && response.endPoints) {
|
||||||
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
||||||
this.addSqlMasterNode(master.endpoint, master.description);
|
this.addSqlMasterNode(master.endpoint, master.description);
|
||||||
@@ -226,6 +233,14 @@ export class ControllerNode extends ControllerTreeNode {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get clusterName() {
|
||||||
|
return this._clusterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set clusterName(clusterName: string) {
|
||||||
|
this._clusterName = clusterName;
|
||||||
|
}
|
||||||
|
|
||||||
public get url() {
|
public get url() {
|
||||||
return this._url;
|
return this._url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
|
|||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
const AddControllerCommand = 'bigDataClusters.command.addController';
|
||||||
|
const DeleteControllerCommand = 'bigDataClusters.command.deleteController';
|
||||||
|
const RefreshControllerCommand = 'bigDataClusters.command.refreshController';
|
||||||
|
|
||||||
|
let throttleTimers: { [key: string]: any } = {};
|
||||||
|
|
||||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||||
IconPath.setExtensionContext(extensionContext);
|
IconPath.setExtensionContext(extensionContext);
|
||||||
let treeDataProvider = new ControllerTreeDataProvider(extensionContext.globalState);
|
let treeDataProvider = new ControllerTreeDataProvider(extensionContext.globalState);
|
||||||
@@ -31,15 +37,15 @@ function registerTreeDataProvider(treeDataProvider: ControllerTreeDataProvider):
|
|||||||
}
|
}
|
||||||
|
|
||||||
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
|
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
|
||||||
vscode.commands.registerCommand('bigDataClusters.command.addController', (node?: TreeNode) => {
|
vscode.commands.registerCommand(AddControllerCommand, (node?: TreeNode) => {
|
||||||
addBdcController(treeDataProvider, node);
|
runThrottledAction(AddControllerCommand, () => addBdcController(treeDataProvider, node));
|
||||||
});
|
});
|
||||||
|
|
||||||
vscode.commands.registerCommand('bigDataClusters.command.deleteController', (node: TreeNode) => {
|
vscode.commands.registerCommand(DeleteControllerCommand, (node: TreeNode) => {
|
||||||
deleteBdcController(treeDataProvider, node);
|
deleteBdcController(treeDataProvider, node);
|
||||||
});
|
});
|
||||||
|
|
||||||
vscode.commands.registerCommand('bigDataClusters.command.refreshController', (node: TreeNode) => {
|
vscode.commands.registerCommand(RefreshControllerCommand, (node: TreeNode) => {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -83,3 +89,21 @@ function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider,
|
|||||||
treeDataProvider.saveControllers();
|
treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttles actions to avoid bug where on clicking in tree, action gets called twice
|
||||||
|
* instead of once. Any right-click action is safe, just the default on-click action in a tree
|
||||||
|
*/
|
||||||
|
function runThrottledAction(id: string, action: () => void) {
|
||||||
|
let timer = throttleTimers[id];
|
||||||
|
if (!timer) {
|
||||||
|
throttleTimers[id] = timer = setTimeout(() => {
|
||||||
|
action();
|
||||||
|
clearTimeout(timer);
|
||||||
|
throttleTimers[id] = undefined;
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
// else ignore this as we got an identical action in the last 150ms
|
||||||
|
}
|
||||||
@@ -416,8 +416,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "%title.endpoints%",
|
"name": "%title.endpoints%",
|
||||||
"row": 0,
|
"row": 1,
|
||||||
"col": 4,
|
"col": 0,
|
||||||
"rowspan": 1.5,
|
"rowspan": 1.5,
|
||||||
"colspan": 2,
|
"colspan": 2,
|
||||||
"widget": {
|
"widget": {
|
||||||
|
|||||||
Reference in New Issue
Block a user