summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--gui/locales/messages.pot28
-rw-r--r--gui/src/main/daemon-rpc.ts21
-rw-r--r--gui/src/main/index.ts10
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx81
-rw-r--r--gui/src/renderer/components/Preferences.tsx90
-rw-r--r--gui/src/renderer/components/PreferencesStyles.tsx6
-rw-r--r--gui/src/renderer/containers/PreferencesPage.tsx5
-rw-r--r--gui/src/renderer/markdown-formatter.tsx19
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts16
-rw-r--r--gui/src/shared/daemon-rpc-types.ts10
11 files changed, 252 insertions, 35 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8140dcd860..5bdcbdffe4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ Line wrap the file at 100 chars. Th
- Make app native on Apple Silicon.
- Add DNS options for ad and tracker blocking to the CLI.
- Support WireGuard multihop using an entry endpoint constraint.
+- Add DNS options for ad and tracker blocking to the desktop app.
### Changed
- Upgrade OpenVPN from 2.5.0 to 2.5.1.
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index 4d8fa54510..ab24bf500c 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -714,6 +714,34 @@ msgid "Beta program"
msgstr ""
msgctxt "preferences-view"
+msgid "Block ads"
+msgstr ""
+
+msgctxt "preferences-view"
+msgid "Block trackers"
+msgstr ""
+
+#. This is displayed when either or both of the block ads/trackers settings are
+#. turned on which makes the custom DNS setting disabled. The text enclosed in "**"
+#. will appear bold.
+#. Available placeholders:
+#. %(blockAdsFeatureName)s - The name displayed next to the "Block ads" toggle.
+#. %(blockTrackersFeatureName)s - The name displayed next to the "Block trackers" toggle.
+#. %(preferencesPageName)s - The page title showed on top in the preferences page.
+msgctxt "preferences-view"
+msgid "Disable **%(blockAdsFeatureName)s** and **%(blockTrackersFeatureName)s** (under %(preferencesPageName)s) to activate this setting."
+msgstr ""
+
+#. This is displayed when the custom DNS setting is turned on which makes the block
+#. ads/trackers settings disabled. The text enclosed in "**" will appear bold.
+#. Advanced settings refer to the name of the page with the title "Advanced".
+#. Available placeholders:
+#. %(customDnsFeatureName)s - The name displayed next to the custom DNS toggle.
+msgctxt "preferences-view"
+msgid "Disable **%(customDnsFeatureName)s** (under Advanced settings) to activate these settings."
+msgstr ""
+
+msgctxt "preferences-view"
msgid "Enable or disable system notifications. The critical notifications will always be displayed."
msgstr ""
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 5e28be9380..04332a4447 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -449,15 +449,15 @@ export class DaemonRpc {
const dnsOptions = new grpcTypes.DnsOptions();
const defaultOptions = new grpcTypes.DefaultDnsOptions();
- defaultOptions.setBlockAds(false);
- defaultOptions.setBlockTrackers(false);
+ defaultOptions.setBlockAds(dns.defaultOptions.blockAds);
+ defaultOptions.setBlockTrackers(dns.defaultOptions.blockTrackers);
dnsOptions.setDefaultOptions(defaultOptions);
const customOptions = new grpcTypes.CustomDnsOptions();
- customOptions.setAddressesList(dns.addresses);
+ customOptions.setAddressesList(dns.customOptions.addresses);
dnsOptions.setCustomOptions(customOptions);
- if (dns.custom) {
+ if (dns.state === 'custom') {
dnsOptions.setState(grpcTypes.DnsOptions.DnsState.CUSTOM);
} else {
dnsOptions.setState(grpcTypes.DnsOptions.DnsState.DEFAULT);
@@ -1042,8 +1042,17 @@ function convertFromTunnelOptions(tunnelOptions: grpcTypes.TunnelOptions.AsObjec
enableIpv6: tunnelOptions.generic!.enableIpv6,
},
dns: {
- custom: tunnelOptions.dnsOptions!.state! === grpcTypes.DnsOptions.DnsState.CUSTOM,
- addresses: tunnelOptions.dnsOptions?.customOptions?.addressesList ?? [],
+ state:
+ tunnelOptions.dnsOptions?.state === grpcTypes.DnsOptions.DnsState.CUSTOM
+ ? 'custom'
+ : 'default',
+ defaultOptions: {
+ blockAds: tunnelOptions.dnsOptions?.defaultOptions?.blockAds ?? false,
+ blockTrackers: tunnelOptions.dnsOptions?.defaultOptions?.blockTrackers ?? false,
+ },
+ customOptions: {
+ addresses: tunnelOptions.dnsOptions?.customOptions?.addressesList ?? [],
+ },
},
};
}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 6b8f3969ad..f6c3d58f3f 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -152,8 +152,14 @@ class ApplicationMain {
mtu: undefined,
},
dns: {
- custom: false,
- addresses: [],
+ state: 'default',
+ defaultOptions: {
+ blockAds: false,
+ blockTrackers: false,
+ },
+ customOptions: {
+ addresses: [],
+ },
},
},
};
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx
index a81981263a..ef59798a46 100644
--- a/gui/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/src/renderer/components/AdvancedSettings.tsx
@@ -42,6 +42,7 @@ import {
import Selector, { ISelectorItem } from './cell/Selector';
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
import Accordion from './Accordion';
+import { formatMarkdown } from '../markdown-formatter';
const MIN_MSSFIX_VALUE = 1000;
const MAX_MSSFIX_VALUE = 1450;
@@ -448,7 +449,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
)}
</StyledButtonCellGroup>
- <StyledCustomDnsSwitchContainer>
+ <StyledCustomDnsSwitchContainer disabled={!this.customDnsAvailable()}>
<AriaInputGroup>
<AriaLabel>
<Cell.InputLabel>
@@ -458,13 +459,17 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
<AriaInput>
<Cell.Switch
ref={this.customDnsSwitchRef}
- isOn={this.props.dns.custom || this.state.showAddCustomDns}
+ isOn={this.props.dns.state === 'custom' || this.state.showAddCustomDns}
onChange={this.setCustomDnsEnabled}
/>
</AriaInput>
</AriaInputGroup>
</StyledCustomDnsSwitchContainer>
- <Accordion expanded={this.props.dns.custom || this.state.showAddCustomDns}>
+ <Accordion
+ expanded={
+ this.customDnsAvailable() &&
+ (this.props.dns.state === 'custom' || this.state.showAddCustomDns)
+ }>
<CellList items={this.customDnsItems()} onRemove={this.removeDnsAddress} />
{this.state.showAddCustomDns && (
@@ -502,9 +507,13 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
<StyledCustomDnsFotter>
<Cell.FooterText>
- {messages.pgettext(
- 'advanced-settings-view',
- 'Enable to add at least one DNS server.',
+ {this.customDnsAvailable() ? (
+ messages.pgettext(
+ 'advanced-settings-view',
+ 'Enable to add at least one DNS server.',
+ )
+ ) : (
+ <CustomDnsDisabledMessage />
)}
</Cell.FooterText>
</StyledCustomDnsFotter>
@@ -520,15 +529,22 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
);
}
+ private customDnsAvailable(): boolean {
+ return (
+ this.props.dns.state === 'custom' ||
+ (!this.props.dns.defaultOptions.blockAds && !this.props.dns.defaultOptions.blockTrackers)
+ );
+ }
+
private setCustomDnsEnabled = async (enabled: boolean) => {
- if (this.props.dns.addresses.length > 0) {
+ if (this.props.dns.customOptions.addresses.length > 0) {
await this.props.setDnsOptions({
- custom: enabled,
- addresses: this.props.dns.addresses,
+ ...this.props.dns,
+ state: enabled ? 'custom' : 'default',
});
}
- if (enabled && this.props.dns.addresses.length === 0) {
+ if (enabled && this.props.dns.customOptions.addresses.length === 0) {
this.showAddCustomDnsRow();
}
@@ -538,7 +554,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
};
private customDnsItems(): ICellListItem<string>[] {
- return this.props.dns.addresses.map((address) => ({
+ return this.props.dns.customOptions.addresses.map((address) => ({
label: address,
value: address,
}));
@@ -589,8 +605,12 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
if (ipAddress.isLocal() || confirmed) {
await this.props.setDnsOptions({
- custom: this.props.dns.custom || this.state.showAddCustomDns,
- addresses: [...this.props.dns.addresses, address],
+ ...this.props.dns,
+ state:
+ this.props.dns.state === 'custom' || this.state.showAddCustomDns ? 'custom' : 'default',
+ customOptions: {
+ addresses: [...this.props.dns.customOptions.addresses, address],
+ },
});
this.hideAddCustomDnsRow();
} else {
@@ -602,11 +622,14 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
};
private removeDnsAddress = (address: string) => {
- const addresses = this.props.dns.addresses.filter((item) => item !== address);
+ const addresses = this.props.dns.customOptions.addresses.filter((item) => item !== address);
consumePromise(
this.props.setDnsOptions({
- custom: addresses.length > 0 && this.props.dns.custom,
- addresses,
+ ...this.props.dns,
+ state: addresses.length > 0 && this.props.dns.state === 'custom' ? 'custom' : 'default',
+ customOptions: {
+ addresses,
+ },
}),
);
};
@@ -758,3 +781,29 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
);
}
}
+
+function CustomDnsDisabledMessage() {
+ const blockAdsFeatureName = messages.pgettext('preferences-view', 'Block ads');
+ const blockTrackersFeatureName = messages.pgettext('preferences-view', 'Block trackers');
+ const preferencesPageName = messages.pgettext('preferences-nav', 'Preferences');
+
+ // TRANSLATORS: This is displayed when either or both of the block ads/trackers settings are
+ // TRANSLATORS: turned on which makes the custom DNS setting disabled. The text enclosed in "**"
+ // TRANSLATORS: will appear bold.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(blockAdsFeatureName)s - The name displayed next to the "Block ads" toggle.
+ // TRANSLATORS: %(blockTrackersFeatureName)s - The name displayed next to the "Block trackers" toggle.
+ // TRANSLATORS: %(preferencesPageName)s - The page title showed on top in the preferences page.
+ const customDnsDisabledMessage = messages.pgettext(
+ 'preferences-view',
+ 'Disable **%(blockAdsFeatureName)s** and **%(blockTrackersFeatureName)s** (under %(preferencesPageName)s) to activate this setting.',
+ );
+
+ return formatMarkdown(
+ sprintf(customDnsDisabledMessage, {
+ blockAdsFeatureName,
+ blockTrackersFeatureName,
+ preferencesPageName,
+ }),
+ );
+}
diff --git a/gui/src/renderer/components/Preferences.tsx b/gui/src/renderer/components/Preferences.tsx
index d47a7315c6..c91330aee0 100644
--- a/gui/src/renderer/components/Preferences.tsx
+++ b/gui/src/renderer/components/Preferences.tsx
@@ -1,5 +1,8 @@
import * as React from 'react';
+import { sprintf } from 'sprintf-js';
+import { IDnsOptions } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
+import { formatMarkdown } from '../markdown-formatter';
import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
import * as Cell from './cell';
import { Layout } from './Layout';
@@ -24,6 +27,7 @@ export interface IProps {
monochromaticIcon: boolean;
startMinimized: boolean;
unpinnedWindow: boolean;
+ dns: IDnsOptions;
setAutoStart: (autoStart: boolean) => void;
setEnableSystemNotifications: (flag: boolean) => void;
setAutoConnect: (autoConnect: boolean) => void;
@@ -32,6 +36,7 @@ export interface IProps {
setStartMinimized: (startMinimized: boolean) => void;
setMonochromaticIcon: (monochromaticIcon: boolean) => void;
setUnpinnedWindow: (unpinnedWindow: boolean) => void;
+ setDnsOptions: (dns: IDnsOptions) => Promise<void>;
onClose: () => void;
}
@@ -105,6 +110,47 @@ export default class Preferences extends React.Component<IProps> {
</AriaInputGroup>
<AriaInputGroup>
+ <Cell.Container disabled={this.props.dns.state === 'custom'}>
+ <AriaLabel>
+ <Cell.InputLabel>
+ {messages.pgettext('preferences-view', 'Block ads')}
+ </Cell.InputLabel>
+ </AriaLabel>
+ <AriaInput>
+ <Cell.Switch
+ isOn={
+ this.props.dns.state === 'default' &&
+ this.props.dns.defaultOptions.blockAds
+ }
+ onChange={this.setBlockAds}
+ />
+ </AriaInput>
+ </Cell.Container>
+ </AriaInputGroup>
+ <StyledSeparator />
+ <AriaInputGroup>
+ <Cell.Container disabled={this.props.dns.state === 'custom'}>
+ <AriaLabel>
+ <Cell.InputLabel>
+ {messages.pgettext('preferences-view', 'Block trackers')}
+ </Cell.InputLabel>
+ </AriaLabel>
+ <AriaInput>
+ <Cell.Switch
+ isOn={
+ this.props.dns.state === 'default' &&
+ this.props.dns.defaultOptions.blockTrackers
+ }
+ onChange={this.setBlockTrackers}
+ />
+ </AriaInput>
+ </Cell.Container>
+ {this.props.dns.state === 'custom' && <CustomDnsEnabledFooter />}
+ </AriaInputGroup>
+
+ {this.props.dns.state !== 'custom' && <StyledSeparator height={20} />}
+
+ <AriaInputGroup>
<Cell.Container>
<AriaLabel>
<Cell.InputLabel>
@@ -275,4 +321,48 @@ export default class Preferences extends React.Component<IProps> {
</Layout>
);
}
+
+ private setBlockAds = async (enabled: boolean) => {
+ await this.props.setDnsOptions({
+ ...this.props.dns,
+ defaultOptions: {
+ ...this.props.dns.defaultOptions,
+ blockAds: enabled,
+ },
+ });
+ };
+
+ private setBlockTrackers = async (enabled: boolean) => {
+ await this.props.setDnsOptions({
+ ...this.props.dns,
+ defaultOptions: {
+ ...this.props.dns.defaultOptions,
+ blockTrackers: enabled,
+ },
+ });
+ };
+}
+
+function CustomDnsEnabledFooter() {
+ const customDnsFeatureName = messages.pgettext('advanced-settings-view', 'Use custom DNS server');
+
+ // TRANSLATORS: This is displayed when the custom DNS setting is turned on which makes the block
+ // TRANSLATORS: ads/trackers settings disabled. The text enclosed in "**" will appear bold.
+ // TRANSLATORS: Advanced settings refer to the name of the page with the title "Advanced".
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(customDnsFeatureName)s - The name displayed next to the custom DNS toggle.
+ const blockingDisabledText = messages.pgettext(
+ 'preferences-view',
+ 'Disable **%(customDnsFeatureName)s** (under Advanced settings) to activate these settings.',
+ );
+
+ return (
+ <Cell.Footer>
+ <AriaDescription>
+ <Cell.FooterText>
+ {formatMarkdown(sprintf(blockingDisabledText, { customDnsFeatureName }))}
+ </Cell.FooterText>
+ </AriaDescription>
+ </Cell.Footer>
+ );
}
diff --git a/gui/src/renderer/components/PreferencesStyles.tsx b/gui/src/renderer/components/PreferencesStyles.tsx
index b9986f2c38..f4fa114469 100644
--- a/gui/src/renderer/components/PreferencesStyles.tsx
+++ b/gui/src/renderer/components/PreferencesStyles.tsx
@@ -13,6 +13,6 @@ export const StyledContent = styled.div({
marginBottom: '2px',
});
-export const StyledSeparator = styled.div({
- height: '1px',
-});
+export const StyledSeparator = styled.div((props: { height?: number }) => ({
+ height: `${props.height ?? 1}px`,
+}));
diff --git a/gui/src/renderer/containers/PreferencesPage.tsx b/gui/src/renderer/containers/PreferencesPage.tsx
index 571219b528..3bd4a20e44 100644
--- a/gui/src/renderer/containers/PreferencesPage.tsx
+++ b/gui/src/renderer/containers/PreferencesPage.tsx
@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router';
+import { IDnsOptions } from '../../shared/daemon-rpc-types';
import log from '../../shared/logging';
import consumePromise from '../../shared/promise';
import Preferences from '../components/Preferences';
@@ -16,6 +17,7 @@ const mapStateToProps = (state: IReduxState) => ({
monochromaticIcon: state.settings.guiSettings.monochromaticIcon,
startMinimized: state.settings.guiSettings.startMinimized,
unpinnedWindow: state.settings.guiSettings.unpinnedWindow,
+ dns: state.settings.dns,
});
const mapDispatchToProps = (_dispatch: ReduxDispatch, props: RouteComponentProps & IAppContext) => {
@@ -51,6 +53,9 @@ const mapDispatchToProps = (_dispatch: ReduxDispatch, props: RouteComponentProps
setUnpinnedWindow: (unpinnedWindow: boolean) => {
props.app.setUnpinnedWindow(unpinnedWindow);
},
+ setDnsOptions: (dns: IDnsOptions) => {
+ return props.app.setDnsOptions(dns);
+ },
};
};
diff --git a/gui/src/renderer/markdown-formatter.tsx b/gui/src/renderer/markdown-formatter.tsx
new file mode 100644
index 0000000000..f455a882c4
--- /dev/null
+++ b/gui/src/renderer/markdown-formatter.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const boldSyntax = '**';
+const Bold = styled.span({ fontWeight: 'bold' });
+
+export function formatMarkdown(inputString: string): React.ReactElement {
+ const formattedString = inputString
+ .split(boldSyntax)
+ .map((value, index) =>
+ index % 2 === 0 ? (
+ <React.Fragment key={index}>{value}</React.Fragment>
+ ) : (
+ <Bold key={index}>{value}</Bold>
+ ),
+ );
+
+ return <>{formattedString}</>;
+}
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index 068add8e89..be4287c90f 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -6,6 +6,7 @@ import {
RelayLocation,
RelayProtocol,
TunnelProtocol,
+ IDnsOptions,
} from '../../../shared/daemon-rpc-types';
import { IGuiSettingsState } from '../../../shared/gui-settings-state';
import log from '../../../shared/logging';
@@ -133,10 +134,7 @@ export interface ISettingsReduxState {
wireguard: {
mtu?: number;
};
- dns: {
- custom: boolean;
- addresses: string[];
- };
+ dns: IDnsOptions;
wireguardKeyState: WgKeyState;
}
@@ -180,8 +178,14 @@ const initialState: ISettingsReduxState = {
type: 'key-not-set',
},
dns: {
- custom: false,
- addresses: [],
+ state: 'default',
+ defaultOptions: {
+ blockAds: false,
+ blockTrackers: false,
+ },
+ customOptions: {
+ addresses: [],
+ },
},
};
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 07a75c07ff..92d08e8d5c 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -261,8 +261,14 @@ export interface ITunnelOptions {
}
export interface IDnsOptions {
- custom: boolean;
- addresses: string[];
+ state: 'custom' | 'default';
+ customOptions: {
+ addresses: string[];
+ };
+ defaultOptions: {
+ blockAds: boolean;
+ blockTrackers: boolean;
+ };
}
export type ProxySettings = ILocalProxySettings | IRemoteProxySettings | IShadowsocksProxySettings;