mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 17:23:05 -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/admin-tool-ext-win/ssmsmin/**',
|
||||
'!extensions/resource-deployment/notebooks/**',
|
||||
'!extensions/mssql/notebooks/**'
|
||||
'!extensions/mssql/notebooks/**',
|
||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts'
|
||||
];
|
||||
|
||||
const copyrightFilter = [
|
||||
@@ -184,7 +185,9 @@ const tslintFilter = [
|
||||
'!extensions/vscode-api-tests/testWorkspace/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace2/**',
|
||||
'!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}}
|
||||
|
||||
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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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(
|
||||
url: string, username: string, password: string, ignoreSslVerification?: boolean
|
||||
clusterName: string,
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
ignoreSslVerification?: boolean
|
||||
): Promise<IEndPointsResponse> {
|
||||
|
||||
if (!url || !username || !password) {
|
||||
@@ -14,8 +41,7 @@ export async function getEndPoints(
|
||||
}
|
||||
|
||||
url = adjustUrl(url);
|
||||
let endPointApi = new EndpointRouterApi(username, password, url);
|
||||
endPointApi.ignoreSslVerification = !!ignoreSslVerification;
|
||||
let endPointApi = new ClusterApiWrapper(username, password, url, !!ignoreSslVerification);
|
||||
|
||||
let controllerResponse: IEndPointsResponse = undefined;
|
||||
let controllerError: IControllerError = undefined;
|
||||
@@ -27,7 +53,7 @@ export async function getEndPoints(
|
||||
};
|
||||
|
||||
try {
|
||||
let result = await endPointApi.endpointsGet();
|
||||
let result = await endPointApi.endpointsGet(clusterName);
|
||||
controllerResponse = <IEndPointsResponse>{
|
||||
response: result.response as IHttpResponse,
|
||||
endPoints: result.body as IEndPoint[],
|
||||
|
||||
@@ -19,25 +19,27 @@ export class AddControllerDialogModel {
|
||||
constructor(
|
||||
public treeDataProvider: ControllerTreeDataProvider,
|
||||
public node?: TreeNode,
|
||||
public prefilledClusterName?: string,
|
||||
public prefilledUrl?: string,
|
||||
public prefilledUsername?: string,
|
||||
public prefilledPassword?: string,
|
||||
public prefilledRememberPassword?: boolean
|
||||
) {
|
||||
this.prefilledClusterName = prefilledClusterName || (node && node['clusterName']);
|
||||
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
||||
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
||||
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
||||
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
||||
}
|
||||
|
||||
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
let response = await getEndPoints(url, username, password, true);
|
||||
public async onComplete(clusterName: string, url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
let response = await getEndPoints(clusterName, url, username, password, true);
|
||||
if (response && response.endPoints) {
|
||||
let masterInstance: IEndPoint = undefined;
|
||||
if (response.endPoints) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +60,7 @@ export class AddControllerDialog {
|
||||
private dialog: azdata.window.Dialog;
|
||||
private uiModelBuilder: azdata.ModelBuilder;
|
||||
|
||||
private clusterNameInputBox: azdata.InputBoxComponent;
|
||||
private urlInputBox: azdata.InputBoxComponent;
|
||||
private usernameInputBox: azdata.InputBoxComponent;
|
||||
private passwordInputBox: azdata.InputBoxComponent;
|
||||
@@ -76,6 +79,11 @@ export class AddControllerDialog {
|
||||
this.dialog.registerContent(async view => {
|
||||
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()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
placeHolder: localize('textUrlLower', 'url'),
|
||||
@@ -101,22 +109,27 @@ export class AddControllerDialog {
|
||||
|
||||
let formModel = this.uiModelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [{
|
||||
component: this.urlInputBox,
|
||||
title: localize('textUrlCapital', 'URL'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: localize('textUsernameCapital', 'Username'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: localize('textPasswordCapital', 'Password'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
}
|
||||
components: [
|
||||
{
|
||||
component: this.clusterNameInputBox,
|
||||
title: localize('textClusterNameCapital', 'Cluster Name'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.urlInputBox,
|
||||
title: localize('textUrlCapital', 'URL'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: localize('textUsernameCapital', 'Username'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: localize('textPasswordCapital', 'Password'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
@@ -131,13 +144,14 @@ export class AddControllerDialog {
|
||||
}
|
||||
|
||||
private async validate(): Promise<boolean> {
|
||||
let clusterName = this.clusterNameInputBox && this.clusterNameInputBox.value;
|
||||
let url = this.urlInputBox && this.urlInputBox.value;
|
||||
let username = this.usernameInputBox && this.usernameInputBox.value;
|
||||
let password = this.passwordInputBox && this.passwordInputBox.value;
|
||||
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
|
||||
|
||||
try {
|
||||
await this.model.onComplete(url, username, password, rememberPassword);
|
||||
await this.model.onComplete(clusterName, url, username, password, rememberPassword);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { LoadingControllerNode } from './loadingControllerNode';
|
||||
const CredentialNamespace = 'clusterControllerCredentials';
|
||||
|
||||
interface IControllerInfoSlim {
|
||||
clusterName: string;
|
||||
url: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
@@ -57,6 +58,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
}
|
||||
|
||||
public addController(
|
||||
clusterName: string,
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
@@ -64,7 +66,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
masterInstance?: IEndPoint
|
||||
): void {
|
||||
this.removeNonControllerNodes();
|
||||
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
|
||||
this.root.addControllerNode(clusterName, url, username, password, rememberPassword, masterInstance);
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
|
||||
@@ -118,7 +120,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
password = await this.getPassword(c.url, c.username);
|
||||
}
|
||||
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
|
||||
));
|
||||
}
|
||||
@@ -136,6 +138,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
let controllers = this.root.children.map(e => {
|
||||
let controller = e as ControllerNode;
|
||||
return <IControllerInfoSlim>{
|
||||
clusterName: controller.clusterName,
|
||||
url: controller.url,
|
||||
username: controller.username,
|
||||
password: controller.password,
|
||||
@@ -145,6 +148,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
|
||||
|
||||
let controllersWithoutPassword = controllers.map(e => {
|
||||
return <IControllerInfoSlim>{
|
||||
clusterName: e.clusterName,
|
||||
url: e.url,
|
||||
username: e.username,
|
||||
rememberPassword: e.rememberPassword
|
||||
|
||||
@@ -113,14 +113,20 @@ export class ControllerRootNode extends ControllerTreeNode {
|
||||
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);
|
||||
if (controllerNode) {
|
||||
controllerNode.password = password;
|
||||
controllerNode.rememberPassword = rememberPassword;
|
||||
controllerNode.clearChildren();
|
||||
} 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);
|
||||
}
|
||||
|
||||
@@ -158,6 +164,7 @@ export class ControllerRootNode extends ControllerTreeNode {
|
||||
export class ControllerNode extends ControllerTreeNode {
|
||||
|
||||
constructor(
|
||||
private _clusterName: string,
|
||||
private _url: string,
|
||||
private _username: string,
|
||||
private _password: string,
|
||||
@@ -184,7 +191,7 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
}
|
||||
|
||||
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) {
|
||||
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
||||
this.addSqlMasterNode(master.endpoint, master.description);
|
||||
@@ -226,6 +233,14 @@ export class ControllerNode extends ControllerTreeNode {
|
||||
return item;
|
||||
}
|
||||
|
||||
public get clusterName() {
|
||||
return this._clusterName;
|
||||
}
|
||||
|
||||
public set clusterName(clusterName: string) {
|
||||
this._clusterName = clusterName;
|
||||
}
|
||||
|
||||
public get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
|
||||
|
||||
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) {
|
||||
IconPath.setExtensionContext(extensionContext);
|
||||
let treeDataProvider = new ControllerTreeDataProvider(extensionContext.globalState);
|
||||
@@ -31,15 +37,15 @@ function registerTreeDataProvider(treeDataProvider: ControllerTreeDataProvider):
|
||||
}
|
||||
|
||||
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
|
||||
vscode.commands.registerCommand('bigDataClusters.command.addController', (node?: TreeNode) => {
|
||||
addBdcController(treeDataProvider, node);
|
||||
vscode.commands.registerCommand(AddControllerCommand, (node?: TreeNode) => {
|
||||
runThrottledAction(AddControllerCommand, () => addBdcController(treeDataProvider, node));
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('bigDataClusters.command.deleteController', (node: TreeNode) => {
|
||||
vscode.commands.registerCommand(DeleteControllerCommand, (node: TreeNode) => {
|
||||
deleteBdcController(treeDataProvider, node);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('bigDataClusters.command.refreshController', (node: TreeNode) => {
|
||||
vscode.commands.registerCommand(RefreshControllerCommand, (node: TreeNode) => {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
@@ -83,3 +89,21 @@ function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider,
|
||||
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%",
|
||||
"row": 0,
|
||||
"col": 4,
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"rowspan": 1.5,
|
||||
"colspan": 2,
|
||||
"widget": {
|
||||
|
||||
Reference in New Issue
Block a user