summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-09-23 17:53:41 +0200
committerOskar <oskar@mullvad.net>2024-09-30 09:20:20 +0200
commit2e6ed4dbc6fa5a5aae6d57dbfedd901f83679f75 (patch)
tree04789a223a57329c46a684e12f149bff9825da89 /gui
parent25ddd914ff6136bc6c2b68855fd2627adab3b410 (diff)
downloadmullvadvpn-2e6ed4dbc6fa5a5aae6d57dbfedd901f83679f75.tar.xz
mullvadvpn-2e6ed4dbc6fa5a5aae6d57dbfedd901f83679f75.zip
Move multihop to top level settings along with a dedicated view
Diffstat (limited to 'gui')
-rw-r--r--gui/src/renderer/components/AppRouter.tsx2
-rw-r--r--gui/src/renderer/components/MultihopSettings.tsx169
-rw-r--r--gui/src/renderer/components/Settings.tsx19
-rw-r--r--gui/src/renderer/components/WireguardSettings.tsx101
-rw-r--r--gui/src/renderer/lib/routes.ts1
5 files changed, 192 insertions, 100 deletions
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx
index 75b8df936c..e1a3d2ab0f 100644
--- a/gui/src/renderer/components/AppRouter.tsx
+++ b/gui/src/renderer/components/AppRouter.tsx
@@ -24,6 +24,7 @@ import Filter from './Filter';
import Focus, { IFocusHandle } from './Focus';
import Launch from './Launch';
import MainView from './main-view/MainView';
+import MultihopSettings from './MultihopSettings';
import OpenVpnSettings from './OpenVpnSettings';
import ProblemReport from './ProblemReport';
import SelectLanguage from './SelectLanguage';
@@ -84,6 +85,7 @@ export default function AppRouter() {
<Route exact path={RoutePath.settings} component={Settings} />
<Route exact path={RoutePath.selectLanguage} component={SelectLanguage} />
<Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} />
+ <Route exact path={RoutePath.multihopSettings} component={MultihopSettings} />
<Route exact path={RoutePath.vpnSettings} component={VpnSettings} />
<Route exact path={RoutePath.wireguardSettings} component={WireguardSettings} />
<Route exact path={RoutePath.daitaSettings} component={DaitaSettings} />
diff --git a/gui/src/renderer/components/MultihopSettings.tsx b/gui/src/renderer/components/MultihopSettings.tsx
new file mode 100644
index 0000000000..b2290b71d5
--- /dev/null
+++ b/gui/src/renderer/components/MultihopSettings.tsx
@@ -0,0 +1,169 @@
+import { useCallback } from 'react';
+import { sprintf } from 'sprintf-js';
+import styled from 'styled-components';
+
+import { strings } from '../../config.json';
+import { messages } from '../../shared/gettext';
+import log from '../../shared/logging';
+import { useRelaySettingsUpdater } from '../lib/constraint-updater';
+import { useHistory } from '../lib/history';
+import { useBoolean } from '../lib/utilityHooks';
+import { useSelector } from '../redux/store';
+import * as AppButton from './AppButton';
+import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
+import * as Cell from './cell';
+import { BackAction } from './KeyboardNavigation';
+import { Layout, SettingsContainer } from './Layout';
+import { ModalAlert, ModalAlertType } from './Modal';
+import {
+ NavigationBar,
+ NavigationContainer,
+ NavigationItems,
+ NavigationScrollbars,
+ TitleBarItem,
+} from './NavigationBar';
+import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader';
+
+const StyledContent = styled.div({
+ display: 'flex',
+ flexDirection: 'column',
+ flex: 1,
+ marginBottom: '2px',
+});
+
+export default function MultihopSettings() {
+ const { pop } = useHistory();
+
+ return (
+ <BackAction action={pop}>
+ <Layout>
+ <SettingsContainer>
+ <NavigationContainer>
+ <NavigationBar>
+ <NavigationItems>
+ <TitleBarItem>
+ {messages.pgettext('wireguard-settings-view', 'Multihop')}
+ </TitleBarItem>
+ </NavigationItems>
+ </NavigationBar>
+
+ <NavigationScrollbars>
+ <SettingsHeader>
+ <HeaderTitle>
+ {messages.pgettext('wireguard-settings-view', 'Multihop')}
+ </HeaderTitle>
+ <HeaderSubTitle>
+ {messages.pgettext(
+ 'wireguard-settings-view',
+ 'Multihop routes your traffic into one WireGuard server and out another, making it harder to trace. This results in increased latency but increases anonymity online.',
+ )}
+ </HeaderSubTitle>
+ </SettingsHeader>
+
+ <StyledContent>
+ <Cell.Group>
+ <MultihopSetting />
+ </Cell.Group>
+ </StyledContent>
+ </NavigationScrollbars>
+ </NavigationContainer>
+ </SettingsContainer>
+ </Layout>
+ </BackAction>
+ );
+}
+
+function MultihopSetting() {
+ const relaySettings = useSelector((state) => state.settings.relaySettings);
+ const relaySettingsUpdater = useRelaySettingsUpdater();
+
+ const multihop = 'normal' in relaySettings ? relaySettings.normal.wireguard.useMultihop : false;
+ const unavailable =
+ 'normal' in relaySettings ? relaySettings.normal.tunnelProtocol === 'openvpn' : true;
+
+ const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean();
+
+ const setMultihopImpl = useCallback(
+ async (enabled: boolean) => {
+ try {
+ await relaySettingsUpdater((settings) => {
+ settings.wireguardConstraints.useMultihop = enabled;
+ return settings;
+ });
+ } catch (e) {
+ const error = e as Error;
+ log.error('Failed to update WireGuard multihop settings', error.message);
+ }
+ },
+ [relaySettingsUpdater],
+ );
+
+ const setMultihop = useCallback(
+ async (newValue: boolean) => {
+ if (newValue) {
+ showConfirmationDialog();
+ } else {
+ await setMultihopImpl(false);
+ }
+ },
+ [setMultihopImpl],
+ );
+
+ const confirmMultihop = useCallback(async () => {
+ await setMultihopImpl(true);
+ hideConfirmationDialog();
+ }, [setMultihopImpl]);
+
+ return (
+ <>
+ <AriaInputGroup>
+ <Cell.Container disabled={unavailable}>
+ <AriaLabel>
+ <Cell.InputLabel>{messages.gettext('Enable')}</Cell.InputLabel>
+ </AriaLabel>
+ <AriaInput>
+ <Cell.Switch isOn={multihop && !unavailable} onChange={setMultihop} />
+ </AriaInput>
+ </Cell.Container>
+ {unavailable ? (
+ <Cell.CellFooter>
+ <AriaDescription>
+ <Cell.CellFooterText>{featureUnavailableMessage()}</Cell.CellFooterText>
+ </AriaDescription>
+ </Cell.CellFooter>
+ ) : null}
+ </AriaInputGroup>
+ <ModalAlert
+ isOpen={confirmationDialogVisible}
+ type={ModalAlertType.caution}
+ message={
+ // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled.
+ messages.gettext('This setting increases latency. Use only if needed.')
+ }
+ buttons={[
+ <AppButton.RedButton key="confirm" onClick={confirmMultihop}>
+ {messages.gettext('Enable anyway')}
+ </AppButton.RedButton>,
+ <AppButton.BlueButton key="back" onClick={hideConfirmationDialog}>
+ {messages.gettext('Back')}
+ </AppButton.BlueButton>,
+ ]}
+ close={hideConfirmationDialog}
+ />
+ </>
+ );
+}
+
+function featureUnavailableMessage() {
+ const automatic = messages.gettext('Automatic');
+ const tunnelProtocol = messages.pgettext('vpn-settings-view', 'Tunnel protocol');
+ const multihop = messages.pgettext('wireguard-settings-view', 'Multihop');
+
+ return sprintf(
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'Switch to “%(wireguard)s” or “%(automatic)s” in Settings > %(tunnelProtocol)s to make %(setting)s available.',
+ ),
+ { wireguard: strings.wireguard, automatic, tunnelProtocol, setting: multihop },
+ );
+}
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index 98d36fb9fd..2280998d3a 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -70,6 +70,7 @@ export default function Support() {
<>
<Cell.Group>
<UserInterfaceSettingsButton />
+ <MultihopButton />
<VpnSettingsButton />
</Cell.Group>
@@ -133,6 +134,24 @@ function UserInterfaceSettingsButton() {
);
}
+function MultihopButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.multihopSettings), [history]);
+ const relaySettings = useSelector((state) => state.settings.relaySettings);
+ const multihop = 'normal' in relaySettings ? relaySettings.normal.wireguard.useMultihop : false;
+ const unavailable =
+ 'normal' in relaySettings ? relaySettings.normal.tunnelProtocol === 'openvpn' : true;
+
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>{messages.pgettext('settings-view', 'Multihop')}</Cell.Label>
+ <Cell.SubText>
+ {multihop && !unavailable ? messages.gettext('On') : messages.gettext('Off')}
+ </Cell.SubText>
+ </Cell.CellNavigationButton>
+ );
+}
+
function VpnSettingsButton() {
const history = useHistory();
const navigate = useCallback(() => history.push(RoutePath.vpnSettings), [history]);
diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx
index 8f2329d77f..51817c2029 100644
--- a/gui/src/renderer/components/WireguardSettings.tsx
+++ b/gui/src/renderer/components/WireguardSettings.tsx
@@ -16,15 +16,13 @@ import { useAppContext } from '../context';
import { useRelaySettingsUpdater } from '../lib/constraint-updater';
import { useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
-import { useBoolean } from '../lib/utilityHooks';
import { useSelector } from '../redux/store';
-import * as AppButton from './AppButton';
import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
import * as Cell from './cell';
import Selector, { SelectorItem, SelectorWithCustomItem } from './cell/Selector';
import { BackAction } from './KeyboardNavigation';
import { Layout, SettingsContainer } from './Layout';
-import { ModalAlert, ModalAlertType, ModalMessage } from './Modal';
+import { ModalMessage } from './Modal';
import {
NavigationBar,
NavigationContainer,
@@ -105,10 +103,6 @@ export default function WireguardSettings() {
</Cell.Group>
<Cell.Group>
- <MultihopSetting />
- </Cell.Group>
-
- <Cell.Group>
<IpVersionSetting />
</Cell.Group>
@@ -286,99 +280,6 @@ function formatPortForSubLabel(port: Constraint<number>): string {
return port === 'any' ? messages.gettext('Automatic') : `${port.only}`;
}
-function MultihopSetting() {
- const relaySettings = useSelector((state) => state.settings.relaySettings);
- const relaySettingsUpdater = useRelaySettingsUpdater();
-
- const multihop = 'normal' in relaySettings ? relaySettings.normal.wireguard.useMultihop : false;
-
- const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean();
-
- const setMultihopImpl = useCallback(
- async (enabled: boolean) => {
- try {
- await relaySettingsUpdater((settings) => {
- settings.wireguardConstraints.useMultihop = enabled;
- return settings;
- });
- } catch (e) {
- const error = e as Error;
- log.error('Failed to update WireGuard multihop settings', error.message);
- }
- },
- [relaySettingsUpdater],
- );
-
- const setMultihop = useCallback(
- async (newValue: boolean) => {
- if (newValue) {
- showConfirmationDialog();
- } else {
- await setMultihopImpl(false);
- }
- },
- [setMultihopImpl],
- );
-
- const confirmMultihop = useCallback(async () => {
- await setMultihopImpl(true);
- hideConfirmationDialog();
- }, [setMultihopImpl]);
-
- return (
- <>
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {
- // TRANSLATORS: The label next to the multihop settings toggle.
- messages.pgettext('vpn-settings-view', 'Enable multihop')
- }
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={multihop} onChange={setMultihop} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {sprintf(
- // TRANSLATORS: Description for multihop settings toggle.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard"
- messages.pgettext(
- 'vpn-settings-view',
- 'Increases anonymity by routing your traffic into one %(wireguard)s server and out another, making it harder to trace.',
- ),
- { wireguard: strings.wireguard },
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- <ModalAlert
- isOpen={confirmationDialogVisible}
- type={ModalAlertType.caution}
- message={
- // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled.
- messages.gettext('This setting increases latency. Use only if needed.')
- }
- buttons={[
- <AppButton.RedButton key="confirm" onClick={confirmMultihop}>
- {messages.gettext('Enable anyway')}
- </AppButton.RedButton>,
- <AppButton.BlueButton key="back" onClick={hideConfirmationDialog}>
- {messages.gettext('Back')}
- </AppButton.BlueButton>,
- ]}
- close={hideConfirmationDialog}
- />
- </>
- );
-}
-
function IpVersionSetting() {
const relaySettingsUpdater = useRelaySettingsUpdater();
const relaySettings = useSelector((state) => state.settings.relaySettings);
diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts
index 5204dc666c..68e8357b6e 100644
--- a/gui/src/renderer/lib/routes.ts
+++ b/gui/src/renderer/lib/routes.ts
@@ -13,6 +13,7 @@ export enum RoutePath {
selectLanguage = '/settings/language',
account = '/account',
userInterfaceSettings = '/settings/interface',
+ multihopSettings = '/settings/multihop',
vpnSettings = '/settings/vpn',
wireguardSettings = '/settings/advanced/wireguard',
daitaSettings = '/settings/advanced/wireguard/daita',