diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-09-17 11:55:02 +0200 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-09-17 11:55:02 +0200 |
| commit | f0d4d64d1042248b255b3b7289bb4073088327cc (patch) | |
| tree | 59337b2a334557cba533858b27a9c58eaa5ad055 /gui | |
| parent | 13af28efa7f73843327b017234640d2c58bbbeea (diff) | |
| parent | d77bd4d9d9412aab5705d949570f419f03415734 (diff) | |
| download | mullvadvpn-f0d4d64d1042248b255b3b7289bb4073088327cc.tar.xz mullvadvpn-f0d4d64d1042248b255b3b7289bb4073088327cc.zip | |
Merge branch 'implement-use-anywhere-daita-setting-des-1068'
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/locales/messages.pot | 38 | ||||
| -rw-r--r-- | gui/src/main/daemon-rpc.ts | 16 | ||||
| -rw-r--r-- | gui/src/main/settings.ts | 7 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 7 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/DaitaSettings.tsx | 193 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardSettings.tsx | 90 | ||||
| -rw-r--r-- | gui/src/renderer/components/main-view/FeatureIndicators.tsx | 86 | ||||
| -rw-r--r-- | gui/src/renderer/components/select-location/RelayListContext.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/select-location/SelectLocation.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/lib/filter-locations.ts | 6 | ||||
| -rw-r--r-- | gui/src/renderer/lib/routes.ts | 1 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/ipc-schema.ts | 4 |
14 files changed, 343 insertions, 118 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 5969e4ffd3..730b75aaca 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -5,6 +5,11 @@ msgstr "" msgid "%(amount)d more..." msgstr "" +#. This refers to the Smart Routing setting in the VPN settings view. +#. This is displayed when both Smart Routing and DAITA features are on. +msgid "%(daita)s: Smart routing" +msgstr "" + msgid "%(duration)s was added, account paid until %(expiry)s." msgstr "" @@ -125,6 +130,9 @@ msgstr "" msgid "Disable" msgstr "" +msgid "Disable anyway" +msgstr "" + msgid "Disconnect" msgstr "" @@ -245,6 +253,9 @@ msgstr "" msgid "Settings" msgstr "" +msgid "Smart routing" +msgstr "" + msgid "System default" msgstr "" @@ -1935,6 +1946,10 @@ msgid "IPv4 is always enabled and the majority of websites and applications use msgstr "" msgctxt "vpn-settings-view" +msgid "Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency." +msgstr "" + +msgctxt "vpn-settings-view" msgid "It does this by allowing network communication outside the tunnel to local multicast and broadcast ranges as well as to and from these private IP ranges:" msgstr "" @@ -2065,7 +2080,7 @@ msgid "%(wireguard)s settings" msgstr "" msgctxt "wireguard-settings-view" -msgid "Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s." +msgid "Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage." msgstr "" msgctxt "wireguard-settings-view" @@ -2080,6 +2095,15 @@ msgctxt "wireguard-settings-view" msgid "MTU" msgstr "" +#. Warning text in a dialog that is displayed after a setting is toggled. +msgctxt "wireguard-settings-view" +msgid "Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing." +msgstr "" + +msgctxt "wireguard-settings-view" +msgid "Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first." +msgstr "" + msgctxt "wireguard-settings-view" msgid "Obfuscation" msgstr "" @@ -2134,11 +2158,6 @@ msgctxt "wireguard-settings-view" msgid "This allows access to %(wireguard)s for devices that only support IPv6." msgstr "" -#. Warning text in a dialog that is displayed after a setting is toggled. -msgctxt "wireguard-settings-view" -msgid "This feature isn’t available on all servers. You might need to change location after enabling." -msgstr "" - msgctxt "wireguard-settings-view" msgid "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers." msgstr "" @@ -2151,6 +2170,10 @@ msgctxt "wireguard-settings-view" msgid "UDP-over-TCP port" msgstr "" +msgctxt "wireguard-settings-view" +msgid "Use Smart routing" +msgstr "" + #. Text describing the valid port range for a port selector. msgctxt "wireguard-settings-view" msgid "Valid range: %(min)s - %(max)s" @@ -2541,6 +2564,9 @@ msgstr "" msgid "This address has already been entered." msgstr "" +msgid "This feature isn’t available on all servers. You might need to change location after enabling." +msgstr "" + msgid "This field is required" msgstr "" diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index c85189cea7..31f6ddcfab 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -38,7 +38,6 @@ import { IAppVersionInfo, IBridgeConstraints, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -586,13 +585,12 @@ export class DaemonRpc { await this.callBool(this.client.prepareRestartV2, quit); } - public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise<void> { - const grpcDaitaSettings = new grpcTypes.DaitaSettings(); - grpcDaitaSettings.setEnabled(daitaSettings.enabled); - await this.call<grpcTypes.DaitaSettings, Empty>( - this.client.setDaitaSettings, - grpcDaitaSettings, - ); + public async setEnableDaita(value: boolean): Promise<void> { + await this.callBool(this.client.setEnableDaita, value); + } + + public async setDaitaSmartRouting(value: boolean): Promise<void> { + await this.callBool(this.client.setDaitaSmartRouting, value); } public async listDevices(accountToken: AccountToken): Promise<Array<IDevice>> { @@ -1182,6 +1180,8 @@ function convertFromFeatureIndicator( return FeatureIndicator.customMssFix; case grpcTypes.FeatureIndicator.DAITA: return FeatureIndicator.daita; + case grpcTypes.FeatureIndicator.DAITA_SMART_ROUTING: + return FeatureIndicator.daitaSmartRouting; case grpcTypes.FeatureIndicator.SHADOWSOCKS: return FeatureIndicator.shadowsocks; } diff --git a/gui/src/main/settings.ts b/gui/src/main/settings.ts index 22238c72c4..03537ba581 100644 --- a/gui/src/main/settings.ts +++ b/gui/src/main/settings.ts @@ -107,8 +107,11 @@ export default class Settings implements Readonly<ISettings> { const settings = await fs.readFile(path); return this.daemonRpc.applyJsonSettings(settings.toString()); }); - IpcMainEventChannel.settings.handleSetDaitaSettings((daitaSettings) => { - return this.daemonRpc.setDaitaSettings(daitaSettings); + IpcMainEventChannel.settings.handleSetEnableDaita((value) => { + return this.daemonRpc.setEnableDaita(value); + }); + IpcMainEventChannel.settings.handleSetDaitaSmartRouting((value) => { + return this.daemonRpc.setDaitaSmartRouting(value); }); IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => { diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index a4c76aa2d1..a4d5fc2fad 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -19,7 +19,6 @@ import { IAccountData, IAppVersionInfo, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -345,8 +344,10 @@ export default class AppRenderer { IpcRendererEventChannel.splitTunneling.forgetManuallyAddedApplication(application); public setObfuscationSettings = (obfuscationSettings: ObfuscationSettings) => IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings); - public setDaitaSettings = (daitaSettings: IDaitaSettings) => - IpcRendererEventChannel.settings.setDaitaSettings(daitaSettings); + public setEnableDaita = (value: boolean) => + IpcRendererEventChannel.settings.setEnableDaita(value); + public setDaitaSmartRouting = (value: boolean) => + IpcRendererEventChannel.settings.setDaitaSmartRouting(value); public collectProblemReport = (toRedact: string | undefined) => IpcRendererEventChannel.problemReport.collectLogs(toRedact); public viewLog = (path: string) => IpcRendererEventChannel.problemReport.viewLog(path); diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index e90729507c..75b8df936c 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -8,6 +8,7 @@ import { ITransitionSpecification, transitions, useHistory } from '../lib/histor import { RoutePath } from '../lib/routes'; import Account from './Account'; import ApiAccessMethods from './ApiAccessMethods'; +import DaitaSettings from './DaitaSettings'; import Debug from './Debug'; import { DeviceRevokedView } from './DeviceRevokedView'; import { EditApiAccessMethod } from './EditApiAccessMethod'; @@ -85,6 +86,7 @@ export default function AppRouter() { <Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} /> <Route exact path={RoutePath.vpnSettings} component={VpnSettings} /> <Route exact path={RoutePath.wireguardSettings} component={WireguardSettings} /> + <Route exact path={RoutePath.daitaSettings} component={DaitaSettings} /> <Route exact path={RoutePath.udpOverTcp} component={UdpOverTcp} /> <Route exact path={RoutePath.shadowsocks} component={Shadowsocks} /> <Route exact path={RoutePath.openVpnSettings} component={OpenVpnSettings} /> diff --git a/gui/src/renderer/components/DaitaSettings.tsx b/gui/src/renderer/components/DaitaSettings.tsx new file mode 100644 index 0000000000..3176192ad1 --- /dev/null +++ b/gui/src/renderer/components/DaitaSettings.tsx @@ -0,0 +1,193 @@ +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 { useAppContext } from '../context'; +import { useHistory } from '../lib/history'; +import { useBoolean } from '../lib/utilityHooks'; +import { useSelector } from '../redux/store'; +import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; +import * as Cell from './cell'; +import InfoButton from './InfoButton'; +import { BackAction } from './KeyboardNavigation'; +import { Layout, SettingsContainer } from './Layout'; +import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; +import { + NavigationBar, + NavigationContainer, + NavigationInfoButton, + NavigationItems, + NavigationScrollbars, + TitleBarItem, +} from './NavigationBar'; +import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; +import { SmallButton, SmallButtonColor } from './SmallButton'; + +const StyledContent = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + marginBottom: '2px', +}); + +export default function DaitaSettings() { + const { pop } = useHistory(); + + return ( + <BackAction action={pop}> + <Layout> + <SettingsContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <TitleBarItem>{strings.daita}</TitleBarItem> + + <NavigationInfoButton> + <ModalMessage> + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.', + ), + { daita: strings.daita, daitaFull: strings.daitaFull }, + )} + </ModalMessage> + </NavigationInfoButton> + </NavigationItems> + </NavigationBar> + + <NavigationScrollbars> + <SettingsHeader> + <HeaderTitle>{strings.daita}</HeaderTitle> + <HeaderSubTitle> + {messages.pgettext( + 'wireguard-settings-view', + 'Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage.', + )} + </HeaderSubTitle> + </SettingsHeader> + + <StyledContent> + <Cell.Group> + <DaitaToggle /> + </Cell.Group> + </StyledContent> + </NavigationScrollbars> + </NavigationContainer> + </SettingsContainer> + </Layout> + </BackAction> + ); +} + +function DaitaToggle() { + const { setEnableDaita, setDaitaSmartRouting } = useAppContext(); + const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); + + const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); + + const setDaita = useCallback((value: boolean) => { + void setEnableDaita(value); + }, []); + + const setSmartRouting = useCallback((value: boolean) => { + if (value) { + void setDaitaSmartRouting(value); + } else { + showConfirmationDialog(); + } + }, []); + + const confirmDisableSmartRouting = useCallback(() => { + void setDaitaSmartRouting(false); + hideConfirmationDialog(); + }, []); + + return ( + <> + <AriaInputGroup> + <Cell.Container> + <AriaLabel> + <Cell.InputLabel>{messages.gettext('Enable')}</Cell.InputLabel> + </AriaLabel> + <AriaInput> + <Cell.Switch isOn={daita} onChange={setDaita} /> + </AriaInput> + </Cell.Container> + </AriaInputGroup> + <AriaInputGroup> + <Cell.Container disabled={!daita}> + <AriaLabel> + <Cell.InputLabel>{messages.gettext('Smart routing')}</Cell.InputLabel> + </AriaLabel> + <InfoButton> + <SmartRoutingModalMessage /> + </InfoButton> + <AriaInput> + <Cell.Switch isOn={smartRouting} onChange={setSmartRouting} /> + </AriaInput> + </Cell.Container> + <Cell.CellFooter> + <AriaDescription> + <Cell.CellFooterText> + {sprintf( + messages.pgettext( + 'vpn-settings-view', + 'Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency.', + ), + { daita: strings.daita }, + )} + </Cell.CellFooterText> + </AriaDescription> + </Cell.CellFooter> + </AriaInputGroup> + <ModalAlert + isOpen={confirmationDialogVisible} + type={ModalAlertType.caution} + gridButtons={[ + <SmallButton + key="confirm" + onClick={confirmDisableSmartRouting} + color={SmallButtonColor.blue}> + {messages.gettext('Disable anyway')} + </SmallButton>, + <SmallButton key="cancel" onClick={hideConfirmationDialog} color={SmallButtonColor.blue}> + {messages.pgettext('wireguard-settings-view', 'Use Smart routing')} + </SmallButton>, + ]} + close={hideConfirmationDialog}> + <ModalMessage> + {sprintf( + // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled. + messages.pgettext( + 'wireguard-settings-view', + 'Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing.', + ), + { daita: strings.daita }, + )} + </ModalMessage> + </ModalAlert> + </> + ); +} + +export function SmartRoutingModalMessage() { + return ( + <ModalMessage> + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + 'Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first.', + ), + { + daita: strings.daita, + }, + )} + </ModalMessage> + ); +} diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 1f4709159f..8f2329d77f 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -22,7 +22,6 @@ 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 InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; @@ -98,7 +97,7 @@ export default function WireguardSettings() { </Cell.Group> <Cell.Group> - <DaitaSettings /> + <DaitaButton /> </Cell.Group> <Cell.Group> @@ -528,89 +527,16 @@ function MtuSetting() { ); } -function DaitaSettings() { - const { setDaitaSettings } = useAppContext(); +function DaitaButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.daitaSettings), [history]); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); - const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); - - const setDaita = useCallback((value: boolean) => { - if (value) { - showConfirmationDialog(); - } else { - void setDaitaSettings({ enabled: value }); - } - }, []); - - const confirmDaita = useCallback(() => { - void setDaitaSettings({ enabled: true }); - hideConfirmationDialog(); - }, []); - return ( - <> - <AriaInputGroup> - <Cell.Container> - <AriaLabel> - <Cell.InputLabel>{strings.daita}</Cell.InputLabel> - </AriaLabel> - <InfoButton> - <ModalMessage> - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.', - ), - { daita: strings.daita, daitaFull: strings.daitaFull }, - )} - </ModalMessage> - <ModalMessage> - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.', - ), - { daita: strings.daita }, - )} - </ModalMessage> - </InfoButton> - <AriaInput> - <Cell.Switch isOn={daita} onChange={setDaita} /> - </AriaInput> - </Cell.Container> - </AriaInputGroup> - <ModalAlert - isOpen={confirmationDialogVisible} - type={ModalAlertType.caution} - buttons={[ - <AppButton.BlueButton key="confirm" onClick={confirmDaita}> - {messages.gettext('Enable anyway')} - </AppButton.BlueButton>, - <AppButton.BlueButton key="back" onClick={hideConfirmationDialog}> - {messages.gettext('Back')} - </AppButton.BlueButton>, - ]} - close={hideConfirmationDialog}> - <ModalMessage> - { - // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled. - messages.pgettext( - 'wireguard-settings-view', - 'This feature isn’t available on all servers. You might need to change location after enabling.', - ) - } - </ModalMessage> - <ModalMessage> - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.', - ), - { daita: strings.daita }, - )} - </ModalMessage> - </ModalAlert> - </> + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label>{strings.daita}</Cell.Label> + <Cell.SubText>{daita ? messages.gettext('On') : messages.gettext('Off')}</Cell.SubText> + </Cell.CellNavigationButton> ); } diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx index 68ee0ae5ae..4d189e712c 100644 --- a/gui/src/renderer/components/main-view/FeatureIndicators.tsx +++ b/gui/src/renderer/components/main-view/FeatureIndicators.tsx @@ -5,9 +5,13 @@ import styled from 'styled-components'; import { colors, strings } from '../../../config.json'; import { FeatureIndicator } from '../../../shared/daemon-rpc-types'; import { messages } from '../../../shared/gettext'; -import { useStyledRef } from '../../lib/utilityHooks'; +import { useBoolean, useStyledRef } from '../../lib/utilityHooks'; import { useSelector } from '../../redux/store'; import { tinyText } from '../common-styles'; +import { SmartRoutingModalMessage } from '../DaitaSettings'; +import { InfoIcon } from '../InfoButton'; +import { ModalAlert, ModalAlertType } from '../Modal'; +import { SmallButton, SmallButtonColor } from '../SmallButton'; import { ConnectionPanelAccordion } from './styles'; const LINE_HEIGHT = 22; @@ -43,8 +47,9 @@ const StyledFeatureIndicatorsWrapper = styled.div<{ $expanded: boolean }>((props })); const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText, (props) => ({ - display: 'inline', - padding: '2px 8px', + display: 'flex', + gap: '4px', + padding: '1px 7px', justifyContent: 'center', alignItems: 'center', borderRadius: '4px', @@ -53,6 +58,15 @@ const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText fontWeight: 400, whiteSpace: 'nowrap', visibility: props.$expanded ? 'visible' : 'hidden', + + // Style clickable feature indicators with a border and on-hover effect + boxSizing: 'border-box', // make border act as padding rather than margin + border: 'solid 1px', + borderColor: props.onClick ? colors.blue : colors.darkerBlue, + transition: 'background ease-in-out 300ms', + '&&:hover': { + background: props.onClick ? colors.blue60 : undefined, + }, })); const StyledBaseEllipsis = styled.span<{ $display: boolean }>(tinyText, (props) => ({ @@ -88,6 +102,11 @@ interface FeatureIndicatorsProps { // we can count those and add another ellipsis element which is visible and place it after the last // visible indicator. export default function FeatureIndicators(props: FeatureIndicatorsProps) { + const [ + daitaSmartRoutingDialogueVisible, + showDaitaSmartRoutingDialogue, + hideDaitaSmartRoutingDialogue, + ] = useBoolean(); const tunnelState = useSelector((state) => state.connection.status); const ellipsisRef = useStyledRef<HTMLSpanElement>(); const ellipsisSpacerRef = useStyledRef<HTMLSpanElement>(); @@ -106,6 +125,16 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { const ellipsis = messages.gettext('%(amount)d more...'); + // Returns an optional callback for clickable feature indicators, or undefined. + const getFeatureIndicatorOnClick = (indicator: FeatureIndicator) => { + switch (indicator) { + case FeatureIndicator.daitaSmartRouting: + return showDaitaSmartRoutingDialogue; + default: + return undefined; + } + }; + useEffect(() => { // We need to defer the visibility logic one painting cycle to make sure the elements are // rendered and available. @@ -164,7 +193,7 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { return ( <StyledAccordion expanded={featureIndicatorsVisible && featureIndicators.current.length > 0}> - <StyledFeatureIndicatorsContainer onClick={props.expandIsland} $expanded={props.expanded}> + <StyledFeatureIndicatorsContainer $expanded={props.expanded}> <StyledAccordion expanded={props.expanded}> <StyledTitle>{messages.pgettext('connect-view', 'Active features')}</StyledTitle> </StyledAccordion> @@ -172,16 +201,20 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { <StyledFeatureIndicatorsWrapper ref={featureIndicatorsContainerRef} $expanded={props.expanded}> - {sortedIndicators.map((indicator) => ( - <StyledFeatureIndicatorLabel - key={indicator.toString()} - data-testid="feature-indicator" - $expanded={props.expanded}> - {getFeatureIndicatorLabel(indicator)} - </StyledFeatureIndicatorLabel> - ))} + {sortedIndicators.map((indicator) => { + const onClick = getFeatureIndicatorOnClick(indicator); + return ( + <StyledFeatureIndicatorLabel + key={indicator.toString()} + data-testid="feature-indicator" + onClick={onClick} + $expanded={props.expanded}> + {getFeatureIndicatorLabel(indicator)} + {onClick ? <InfoIcon size={10} /> : null} + </StyledFeatureIndicatorLabel> + ); + })} </StyledFeatureIndicatorsWrapper> - <StyledEllipsis $display={!props.expanded} ref={ellipsisRef} /> <StyledEllipsisSpacer $display={!props.expanded} ref={ellipsisSpacerRef}> { // Mock amount for the spacer ellipsis. This needs to be wider than the real @@ -189,8 +222,28 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { sprintf(ellipsis, { amount: 222 }) } </StyledEllipsisSpacer> + <StyledEllipsis + onClick={props.expandIsland} + $display={!props.expanded} + ref={ellipsisRef} + /> </StyledFeatureIndicators> </StyledFeatureIndicatorsContainer> + + <ModalAlert + isOpen={daitaSmartRoutingDialogueVisible} + type={ModalAlertType.info} + gridButtons={[ + <SmallButton + key="dismiss" + onClick={hideDaitaSmartRoutingDialogue} + color={SmallButtonColor.blue}> + {messages.gettext('Got it!')} + </SmallButton>, + ]} + close={hideDaitaSmartRoutingDialogue}> + <SmartRoutingModalMessage /> + </ModalAlert> </StyledAccordion> ); } @@ -216,6 +269,13 @@ function getFeatureIndicatorLabel(indicator: FeatureIndicator) { switch (indicator) { case FeatureIndicator.daita: return strings.daita; + case FeatureIndicator.daitaSmartRouting: + return sprintf( + // TRANSLATORS: This refers to the Smart Routing setting in the VPN settings view. + // TRANSLATORS: This is displayed when both Smart Routing and DAITA features are on. + messages.gettext('%(daita)s: Smart routing'), + { daita: strings.daita }, + ); case FeatureIndicator.udp2tcp: case FeatureIndicator.shadowsocks: return messages.pgettext('wireguard-settings-view', 'Obfuscation'); diff --git a/gui/src/renderer/components/select-location/RelayListContext.tsx b/gui/src/renderer/components/select-location/RelayListContext.tsx index 13e2dc28e8..241d11fbdc 100644 --- a/gui/src/renderer/components/select-location/RelayListContext.tsx +++ b/gui/src/renderer/components/select-location/RelayListContext.tsx @@ -62,6 +62,10 @@ interface RelayListContextProviderProps { export function RelayListContextProvider(props: RelayListContextProviderProps) { const { locationType, searchTerm } = useSelectLocationContext(); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); + const fullRelayList = useSelector((state) => state.settings.relayLocations); const relaySettings = useNormalRelaySettings(); @@ -77,6 +81,7 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { return filterLocationsByDaita( relayListForEndpointType, daita, + smartRouting, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index f6cf772019..9cd66f98f4 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -68,8 +68,12 @@ export default function SelectLocation() { const providers = relaySettings?.providers ?? []; const filteredProviders = useFilteredProviders(providers, ownership); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); const showDaitaFilter = daitaFilterActive( daita, + smartRouting, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/lib/filter-locations.ts b/gui/src/renderer/lib/filter-locations.ts index 78126f1fb4..661d8ecc29 100644 --- a/gui/src/renderer/lib/filter-locations.ts +++ b/gui/src/renderer/lib/filter-locations.ts @@ -37,17 +37,19 @@ export function filterLocationsByEndPointType( export function filterLocationsByDaita( locations: IRelayLocationCountryRedux[], daita: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint<TunnelProtocol>, multihop: boolean, ): IRelayLocationCountryRedux[] { - return daitaFilterActive(daita, locationType, tunnelProtocol, multihop) + return daitaFilterActive(daita, smartRouting, locationType, tunnelProtocol, multihop) ? filterLocationsImpl(locations, (relay: IRelayLocationRelayRedux) => relay.daita) : locations; } export function daitaFilterActive( daita: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint<TunnelProtocol>, multihop: boolean, @@ -55,7 +57,7 @@ export function daitaFilterActive( const isEntry = multihop ? locationType === LocationType.entry : locationType === LocationType.exit; - return daita && isEntry && tunnelProtocol !== 'openvpn'; + return daita && (!smartRouting || multihop) && isEntry && tunnelProtocol !== 'openvpn'; } export function filterLocations( diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts index 0ccc3679ff..5204dc666c 100644 --- a/gui/src/renderer/lib/routes.ts +++ b/gui/src/renderer/lib/routes.ts @@ -15,6 +15,7 @@ export enum RoutePath { userInterfaceSettings = '/settings/interface', vpnSettings = '/settings/vpn', wireguardSettings = '/settings/advanced/wireguard', + daitaSettings = '/settings/advanced/wireguard/daita', udpOverTcp = '/settings/advanced/wireguard/udp-over-tcp', shadowsocks = '/settings/advanced/shadowsocks', openVpnSettings = '/settings/advanced/openvpn', diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index b84ee8a72e..33ff6bb8ce 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -183,6 +183,7 @@ export interface ITunnelStateRelayInfo { // The order of the variants match the priority order and can be sorted on. export enum FeatureIndicator { daita, + daitaSmartRouting, quantumResistance, multihop, bridgeMode, @@ -551,6 +552,7 @@ export interface RelayOverride { export interface IDaitaSettings { enabled: boolean; + smartRouting: boolean; } export function parseSocketAddress(socketAddrStr: string): ISocketAddress { diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index acbb6366d2..4e08e7109d 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -14,7 +14,6 @@ import { IAccountData, IAppVersionInfo, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -195,7 +194,8 @@ export const ipcSchema = { testApiAccessMethodById: invoke<string, boolean>(), testCustomApiAccessMethod: invoke<CustomProxy, boolean>(), clearAllRelayOverrides: invoke<void, void>(), - setDaitaSettings: invoke<IDaitaSettings, void>(), + setEnableDaita: invoke<boolean, void>(), + setDaitaSmartRouting: invoke<boolean, void>(), }, guiSettings: { '': notifyRenderer<IGuiSettingsState>(), |
