Add support for firewall rule name (#21430)

This commit is contained in:
Cheena Malhotra
2022-12-16 18:29:44 -08:00
committed by GitHub
parent e2b1e6cbf3
commit ca22686061
11 changed files with 303 additions and 85 deletions

View File

@@ -28,6 +28,7 @@ import { Tenant, TenantListDelegate, TenantPickerListRenderer } from 'sql/workbe
export class AccountPicker extends Disposable {
public static ACCOUNTPICKERLIST_HEIGHT = 47;
public static ACCOUNTTENANTLIST_HEIGHT = 32;
public viewModel: AccountPickerViewModel;
private _accountList?: List<azdata.Account>;
private _rootContainer?: HTMLElement;
@@ -97,7 +98,7 @@ export class AccountPicker extends Disposable {
public createAccountPickerComponent() {
// Create an account list
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 tenantRenderer = new TenantPickerListRenderer();
@@ -203,7 +204,7 @@ export class AccountPicker extends Disposable {
private createLabelElement(content: string, isHeader?: boolean) {
let className = 'dialog-label';
if (isHeader) {
className += ' header';
className += '.header';
}
const element = DOM.$(`.${className}`);
element.innerText = content;

View File

@@ -1,4 +1,4 @@
/*---------------------------------------------------------------------------------------------
/*--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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.tenant-picker-list .label {
flex: 1 1 auto;
margin-left: 15px;
margin-left: 12px;
overflow: hidden;
}
.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.tenant-picker-list .label .display-name {
font-size: 13px;
font-size: 12px;
line-height: 1.5;
}
.list-row.account-picker-list .label .content,

View File

@@ -57,10 +57,10 @@
}
.account-list-container .list-row .codicon {
flex: 0 0 35px;
height: 35px;
width: 35px;
background-size: 35px;
flex: 0 0 32px;
height: 32px;
width: 32px;
background-size: 32px;
}
.account-list-container .list-row .codicon .badge {

View File

@@ -38,13 +38,24 @@ const firewallHelpUri = 'https://aka.ms/sqlopsfirewallhelp';
const LocalizedStrings = {
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 {
public viewModel: FirewallRuleViewModel;
private _createButton?: Button;
private _closeButton?: Button;
private _ruleNameInpuBox?: InputBox;
private _fromRangeinputBox?: InputBox;
private _toRangeinputBox?: InputBox;
@@ -77,7 +88,7 @@ export class FirewallRuleDialog extends Modal {
@IOpenerService private readonly openerService: IOpenerService
) {
super(
localize('createNewFirewallRule', "Create new firewall rule"),
LocalizedStrings.CreateNewFirewallRule,
TelemetryKeys.ModalDialogName.FireWallRule,
telemetryService,
layoutService,
@@ -106,30 +117,97 @@ export class FirewallRuleDialog extends Modal {
attachModalDialogStyler(this, this._themeService);
this.backButton!.onDidClick(() => this.cancel());
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._closeButton = this.addFooterButton(localize('firewall.cancel', "Cancel"), () => this.cancel(), 'right', true);
this._createButton = this.addFooterButton(LocalizedStrings.OK, () => this.createFirewallRule());
this._closeButton = this.addFooterButton(LocalizedStrings.Cancel, () => this.cancel(), 'right', true);
this.registerListeners();
}
protected renderBody(container: HTMLElement) {
const body = DOM.append(container, DOM.$('.firewall-rule-dialog'));
const descriptionSection = DOM.append(body, DOM.$('.firewall-rule-description-section.new-section'));
const dialogBody = DOM.append(container, DOM.$('.firewall-rule-dialog'));
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'));
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.setAttribute('href', firewallHelpUri);
this._helpLink.innerHTML += localize('firewallRuleHelpDescription', "Learn more about firewall settings");
this._helpLink.innerHTML += LocalizedStrings.FirewallRuleHelpLink;
this._helpLink.onclick = () => {
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.addAccountErrorEvent((msg) => {
this.spinner = false;
@@ -139,68 +217,22 @@ export class FirewallRuleDialog extends Modal {
this._accountPickerService.onAccountSelectionChangeEvent((account) => this.onAccountSelectionChange(account));
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);
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) {
this.viewModel.isIPAddressSelected = isIPAddress;
if (this._fromRangeinputBox) {
isIPAddress ? this._fromRangeinputBox.disable() : this._fromRangeinputBox.enable();
}
if (this._toRangeinputBox) {
isIPAddress ? this._toRangeinputBox.disable() : this._toRangeinputBox.enable();
if (isIPAddress) {
this._fromRangeinputBox!.disable();
this._fromRangeinputBox!.value = '';
this._toRangeinputBox!.disable();
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) {
let className = 'dialog-label';
if (isHeader) {
className += ' header';
className += '.header';
}
const element = DOM.append(container, DOM.$(`.${className}`));
element.innerText = content;
@@ -231,9 +263,15 @@ export class FirewallRuleDialog extends Modal {
// Theme styler
this._register(attachButtonStyler(this._createButton!, 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._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
this._register(this._fromRangeinputBox!.onDidChange(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) {
this.viewModel.fromSubnetIPRange = IPAddress;
}
@@ -301,6 +343,7 @@ export class FirewallRuleDialog extends Modal {
public open() {
this._IPAddressInput!.click();
this.onAccountSelectionChange(this._accountPickerService.selectedAccount);
this._ruleNameInpuBox!.value = this.viewModel!.defaultFirewallRuleName ?? '';
this._fromRangeinputBox!.setPlaceHolder(this.viewModel!.defaultFromSubnetIPRange ?? '');
this._toRangeinputBox!.setPlaceHolder(this.viewModel!.defaultToSubnetIPRange ?? '');
this._IPAddressElement!.innerText = `(${this.viewModel.defaultIPAddress ?? ''})`;

View File

@@ -68,6 +68,7 @@ export class FirewallRuleDialogController {
};
const firewallRuleInfo: azdata.FirewallRuleInfo = {
firewallRuleName: this._firewallRuleDialog!.viewModel.firewallRuleName,
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,
serverName: this._connection!.serverName,

View File

@@ -8,7 +8,8 @@
}
.modal .firewall-rule-dialog .dialog-label.header {
font-size: 15px;
font-size: 12px;
padding-top: 10px;
}
.modal .firewall-rule-dialog .new-section {
@@ -29,8 +30,14 @@
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 {
flex: 0 0 15px;
flex: 0 0;
padding-top: 3px;
align-self: center;
}
@@ -42,6 +49,10 @@
padding-bottom: 0px;
}
.modal .firewall-rule-dialog .firewall-rulename .dialog-input-section .dialog-input {
padding-left: 0px;
}
/* Firewall rule description section */
.modal .firewall-rule-dialog a:link {
text-decoration: underline;