summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-11-03 17:12:25 +0100
committerOskar Nyberg <oskar@mullvad.net>2020-11-16 17:19:49 +0100
commitd698175c9b521c1af805a22bec08c518efd21759 (patch)
tree479dbc696fa7fe223061575ccc445a8f5c8692f5 /gui/src
parentfbf3cd5485cc7f0669b1bba9480bf4bf88e11208 (diff)
downloadmullvadvpn-d698175c9b521c1af805a22bec08c518efd21759.tar.xz
mullvadvpn-d698175c9b521c1af805a22bec08c518efd21759.zip
Add custom DNS setting
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx160
-rw-r--r--gui/src/renderer/components/AdvancedSettingsStyles.tsx28
-rw-r--r--gui/src/renderer/containers/AdvancedSettingsPage.tsx13
3 files changed, 195 insertions, 6 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx
index 239b5206cd..abf82b79b3 100644
--- a/gui/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/src/renderer/components/AdvancedSettings.tsx
@@ -1,10 +1,17 @@
import * as React from 'react';
import { sprintf } from 'sprintf-js';
-import { BridgeState, RelayProtocol, TunnelProtocol } from '../../shared/daemon-rpc-types';
+import { colors } from '../../config.json';
+import {
+ BridgeState,
+ IDnsOptions,
+ RelayProtocol,
+ TunnelProtocol,
+} from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
+import consumePromise from '../../shared/promise';
import { WgKeyState } from '../redux/settings/reducers';
import {
- StyledBottomCellGroup,
+ StyledButtonCellGroup,
StyledContainer,
StyledInputFrame,
StyledNavigationScrollbars,
@@ -13,10 +20,15 @@ import {
StyledSelectorContainer,
StyledTunnelProtocolSelector,
StyledTunnelProtocolContainer,
+ StyledCustomDnsSwitchContainer,
+ StyledCustomDnsFotter,
+ StyledAddCustomDnsLabel,
+ StyledAddCustomDnsButton,
} from './AdvancedSettingsStyles';
import * as AppButton from './AppButton';
import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
import * as Cell from './cell';
+import CellList, { ICellListItem } from './cell/List';
import { Layout } from './Layout';
import { ModalAlert, ModalAlertType, ModalContainer, ModalMessage } from './Modal';
import {
@@ -28,6 +40,7 @@ import {
} from './NavigationBar';
import Selector, { ISelectorItem } from './cell/Selector';
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
+import Accordion from './Accordion';
const MIN_MSSFIX_VALUE = 1000;
const MAX_MSSFIX_VALUE = 1450;
@@ -59,6 +72,7 @@ interface IProps {
mssfix?: number;
wireguardMtu?: number;
bridgeState: BridgeState;
+ dns: IDnsOptions;
setBridgeState: (value: BridgeState) => void;
setEnableIpv6: (value: boolean) => void;
setBlockWhenDisconnected: (value: boolean) => void;
@@ -67,6 +81,7 @@ interface IProps {
setWireguardMtu: (value: number | undefined) => void;
setOpenVpnRelayProtocolAndPort: (protocol?: RelayProtocol, port?: number) => void;
setWireguardRelayPort: (port?: number) => void;
+ setDnsOptions: (dns: IDnsOptions) => Promise<void>;
onViewWireguardKeys: () => void;
onViewLinuxSplitTunneling: () => void;
onClose: () => void;
@@ -74,13 +89,21 @@ interface IProps {
interface IState {
showConfirmBlockWhenDisconnectedAlert: boolean;
+ showAddCustomDns: boolean;
+ invalidDnsIp: boolean;
}
export default class AdvancedSettings extends React.Component<IProps, IState> {
public state = {
showConfirmBlockWhenDisconnectedAlert: false,
+ showAddCustomDns: false,
+ invalidDnsIp: false,
};
+ private customDnsSwitchRef = React.createRef<HTMLDivElement>();
+ private customDnsAddButtonRef = React.createRef<HTMLButtonElement>();
+ private customDnsInputContainerRef = React.createRef<HTMLDivElement>();
+
private portItems: { [key in RelayProtocol]: Array<ISelectorItem<OptionalPort>> };
private protocolItems: Array<ISelectorItem<OptionalRelayProtocol>>;
private bridgeStateItems: Array<ISelectorItem<BridgeState>>;
@@ -395,7 +418,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
</Cell.Footer>
</AriaInputGroup>
- <StyledBottomCellGroup>
+ <StyledButtonCellGroup>
<Cell.CellButton onClick={this.props.onViewWireguardKeys}>
<Cell.Label>
{messages.pgettext('advanced-settings-view', 'WireGuard key')}
@@ -411,7 +434,61 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
<Cell.Icon height={12} width={7} source="icon-chevron" />
</Cell.CellButton>
)}
- </StyledBottomCellGroup>
+ </StyledButtonCellGroup>
+
+ <StyledCustomDnsSwitchContainer>
+ <Cell.InputLabel>
+ {messages.pgettext('advanced-settings-view', 'Use custom DNS server')}
+ </Cell.InputLabel>
+ <Cell.Switch
+ ref={this.customDnsSwitchRef}
+ isOn={this.props.dns.custom}
+ onChange={this.setCustomDnsEnabled}
+ />
+ </StyledCustomDnsSwitchContainer>
+ <Accordion expanded={this.props.dns.custom}>
+ <CellList items={this.customDnsItems()} onRemove={this.removeDnsAddress} />
+
+ {this.state.showAddCustomDns && (
+ <div ref={this.customDnsInputContainerRef}>
+ <Cell.RowInput
+ onSubmit={this.addDnsAddress}
+ onChange={this.addDnsInputChange}
+ invalid={this.state.invalidDnsIp}
+ paddingLeft={32}
+ onBlur={this.customDnsInputBlur}
+ autofocus
+ />
+ </div>
+ )}
+
+ <StyledAddCustomDnsButton
+ ref={this.customDnsAddButtonRef}
+ onClick={this.showAddCustomDnsRow}
+ disabled={this.state.showAddCustomDns}
+ tabIndex={-1}>
+ <StyledAddCustomDnsLabel tabIndex={-1}>
+ {messages.pgettext('advanced-settings-view', 'Add a server')}
+ </StyledAddCustomDnsLabel>
+ <Cell.UntintedIcon
+ source="icon-add"
+ width={22}
+ height={22}
+ tintColor={colors.white60}
+ tintHoverColor={colors.white80}
+ tabIndex={-1}
+ />
+ </StyledAddCustomDnsButton>
+ </Accordion>
+
+ <StyledCustomDnsFotter>
+ <Cell.FooterText>
+ {messages.pgettext(
+ 'advanced-settings-view',
+ 'Enable to add at least one DNS server.',
+ )}
+ </Cell.FooterText>
+ </StyledCustomDnsFotter>
</StyledNavigationScrollbars>
</NavigationContainer>
</StyledContainer>
@@ -423,6 +500,81 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
);
}
+ private setCustomDnsEnabled = async (enabled: boolean) => {
+ await this.props.setDnsOptions({
+ custom: enabled,
+ addresses: this.props.dns.addresses,
+ });
+
+ if (enabled && this.props.dns.addresses.length === 0) {
+ this.showAddCustomDnsRow();
+ }
+
+ if (!enabled) {
+ this.setState({ showAddCustomDns: false });
+ }
+ };
+
+ private customDnsItems(): ICellListItem<string>[] {
+ return this.props.dns.addresses.map((address) => ({
+ label: address,
+ value: address,
+ }));
+ }
+
+ private showAddCustomDnsRow = () => {
+ this.setState({ showAddCustomDns: true });
+ };
+
+ // The input field should be hidden when it loses focus unless something on the same row or the
+ // add-button is the new focused element.
+ private customDnsInputBlur = (event?: React.FocusEvent<HTMLTextAreaElement>) => {
+ const relatedTarget = event?.relatedTarget as Node | undefined;
+ if (
+ relatedTarget &&
+ (this.customDnsSwitchRef.current?.contains(relatedTarget) ||
+ this.customDnsAddButtonRef.current?.contains(relatedTarget) ||
+ this.customDnsInputContainerRef.current?.contains(relatedTarget))
+ ) {
+ event?.target.focus();
+ } else {
+ this.hideAddCustomDnsRow(false);
+ }
+ };
+
+ private hideAddCustomDnsRow(justAdded: boolean) {
+ this.setState({ showAddCustomDns: false });
+ if (!justAdded && this.props.dns.addresses.length === 0) {
+ consumePromise(this.setCustomDnsEnabled(false));
+ }
+ }
+
+ private addDnsInputChange = (_value: string) => {
+ this.setState({ invalidDnsIp: false });
+ };
+
+ private addDnsAddress = async (address: string) => {
+ try {
+ await this.props.setDnsOptions({
+ custom: this.props.dns.custom,
+ addresses: [...this.props.dns.addresses, address],
+ });
+ this.hideAddCustomDnsRow(true);
+ } catch (_e) {
+ this.setState({ invalidDnsIp: true });
+ }
+ };
+
+ private removeDnsAddress = (address: string) => {
+ const addresses = this.props.dns.addresses.filter((item) => item !== address);
+ consumePromise(
+ this.props.setDnsOptions({
+ custom: addresses.length > 0 && this.props.dns.custom,
+ addresses,
+ }),
+ );
+ };
+
private tunnelProtocolItems = (
hasWireguardKey: boolean,
): Array<ISelectorItem<OptionalTunnelProtocol>> => {
diff --git a/gui/src/renderer/components/AdvancedSettingsStyles.tsx b/gui/src/renderer/components/AdvancedSettingsStyles.tsx
index 0bccc7c158..d26972ed1c 100644
--- a/gui/src/renderer/components/AdvancedSettingsStyles.tsx
+++ b/gui/src/renderer/components/AdvancedSettingsStyles.tsx
@@ -29,7 +29,7 @@ export const StyledNavigationScrollbars = styled(NavigationScrollbars)({
flex: 1,
});
-export const StyledBottomCellGroup = styled.div({
+export const StyledButtonCellGroup = styled.div({
display: 'flex',
flexDirection: 'column',
flex: 1,
@@ -44,3 +44,29 @@ export const StyledNoWireguardKeyError = styled(Cell.FooterText)({
fontWeight: 800,
color: colors.red,
});
+
+export const StyledCustomDnsSwitchContainer = styled(Cell.Container)({
+ marginBottom: '1px',
+});
+
+export const StyledCustomDnsFotter = styled(Cell.Footer)({
+ marginBottom: '2px',
+});
+
+export const StyledAddCustomDnsButton = styled(Cell.CellButton)({
+ backgroundColor: colors.blue40,
+});
+
+export const StyledAddCustomDnsLabel = styled(Cell.Label)(
+ {},
+ (props: { paddingLeft?: number }) => ({
+ fontFamily: 'Open Sans',
+ fontWeight: 'normal',
+ fontSize: '16px',
+ paddingLeft: (props.paddingLeft ?? 32) + 'px',
+ whiteSpace: 'pre-wrap',
+ overflowWrap: 'break-word',
+ width: '171px',
+ marginRight: '25px',
+ }),
+);
diff --git a/gui/src/renderer/containers/AdvancedSettingsPage.tsx b/gui/src/renderer/containers/AdvancedSettingsPage.tsx
index 6dec67cf4d..68d6899acb 100644
--- a/gui/src/renderer/containers/AdvancedSettingsPage.tsx
+++ b/gui/src/renderer/containers/AdvancedSettingsPage.tsx
@@ -1,7 +1,12 @@
import log from 'electron-log';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router';
-import { BridgeState, RelayProtocol, TunnelProtocol } from '../../shared/daemon-rpc-types';
+import {
+ BridgeState,
+ IDnsOptions,
+ RelayProtocol,
+ TunnelProtocol,
+} from '../../shared/daemon-rpc-types';
import RelaySettingsBuilder from '../../shared/relay-settings-builder';
import AdvancedSettings from '../components/AdvancedSettings';
@@ -19,6 +24,7 @@ const mapStateToProps = (state: IReduxState) => {
mssfix: state.settings.openVpn.mssfix,
wireguardMtu: state.settings.wireguard.mtu,
bridgeState: state.settings.bridgeState,
+ dns: state.settings.dns,
...protocolAndPort,
};
};
@@ -152,6 +158,11 @@ const mapDispatchToProps = (_dispatch: ReduxDispatch, props: RouteComponentProps
log.error('Failed to update mtu value', e.message);
}
},
+
+ setDnsOptions: (dns: IDnsOptions) => {
+ return props.app.setDnsOptions(dns);
+ },
+
onViewWireguardKeys: () => props.history.push('/settings/advanced/wireguard-keys'),
onViewLinuxSplitTunneling: () => props.history.push('/settings/advanced/linux-split-tunneling'),
};