mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Add support for firewall rule name (#21430)
This commit is contained in:
@@ -7,35 +7,98 @@ import { RequestType } from 'vscode-languageclient';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
// ------------------------------- < Resource Events > ------------------------------------
|
// ------------------------------- < Resource Events > ------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to open up a firewall rule
|
||||||
|
*/
|
||||||
export namespace CreateFirewallRuleRequest {
|
export namespace CreateFirewallRuleRequest {
|
||||||
export const type = new RequestType<CreateFirewallRuleParams, CreateFirewallRuleResponse, void, void>('resource/createFirewallRule');
|
export const type = new RequestType<CreateFirewallRuleParams, CreateFirewallRuleResponse, void, void>('resource/createFirewallRule');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall rule request handler
|
||||||
|
*/
|
||||||
export namespace HandleFirewallRuleRequest {
|
export namespace HandleFirewallRuleRequest {
|
||||||
export const type = new RequestType<HandleFirewallRuleParams, HandleFirewallRuleResponse, void, void>('resource/handleFirewallRule');
|
export const type = new RequestType<HandleFirewallRuleParams, HandleFirewallRuleResponse, void, void>('resource/handleFirewallRule');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Firewall rule interfaces
|
/**
|
||||||
|
* Firewall rule creation parameters
|
||||||
|
*/
|
||||||
export interface CreateFirewallRuleParams {
|
export interface CreateFirewallRuleParams {
|
||||||
|
/**
|
||||||
|
* Account information to use in connecting to Azure
|
||||||
|
*/
|
||||||
account: azdata.Account;
|
account: azdata.Account;
|
||||||
|
/**
|
||||||
|
* Fully qualified name of the server to create a new firewall rule on
|
||||||
|
*/
|
||||||
serverName: string;
|
serverName: string;
|
||||||
|
/**
|
||||||
|
* Firewall rule name to set
|
||||||
|
*/
|
||||||
|
firewallRuleName: string;
|
||||||
|
/**
|
||||||
|
* Start of the IP address range
|
||||||
|
*/
|
||||||
startIpAddress: string;
|
startIpAddress: string;
|
||||||
|
/**
|
||||||
|
* End of the IP address range
|
||||||
|
*/
|
||||||
endIpAddress: string;
|
endIpAddress: string;
|
||||||
|
/**
|
||||||
|
* Per-tenant token mappings. Ideally would be set independently of this call,
|
||||||
|
* but for now this allows us to get the tokens necessary to find a server and open a firewall rule
|
||||||
|
*/
|
||||||
securityTokenMappings: {};
|
securityTokenMappings: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall rule creation response
|
||||||
|
*/
|
||||||
interface CreateFirewallRuleResponse {
|
interface CreateFirewallRuleResponse {
|
||||||
|
/**
|
||||||
|
* Whether or not request can be handled.
|
||||||
|
*/
|
||||||
result: boolean;
|
result: boolean;
|
||||||
|
/**
|
||||||
|
* Contains error message, if request could not be handled.
|
||||||
|
*/
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall rule handling parameters
|
||||||
|
*/
|
||||||
export interface HandleFirewallRuleParams {
|
export interface HandleFirewallRuleParams {
|
||||||
|
/**
|
||||||
|
* The error code used to defined the error type
|
||||||
|
*/
|
||||||
errorCode: number;
|
errorCode: number;
|
||||||
|
/**
|
||||||
|
* The error message from which to parse the IP address
|
||||||
|
*/
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
|
/**
|
||||||
|
* The connection type, for example MSSQL
|
||||||
|
*/
|
||||||
connectionTypeId: string;
|
connectionTypeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response to the check for Firewall rule support given an error message
|
||||||
|
*/
|
||||||
interface HandleFirewallRuleResponse {
|
interface HandleFirewallRuleResponse {
|
||||||
|
/**
|
||||||
|
* Whether or not request can be handled.
|
||||||
|
*/
|
||||||
result: boolean;
|
result: boolean;
|
||||||
|
/**
|
||||||
|
* Contains error message, if request could not be handled.
|
||||||
|
*/
|
||||||
|
errorMessage: string;
|
||||||
|
/**
|
||||||
|
* If handled, the default IP address to send back; so users can tell what their blocked IP is.
|
||||||
|
*/
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class FireWallFeature extends SqlOpsFeature<any> {
|
|||||||
function asCreateFirewallRuleParams(account: azdata.Account, params: azdata.FirewallRuleInfo): CreateFirewallRuleParams {
|
function asCreateFirewallRuleParams(account: azdata.Account, params: azdata.FirewallRuleInfo): CreateFirewallRuleParams {
|
||||||
return {
|
return {
|
||||||
account: account,
|
account: account,
|
||||||
|
firewallRuleName: params.firewallRuleName,
|
||||||
serverName: params.serverName,
|
serverName: params.serverName,
|
||||||
startIpAddress: params.startIpAddress,
|
startIpAddress: params.startIpAddress,
|
||||||
endIpAddress: params.endIpAddress,
|
endIpAddress: params.endIpAddress,
|
||||||
|
|||||||
54
src/sql/azdata.d.ts
vendored
54
src/sql/azdata.d.ts
vendored
@@ -2642,24 +2642,78 @@ declare module 'azdata' {
|
|||||||
* Represents a provider of resource
|
* Represents a provider of resource
|
||||||
*/
|
*/
|
||||||
export interface ResourceProvider {
|
export interface ResourceProvider {
|
||||||
|
/**
|
||||||
|
* Creates a firewall rule for the given account
|
||||||
|
* @param account Account with which firewall rule request will be made.
|
||||||
|
* @param firewallruleInfo Firewall rule creation information
|
||||||
|
*/
|
||||||
createFirewallRule(account: Account, firewallruleInfo: FirewallRuleInfo): Thenable<CreateFirewallRuleResponse>;
|
createFirewallRule(account: Account, firewallruleInfo: FirewallRuleInfo): Thenable<CreateFirewallRuleResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the response from the firewall rule creation request
|
||||||
|
* @param errorCode Error code from the firewall rule creation request
|
||||||
|
* @param errorMessage Error message from the firewall rule creation request
|
||||||
|
* @param connectionTypeId Connection type id of the firewall rule creation request
|
||||||
|
*/
|
||||||
handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<HandleFirewallRuleResponse>;
|
handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<HandleFirewallRuleResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall rule creation information
|
||||||
|
*/
|
||||||
export interface FirewallRuleInfo {
|
export interface FirewallRuleInfo {
|
||||||
|
/**
|
||||||
|
* Start of the IP address range
|
||||||
|
*/
|
||||||
startIpAddress?: string | undefined;
|
startIpAddress?: string | undefined;
|
||||||
|
/**
|
||||||
|
* End of the IP address range
|
||||||
|
*/
|
||||||
endIpAddress?: string | undefined;
|
endIpAddress?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Fully qualified name of the server to create a new firewall rule on
|
||||||
|
*/
|
||||||
serverName: string;
|
serverName: string;
|
||||||
|
/**
|
||||||
|
* Firewall rule name to set
|
||||||
|
*/
|
||||||
|
firewallRuleName: string;
|
||||||
|
/**
|
||||||
|
* Per-tenant token mappings. Ideally would be set independently of this call,
|
||||||
|
* but for now this allows us to get the tokens necessary to find a server and open a firewall rule
|
||||||
|
*/
|
||||||
securityTokenMappings: {};
|
securityTokenMappings: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firewall rule creation response
|
||||||
|
*/
|
||||||
export interface CreateFirewallRuleResponse {
|
export interface CreateFirewallRuleResponse {
|
||||||
|
/**
|
||||||
|
* Whether or not request can be handled.
|
||||||
|
*/
|
||||||
result: boolean;
|
result: boolean;
|
||||||
|
/**
|
||||||
|
* Contains error message, if request could not be handled.
|
||||||
|
*/
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response to the check for Firewall rule support given an error message
|
||||||
|
*/
|
||||||
export interface HandleFirewallRuleResponse {
|
export interface HandleFirewallRuleResponse {
|
||||||
|
/**
|
||||||
|
* Whether or not request can be handled.
|
||||||
|
*/
|
||||||
result: boolean;
|
result: boolean;
|
||||||
|
/**
|
||||||
|
* Contains error message, if request could not be handled.
|
||||||
|
*/
|
||||||
|
errorMessage: string;
|
||||||
|
/**
|
||||||
|
* If handled, the default IP address to send back; so users can tell what their blocked IP is.
|
||||||
|
*/
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class DropdownList extends Dropdown {
|
|||||||
protected backgroundColor?: Color;
|
protected backgroundColor?: Color;
|
||||||
protected foregroundColor?: Color;
|
protected foregroundColor?: Color;
|
||||||
protected borderColor?: Color;
|
protected borderColor?: Color;
|
||||||
|
protected borderWidth = 1;
|
||||||
|
|
||||||
private button?: Button;
|
private button?: Button;
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ export class DropdownList extends Dropdown {
|
|||||||
*/
|
*/
|
||||||
protected override renderContents(container: HTMLElement): IDisposable {
|
protected override renderContents(container: HTMLElement): IDisposable {
|
||||||
let div = DOM.append(container, this._contentContainer);
|
let div = DOM.append(container, this._contentContainer);
|
||||||
div.style.width = DOM.getTotalWidth(this.element) + 'px';
|
div.style.width = (DOM.getTotalWidth(this.element) - this.borderWidth * 2) + 'px'; // Subtract border width
|
||||||
return { dispose: () => { } };
|
return { dispose: () => { } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +146,8 @@ export class DropdownList extends Dropdown {
|
|||||||
element.style.backgroundColor = background;
|
element.style.backgroundColor = background;
|
||||||
element.style.color = foreground;
|
element.style.color = foreground;
|
||||||
|
|
||||||
element.style.borderWidth = border ? '1px' : '';
|
this.borderWidth = border ? 1 : 0;
|
||||||
|
element.style.borderWidth = border ? this.borderWidth + 'px' : '';
|
||||||
element.style.borderStyle = border ? 'solid' : '';
|
element.style.borderStyle = border ? 'solid' : '';
|
||||||
element.style.borderColor = border;
|
element.style.borderColor = border;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export class FirewallRuleViewModel {
|
|||||||
public selectedAccount: azdata.Account | undefined;
|
public selectedAccount: azdata.Account | undefined;
|
||||||
public selectedTenantId: string | undefined;
|
public selectedTenantId: string | undefined;
|
||||||
|
|
||||||
|
private _defaultFirewallRuleName: string;
|
||||||
|
private _firewallRuleName: string;
|
||||||
private _defaultIPAddress?: string;
|
private _defaultIPAddress?: string;
|
||||||
private _defaultFromSubnetIPRange?: string;
|
private _defaultFromSubnetIPRange?: string;
|
||||||
private _defaultToSubnetIPRange?: string;
|
private _defaultToSubnetIPRange?: string;
|
||||||
@@ -23,6 +25,22 @@ export class FirewallRuleViewModel {
|
|||||||
this.isIPAddressSelected = true;
|
this.isIPAddressSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public set defaultFirewallRuleName(ruleName: string) {
|
||||||
|
this._defaultFirewallRuleName = ruleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get defaultFirewallRuleName(): string | undefined {
|
||||||
|
return this._defaultFirewallRuleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set firewallRuleName(ruleName: string) {
|
||||||
|
this._firewallRuleName = ruleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get firewallRuleName(): string | undefined {
|
||||||
|
return this._firewallRuleName;
|
||||||
|
}
|
||||||
|
|
||||||
public get defaultIPAddress(): string | undefined {
|
public get defaultIPAddress(): string | undefined {
|
||||||
return this._defaultIPAddress;
|
return this._defaultIPAddress;
|
||||||
}
|
}
|
||||||
@@ -60,6 +78,29 @@ export class FirewallRuleViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updateDefaultValues(ipAddress: string): void {
|
public updateDefaultValues(ipAddress: string): void {
|
||||||
|
function padTo2Digits(num: number) {
|
||||||
|
return num.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// format as "YYYY-MM-DD_hh-mm-ss" (default Azure rulename format)
|
||||||
|
function formatDate(date: Date) {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
date.getFullYear(),
|
||||||
|
padTo2Digits(date.getMonth() + 1),
|
||||||
|
padTo2Digits(date.getDate()),
|
||||||
|
].join('-') +
|
||||||
|
'_' +
|
||||||
|
[
|
||||||
|
padTo2Digits(date.getHours()),
|
||||||
|
padTo2Digits(date.getMinutes()),
|
||||||
|
padTo2Digits(date.getSeconds()),
|
||||||
|
].join('-')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default rule name format as Azure portal.
|
||||||
|
this._defaultFirewallRuleName = `ClientIPAddress_${formatDate(new Date())}`;
|
||||||
this._defaultIPAddress = ipAddress;
|
this._defaultIPAddress = ipAddress;
|
||||||
this._defaultFromSubnetIPRange = ipAddress.replace(/\.[0-9]+$/g, '.0');
|
this._defaultFromSubnetIPRange = ipAddress.replace(/\.[0-9]+$/g, '.0');
|
||||||
this._defaultToSubnetIPRange = ipAddress.replace(/\.[0-9]+$/g, '.255');
|
this._defaultToSubnetIPRange = ipAddress.replace(/\.[0-9]+$/g, '.255');
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { Tenant, TenantListDelegate, TenantPickerListRenderer } from 'sql/workbe
|
|||||||
|
|
||||||
export class AccountPicker extends Disposable {
|
export class AccountPicker extends Disposable {
|
||||||
public static ACCOUNTPICKERLIST_HEIGHT = 47;
|
public static ACCOUNTPICKERLIST_HEIGHT = 47;
|
||||||
|
public static ACCOUNTTENANTLIST_HEIGHT = 32;
|
||||||
public viewModel: AccountPickerViewModel;
|
public viewModel: AccountPickerViewModel;
|
||||||
private _accountList?: List<azdata.Account>;
|
private _accountList?: List<azdata.Account>;
|
||||||
private _rootContainer?: HTMLElement;
|
private _rootContainer?: HTMLElement;
|
||||||
@@ -97,7 +98,7 @@ export class AccountPicker extends Disposable {
|
|||||||
public createAccountPickerComponent() {
|
public createAccountPickerComponent() {
|
||||||
// Create an account list
|
// Create an account list
|
||||||
const accountDelegate = new AccountListDelegate(AccountPicker.ACCOUNTPICKERLIST_HEIGHT);
|
const accountDelegate = new AccountListDelegate(AccountPicker.ACCOUNTPICKERLIST_HEIGHT);
|
||||||
const tenantDelegate = new TenantListDelegate(AccountPicker.ACCOUNTPICKERLIST_HEIGHT);
|
const tenantDelegate = new TenantListDelegate(AccountPicker.ACCOUNTTENANTLIST_HEIGHT);
|
||||||
|
|
||||||
const accountRenderer = new AccountPickerListRenderer();
|
const accountRenderer = new AccountPickerListRenderer();
|
||||||
const tenantRenderer = new TenantPickerListRenderer();
|
const tenantRenderer = new TenantPickerListRenderer();
|
||||||
@@ -203,7 +204,7 @@ export class AccountPicker extends Disposable {
|
|||||||
private createLabelElement(content: string, isHeader?: boolean) {
|
private createLabelElement(content: string, isHeader?: boolean) {
|
||||||
let className = 'dialog-label';
|
let className = 'dialog-label';
|
||||||
if (isHeader) {
|
if (isHeader) {
|
||||||
className += ' header';
|
className += '.header';
|
||||||
}
|
}
|
||||||
const element = DOM.$(`.${className}`);
|
const element = DOM.$(`.${className}`);
|
||||||
element.innerText = content;
|
element.innerText = content;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
/*--------------------------------------------------------------------------------------------
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* 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.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
@@ -12,17 +12,18 @@
|
|||||||
.list-row.account-picker-list .label,
|
.list-row.account-picker-list .label,
|
||||||
.list-row.tenant-picker-list .label {
|
.list-row.tenant-picker-list .label {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-left: 15px;
|
margin-left: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-row.account-picker-list .label .contextual-display-name {
|
.list-row.account-picker-list .label .contextual-display-name {
|
||||||
font-size: 15px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-row.account-picker-list .label .display-name,
|
.list-row.account-picker-list .label .display-name,
|
||||||
.list-row.tenant-picker-list .label .display-name {
|
.list-row.tenant-picker-list .label .display-name {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-row.account-picker-list .label .content,
|
.list-row.account-picker-list .label .content,
|
||||||
|
|||||||
@@ -57,10 +57,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.account-list-container .list-row .codicon {
|
.account-list-container .list-row .codicon {
|
||||||
flex: 0 0 35px;
|
flex: 0 0 32px;
|
||||||
height: 35px;
|
height: 32px;
|
||||||
width: 35px;
|
width: 32px;
|
||||||
background-size: 35px;
|
background-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-list-container .list-row .codicon .badge {
|
.account-list-container .list-row .codicon .badge {
|
||||||
|
|||||||
@@ -38,13 +38,24 @@ const firewallHelpUri = 'https://aka.ms/sqlopsfirewallhelp';
|
|||||||
|
|
||||||
const LocalizedStrings = {
|
const LocalizedStrings = {
|
||||||
FROM: localize('from', "From"),
|
FROM: localize('from', "From"),
|
||||||
TO: localize('to', "To")
|
TO: localize('to', "To"),
|
||||||
|
OK: localize('firewall.ok', "OK"),
|
||||||
|
Cancel: localize('firewall.cancel', "Cancel"),
|
||||||
|
RuleName: localize('firewall.ruleName', "Rule name"),
|
||||||
|
CreateNewFirewallRule: localize('createNewFirewallRule', "Create new firewall rule"),
|
||||||
|
FirewallRuleLabel: localize('filewallRule', "Firewall rule"),
|
||||||
|
FirewallRuleDescription: localize('firewallRuleDescription',
|
||||||
|
"A firewall rule is required to access the SQL Server instance. Click the link below to create a new firewall rule."),
|
||||||
|
FirewallRuleHelpLink: localize('firewallRuleHelpLink', "Learn more about firewall rules"),
|
||||||
|
AddClientIPLabel: localize('addIPAddressLabel', "Add my client IP "),
|
||||||
|
AddIPRangeLabel: localize('addIpRangeLabel', "Add my subnet IP range")
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FirewallRuleDialog extends Modal {
|
export class FirewallRuleDialog extends Modal {
|
||||||
public viewModel: FirewallRuleViewModel;
|
public viewModel: FirewallRuleViewModel;
|
||||||
private _createButton?: Button;
|
private _createButton?: Button;
|
||||||
private _closeButton?: Button;
|
private _closeButton?: Button;
|
||||||
|
private _ruleNameInpuBox?: InputBox;
|
||||||
private _fromRangeinputBox?: InputBox;
|
private _fromRangeinputBox?: InputBox;
|
||||||
private _toRangeinputBox?: InputBox;
|
private _toRangeinputBox?: InputBox;
|
||||||
|
|
||||||
@@ -77,7 +88,7 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
@IOpenerService private readonly openerService: IOpenerService
|
@IOpenerService private readonly openerService: IOpenerService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
localize('createNewFirewallRule', "Create new firewall rule"),
|
LocalizedStrings.CreateNewFirewallRule,
|
||||||
TelemetryKeys.ModalDialogName.FireWallRule,
|
TelemetryKeys.ModalDialogName.FireWallRule,
|
||||||
telemetryService,
|
telemetryService,
|
||||||
layoutService,
|
layoutService,
|
||||||
@@ -106,30 +117,97 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
attachModalDialogStyler(this, this._themeService);
|
attachModalDialogStyler(this, this._themeService);
|
||||||
this.backButton!.onDidClick(() => this.cancel());
|
this.backButton!.onDidClick(() => this.cancel());
|
||||||
this._register(attachButtonStyler(this.backButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
|
this._register(attachButtonStyler(this.backButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
|
||||||
this._createButton = this.addFooterButton(localize('firewall.ok', "OK"), () => this.createFirewallRule());
|
this._createButton = this.addFooterButton(LocalizedStrings.OK, () => this.createFirewallRule());
|
||||||
this._closeButton = this.addFooterButton(localize('firewall.cancel', "Cancel"), () => this.cancel(), 'right', true);
|
this._closeButton = this.addFooterButton(LocalizedStrings.Cancel, () => this.cancel(), 'right', true);
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement) {
|
protected renderBody(container: HTMLElement) {
|
||||||
const body = DOM.append(container, DOM.$('.firewall-rule-dialog'));
|
const dialogBody = DOM.append(container, DOM.$('.firewall-rule-dialog'));
|
||||||
const descriptionSection = DOM.append(body, DOM.$('.firewall-rule-description-section.new-section'));
|
|
||||||
|
|
||||||
|
this.createFirewallRuleHeader(dialogBody);
|
||||||
|
this.createAccountPicker(dialogBody);
|
||||||
|
|
||||||
|
// Firewall rule section
|
||||||
|
const firewallRuleSection = DOM.append(dialogBody, DOM.$('.firewall-rule-section.new-section'));
|
||||||
|
this.createLabelElement(firewallRuleSection, LocalizedStrings.FirewallRuleLabel, true);
|
||||||
|
const radioContainer = DOM.append(firewallRuleSection, DOM.$('.radio-section'));
|
||||||
|
const form = DOM.append(radioContainer, DOM.$('form.firewall-rule'));
|
||||||
|
|
||||||
|
// Firewall rule name inputBox
|
||||||
|
const descriptionDiv = DOM.append(form, DOM.$('div.firewall-rulename dialog-input'));
|
||||||
|
const descInputContainer = DOM.append(descriptionDiv, DOM.$('.dialog-input-section'));
|
||||||
|
DOM.append(descInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.RuleName;
|
||||||
|
this._ruleNameInpuBox = new InputBox(DOM.append(descInputContainer, DOM.$('.dialog-input')), this._contextViewService, {
|
||||||
|
ariaLabel: LocalizedStrings.RuleName
|
||||||
|
});
|
||||||
|
|
||||||
|
// Single IP Address radio button
|
||||||
|
const IPAddressDiv = DOM.append(form, DOM.$('div.firewall-ip-address dialog-input'));
|
||||||
|
const subnetIPRangeDiv = DOM.append(form, DOM.$('div.firewall-subnet-ip-range dialog-input'));
|
||||||
|
const IPAddressContainer = DOM.append(IPAddressDiv, DOM.$('div.option-container'));
|
||||||
|
this._IPAddressInput = DOM.append(IPAddressContainer, DOM.$('input.option-input'));
|
||||||
|
this._IPAddressInput.setAttribute('type', 'radio');
|
||||||
|
this._IPAddressInput.setAttribute('name', 'firewallRuleChoice');
|
||||||
|
this._IPAddressInput.setAttribute('value', 'ipAddress');
|
||||||
|
const IPAddressDescription = DOM.append(IPAddressContainer, DOM.$('div.option-description'));
|
||||||
|
IPAddressDescription.innerText = LocalizedStrings.AddClientIPLabel;
|
||||||
|
this._IPAddressElement = DOM.append(IPAddressContainer, DOM.$('div.option-ip-address'));
|
||||||
|
|
||||||
|
// IP Range radio button
|
||||||
|
const subnetIpRangeContainer = DOM.append(subnetIPRangeDiv, DOM.$('div.option-container'));
|
||||||
|
this._subnetIPRangeInput = DOM.append(subnetIpRangeContainer, DOM.$('input.option-input'));
|
||||||
|
this._subnetIPRangeInput.setAttribute('type', 'radio');
|
||||||
|
this._subnetIPRangeInput.setAttribute('name', 'firewallRuleChoice');
|
||||||
|
this._subnetIPRangeInput.setAttribute('value', 'ipRange');
|
||||||
|
const subnetIPRangeDescription = DOM.append(subnetIpRangeContainer, DOM.$('div.option-description'));
|
||||||
|
subnetIPRangeDescription.innerText = LocalizedStrings.AddIPRangeLabel;
|
||||||
|
|
||||||
|
// IP Range input boxes
|
||||||
|
const subnetIPRangeSection = DOM.append(subnetIPRangeDiv, DOM.$('.subnet-ip-range-input'));
|
||||||
|
const inputContainer = DOM.append(subnetIPRangeSection, DOM.$('.dialog-input-section'));
|
||||||
|
|
||||||
|
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.FROM;
|
||||||
|
this._fromRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
|
||||||
|
ariaLabel: LocalizedStrings.FROM
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.TO;
|
||||||
|
this._toRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
|
||||||
|
ariaLabel: LocalizedStrings.TO
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register events
|
||||||
|
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
||||||
|
this.updateTheme(this._themeService.getColorTheme());
|
||||||
|
|
||||||
|
this._register(DOM.addDisposableListener(this._IPAddressInput, DOM.EventType.CLICK, () => {
|
||||||
|
this.onFirewallRuleOptionSelected(true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(DOM.addDisposableListener(this._subnetIPRangeInput, DOM.EventType.CLICK, () => {
|
||||||
|
this.onFirewallRuleOptionSelected(false);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create firewall rule header
|
||||||
|
private createFirewallRuleHeader(dialogBody: HTMLElement) {
|
||||||
|
const descriptionSection = DOM.append(dialogBody, DOM.$('.firewall-rule-description-section.new-section'));
|
||||||
DOM.append(descriptionSection, DOM.$('div.firewall-rule-icon'));
|
DOM.append(descriptionSection, DOM.$('div.firewall-rule-icon'));
|
||||||
|
|
||||||
const textDescriptionContainer = DOM.append(descriptionSection, DOM.$('div.firewall-rule-description'));
|
const textDescriptionContainer = DOM.append(descriptionSection, DOM.$('div.firewall-rule-description'));
|
||||||
const dialogDescription = localize('firewallRuleDialogDescription',
|
|
||||||
"Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access.");
|
|
||||||
this.createLabelElement(textDescriptionContainer, dialogDescription, false);
|
|
||||||
|
|
||||||
|
this.createLabelElement(textDescriptionContainer, LocalizedStrings.FirewallRuleDescription, false);
|
||||||
this._helpLink = DOM.append(textDescriptionContainer, DOM.$('a.help-link'));
|
this._helpLink = DOM.append(textDescriptionContainer, DOM.$('a.help-link'));
|
||||||
|
|
||||||
this._helpLink.setAttribute('href', firewallHelpUri);
|
this._helpLink.setAttribute('href', firewallHelpUri);
|
||||||
this._helpLink.innerHTML += localize('firewallRuleHelpDescription', "Learn more about firewall settings");
|
this._helpLink.innerHTML += LocalizedStrings.FirewallRuleHelpLink;
|
||||||
this._helpLink.onclick = () => {
|
this._helpLink.onclick = () => {
|
||||||
this.openerService.open(URI.parse(firewallHelpUri));
|
this.openerService.open(URI.parse(firewallHelpUri));
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Create account picker with event handling
|
// Create account picker with event handling
|
||||||
|
private createAccountPicker(dialogBody: HTMLElement) {
|
||||||
this._accountPickerService.addAccountCompleteEvent(() => this.spinner = false);
|
this._accountPickerService.addAccountCompleteEvent(() => this.spinner = false);
|
||||||
this._accountPickerService.addAccountErrorEvent((msg) => {
|
this._accountPickerService.addAccountErrorEvent((msg) => {
|
||||||
this.spinner = false;
|
this.spinner = false;
|
||||||
@@ -139,68 +217,22 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
this._accountPickerService.onAccountSelectionChangeEvent((account) => this.onAccountSelectionChange(account));
|
this._accountPickerService.onAccountSelectionChangeEvent((account) => this.onAccountSelectionChange(account));
|
||||||
this._accountPickerService.onTenantSelectionChangeEvent((tenantId) => !!tenantId && this.onTenantSelectionChange(tenantId));
|
this._accountPickerService.onTenantSelectionChangeEvent((tenantId) => !!tenantId && this.onTenantSelectionChange(tenantId));
|
||||||
|
|
||||||
const azureAccountSection = DOM.append(body, DOM.$('.azure-account-section.new-section'));
|
const azureAccountSection = DOM.append(dialogBody, DOM.$('.azure-account-section.new-section'));
|
||||||
this._accountPickerService.renderAccountPicker(azureAccountSection);
|
this._accountPickerService.renderAccountPicker(azureAccountSection);
|
||||||
|
|
||||||
const firewallRuleSection = DOM.append(body, DOM.$('.firewall-rule-section.new-section'));
|
|
||||||
const firewallRuleLabel = localize('filewallRule', "Firewall rule");
|
|
||||||
this.createLabelElement(firewallRuleSection, firewallRuleLabel, true);
|
|
||||||
const radioContainer = DOM.append(firewallRuleSection, DOM.$('.radio-section'));
|
|
||||||
const form = DOM.append(radioContainer, DOM.$('form.firewall-rule'));
|
|
||||||
const IPAddressDiv = DOM.append(form, DOM.$('div.firewall-ip-address dialog-input'));
|
|
||||||
const subnetIPRangeDiv = DOM.append(form, DOM.$('div.firewall-subnet-ip-range dialog-input'));
|
|
||||||
|
|
||||||
const IPAddressContainer = DOM.append(IPAddressDiv, DOM.$('div.option-container'));
|
|
||||||
this._IPAddressInput = DOM.append(IPAddressContainer, DOM.$('input.option-input'));
|
|
||||||
this._IPAddressInput.setAttribute('type', 'radio');
|
|
||||||
this._IPAddressInput.setAttribute('name', 'firewallRuleChoice');
|
|
||||||
this._IPAddressInput.setAttribute('value', 'ipAddress');
|
|
||||||
const IPAddressDescription = DOM.append(IPAddressContainer, DOM.$('div.option-description'));
|
|
||||||
IPAddressDescription.innerText = localize('addIPAddressLabel', "Add my client IP ");
|
|
||||||
this._IPAddressElement = DOM.append(IPAddressContainer, DOM.$('div.option-ip-address'));
|
|
||||||
|
|
||||||
const subnetIpRangeContainer = DOM.append(subnetIPRangeDiv, DOM.$('div.option-container'));
|
|
||||||
this._subnetIPRangeInput = DOM.append(subnetIpRangeContainer, DOM.$('input.option-input'));
|
|
||||||
this._subnetIPRangeInput.setAttribute('type', 'radio');
|
|
||||||
this._subnetIPRangeInput.setAttribute('name', 'firewallRuleChoice');
|
|
||||||
this._subnetIPRangeInput.setAttribute('value', 'ipRange');
|
|
||||||
const subnetIPRangeDescription = DOM.append(subnetIpRangeContainer, DOM.$('div.option-description'));
|
|
||||||
subnetIPRangeDescription.innerText = localize('addIpRangeLabel', "Add my subnet IP range");
|
|
||||||
const subnetIPRangeSection = DOM.append(subnetIPRangeDiv, DOM.$('.subnet-ip-range-input'));
|
|
||||||
|
|
||||||
const inputContainer = DOM.append(subnetIPRangeSection, DOM.$('.dialog-input-section'));
|
|
||||||
|
|
||||||
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.FROM;
|
|
||||||
|
|
||||||
this._fromRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
|
|
||||||
ariaLabel: LocalizedStrings.FROM
|
|
||||||
});
|
|
||||||
|
|
||||||
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.TO;
|
|
||||||
|
|
||||||
this._toRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, {
|
|
||||||
ariaLabel: LocalizedStrings.TO
|
|
||||||
});
|
|
||||||
|
|
||||||
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
|
||||||
this.updateTheme(this._themeService.getColorTheme());
|
|
||||||
|
|
||||||
this._register(DOM.addDisposableListener(this._IPAddressElement, DOM.EventType.CLICK, () => {
|
|
||||||
this.onFirewallRuleOptionSelected(true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(DOM.addDisposableListener(this._subnetIPRangeInput, DOM.EventType.CLICK, () => {
|
|
||||||
this.onFirewallRuleOptionSelected(false);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFirewallRuleOptionSelected(isIPAddress: boolean) {
|
private onFirewallRuleOptionSelected(isIPAddress: boolean) {
|
||||||
this.viewModel.isIPAddressSelected = isIPAddress;
|
this.viewModel.isIPAddressSelected = isIPAddress;
|
||||||
if (this._fromRangeinputBox) {
|
if (isIPAddress) {
|
||||||
isIPAddress ? this._fromRangeinputBox.disable() : this._fromRangeinputBox.enable();
|
this._fromRangeinputBox!.disable();
|
||||||
}
|
this._fromRangeinputBox!.value = '';
|
||||||
if (this._toRangeinputBox) {
|
this._toRangeinputBox!.disable();
|
||||||
isIPAddress ? this._toRangeinputBox.disable() : this._toRangeinputBox.enable();
|
this._toRangeinputBox!.value = '';
|
||||||
|
} else {
|
||||||
|
this._fromRangeinputBox!.enable();
|
||||||
|
this._fromRangeinputBox!.value = this.viewModel!.defaultFromSubnetIPRange ?? '';
|
||||||
|
this._toRangeinputBox!.enable();
|
||||||
|
this._toRangeinputBox!.value = this.viewModel!.defaultToSubnetIPRange ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +243,7 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
private createLabelElement(container: HTMLElement, content: string, isHeader?: boolean) {
|
private createLabelElement(container: HTMLElement, content: string, isHeader?: boolean) {
|
||||||
let className = 'dialog-label';
|
let className = 'dialog-label';
|
||||||
if (isHeader) {
|
if (isHeader) {
|
||||||
className += ' header';
|
className += '.header';
|
||||||
}
|
}
|
||||||
const element = DOM.append(container, DOM.$(`.${className}`));
|
const element = DOM.append(container, DOM.$(`.${className}`));
|
||||||
element.innerText = content;
|
element.innerText = content;
|
||||||
@@ -231,9 +263,15 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
// Theme styler
|
// Theme styler
|
||||||
this._register(attachButtonStyler(this._createButton!, this._themeService));
|
this._register(attachButtonStyler(this._createButton!, this._themeService));
|
||||||
this._register(attachButtonStyler(this._closeButton!, this._themeService));
|
this._register(attachButtonStyler(this._closeButton!, this._themeService));
|
||||||
|
this._register(attachInputBoxStyler(this._ruleNameInpuBox!, this._themeService));
|
||||||
this._register(attachInputBoxStyler(this._fromRangeinputBox!, this._themeService));
|
this._register(attachInputBoxStyler(this._fromRangeinputBox!, this._themeService));
|
||||||
this._register(attachInputBoxStyler(this._toRangeinputBox!, this._themeService));
|
this._register(attachInputBoxStyler(this._toRangeinputBox!, this._themeService));
|
||||||
|
|
||||||
|
// handler for firewall rule name change events
|
||||||
|
this._register(this._ruleNameInpuBox!.onDidChange(ruleName => {
|
||||||
|
this.firewallRuleNameChanged(ruleName);
|
||||||
|
}));
|
||||||
|
|
||||||
// handler for from subnet ip range change events
|
// handler for from subnet ip range change events
|
||||||
this._register(this._fromRangeinputBox!.onDidChange(IPAddress => {
|
this._register(this._fromRangeinputBox!.onDidChange(IPAddress => {
|
||||||
this.fromRangeInputChanged(IPAddress);
|
this.fromRangeInputChanged(IPAddress);
|
||||||
@@ -245,6 +283,10 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private firewallRuleNameChanged(ruleName: string) {
|
||||||
|
this.viewModel.firewallRuleName = ruleName;
|
||||||
|
}
|
||||||
|
|
||||||
private fromRangeInputChanged(IPAddress: string) {
|
private fromRangeInputChanged(IPAddress: string) {
|
||||||
this.viewModel.fromSubnetIPRange = IPAddress;
|
this.viewModel.fromSubnetIPRange = IPAddress;
|
||||||
}
|
}
|
||||||
@@ -301,6 +343,7 @@ export class FirewallRuleDialog extends Modal {
|
|||||||
public open() {
|
public open() {
|
||||||
this._IPAddressInput!.click();
|
this._IPAddressInput!.click();
|
||||||
this.onAccountSelectionChange(this._accountPickerService.selectedAccount);
|
this.onAccountSelectionChange(this._accountPickerService.selectedAccount);
|
||||||
|
this._ruleNameInpuBox!.value = this.viewModel!.defaultFirewallRuleName ?? '';
|
||||||
this._fromRangeinputBox!.setPlaceHolder(this.viewModel!.defaultFromSubnetIPRange ?? '');
|
this._fromRangeinputBox!.setPlaceHolder(this.viewModel!.defaultFromSubnetIPRange ?? '');
|
||||||
this._toRangeinputBox!.setPlaceHolder(this.viewModel!.defaultToSubnetIPRange ?? '');
|
this._toRangeinputBox!.setPlaceHolder(this.viewModel!.defaultToSubnetIPRange ?? '');
|
||||||
this._IPAddressElement!.innerText = `(${this.viewModel.defaultIPAddress ?? ''})`;
|
this._IPAddressElement!.innerText = `(${this.viewModel.defaultIPAddress ?? ''})`;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export class FirewallRuleDialogController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const firewallRuleInfo: azdata.FirewallRuleInfo = {
|
const firewallRuleInfo: azdata.FirewallRuleInfo = {
|
||||||
|
firewallRuleName: this._firewallRuleDialog!.viewModel.firewallRuleName,
|
||||||
startIpAddress: this._firewallRuleDialog!.viewModel.isIPAddressSelected ? this._firewallRuleDialog!.viewModel.defaultIPAddress : this._firewallRuleDialog!.viewModel.fromSubnetIPRange,
|
startIpAddress: this._firewallRuleDialog!.viewModel.isIPAddressSelected ? this._firewallRuleDialog!.viewModel.defaultIPAddress : this._firewallRuleDialog!.viewModel.fromSubnetIPRange,
|
||||||
endIpAddress: this._firewallRuleDialog!.viewModel.isIPAddressSelected ? this._firewallRuleDialog!.viewModel.defaultIPAddress : this._firewallRuleDialog!.viewModel.toSubnetIPRange,
|
endIpAddress: this._firewallRuleDialog!.viewModel.isIPAddressSelected ? this._firewallRuleDialog!.viewModel.defaultIPAddress : this._firewallRuleDialog!.viewModel.toSubnetIPRange,
|
||||||
serverName: this._connection!.serverName,
|
serverName: this._connection!.serverName,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal .firewall-rule-dialog .dialog-label.header {
|
.modal .firewall-rule-dialog .dialog-label.header {
|
||||||
font-size: 15px;
|
font-size: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal .firewall-rule-dialog .new-section {
|
.modal .firewall-rule-dialog .new-section {
|
||||||
@@ -29,8 +30,14 @@
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal .firewall-rule-dialog .firewall-rulename .dialog-input-section{
|
||||||
|
display: block;
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal .firewall-rule-dialog .dialog-input-section .dialog-label {
|
.modal .firewall-rule-dialog .dialog-input-section .dialog-label {
|
||||||
flex: 0 0 15px;
|
flex: 0 0;
|
||||||
|
padding-top: 3px;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +49,10 @@
|
|||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal .firewall-rule-dialog .firewall-rulename .dialog-input-section .dialog-input {
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Firewall rule description section */
|
/* Firewall rule description section */
|
||||||
.modal .firewall-rule-dialog a:link {
|
.modal .firewall-rule-dialog a:link {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|||||||
Reference in New Issue
Block a user