diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-12-18 15:31:57 +0100 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-12-18 15:31:57 +0100 |
| commit | 2c472944dc3521cbd694e77766e54d04faebe880 (patch) | |
| tree | d7a92cd897506f1f78866637fba887c6cc4086e1 | |
| parent | 7cb8b8c16cbcb2938823294c1d6f78bab4040c9e (diff) | |
| parent | 64828f69ed57ea9b02aba037997efb4d41d3a2b2 (diff) | |
| download | mullvadvpn-2c472944dc3521cbd694e77766e54d04faebe880.tar.xz mullvadvpn-2c472944dc3521cbd694e77766e54d04faebe880.zip | |
Merge branch 'add-spinner-while-fda-check-is-being-performed-des-1554'
5 files changed, 133 insertions, 66 deletions
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index be9d787aa5..1b5f981cc5 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -1781,6 +1781,10 @@ msgid "Connecting. %(location)s" msgstr "" msgctxt "troubleshoot" +msgid "Disable split tunneling" +msgstr "" + +msgctxt "troubleshoot" msgid "Enable “Full Disk Access” for “Mullvad VPN” in the macOS system settings." msgstr "" diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx index ab9965250a..df3c78d4b0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx @@ -9,7 +9,6 @@ import { ErrorNotificationProvider, InAppNotificationAction, InAppNotificationProvider, - InAppNotificationTroubleshootInfo, InconsistentVersionNotificationProvider, ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, @@ -47,7 +46,7 @@ interface IProps { } export default function NotificationArea(props: IProps) { - const { showFullDiskAccessSettings } = useAppContext(); + const { showFullDiskAccessSettings, reconnectTunnel } = useAppContext(); const account = useSelector((state: IReduxState) => state.account); const locale = useSelector((state: IReduxState) => state.userInterface.locale); @@ -75,6 +74,15 @@ export default function NotificationArea(props: IProps) { setDisplayedChangelog(); }, [setDisplayedChangelog]); + const [isModalOpen, setIsModalOpen] = useState(false); + + const { setSplitTunnelingState } = useAppContext(); + const disableSplitTunneling = useCallback(async () => { + setIsModalOpen(false); + await setSplitTunnelingState(false); + await reconnectTunnel(); + }, [reconnectTunnel, setSplitTunnelingState]); + const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), new ReconnectingNotificationProvider(tunnelState), @@ -83,7 +91,13 @@ export default function NotificationArea(props: IProps) { blockWhenDisconnected, hasExcludedApps, }), - new ErrorNotificationProvider({ tunnelState, hasExcludedApps, showFullDiskAccessSettings }), + + new ErrorNotificationProvider({ + tunnelState, + hasExcludedApps, + showFullDiskAccessSettings, + disableSplitTunneling, + }), new InconsistentVersionNotificationProvider({ consistent: version.consistent }), new UnsupportedVersionNotificationProvider(version), ]; @@ -140,7 +154,13 @@ export default function NotificationArea(props: IProps) { )} </NotificationSubtitle> </NotificationContent> - {notification.action && <NotificationActionWrapper action={notification.action} />} + {notification.action && ( + <NotificationActionWrapper + action={notification.action} + isModalOpen={isModalOpen} + setIsModalOpen={setIsModalOpen} + /> + )} </NotificationBanner> ); } else { @@ -153,46 +173,51 @@ export default function NotificationArea(props: IProps) { return <NotificationBanner className={props.className} aria-hidden={true} />; } -interface INotificationActionWrapperProps { +interface NotificationActionWrapperProps { action: InAppNotificationAction; + isModalOpen: boolean; + setIsModalOpen: (isOpen: boolean) => void; } -function NotificationActionWrapper(props: INotificationActionWrapperProps) { +function NotificationActionWrapper({ + action, + isModalOpen, + setIsModalOpen, +}: NotificationActionWrapperProps) { const { push } = useHistory(); const { openLinkWithAuth, openUrl } = useAppContext(); - const [troubleshootInfo, setTroubleshootInfo] = useState<InAppNotificationTroubleshootInfo>(); + + const closeTroubleshootModal = useCallback(() => setIsModalOpen(false), [setIsModalOpen]); const handleClick = useCallback(() => { - if (props.action) { - switch (props.action.type) { + if (action) { + switch (action.type) { case 'open-url': - if (props.action.withAuth) { - return openLinkWithAuth(props.action.url); + if (action.withAuth) { + return openLinkWithAuth(action.url); } else { - return openUrl(props.action.url); + return openUrl(action.url); } case 'troubleshoot-dialog': - setTroubleshootInfo(props.action.troubleshoot); + setIsModalOpen(true); break; case 'close': - props.action.close(); + action.close(); break; } } return Promise.resolve(); - }, [openLinkWithAuth, openUrl, props.action]); + }, [action, setIsModalOpen, openLinkWithAuth, openUrl]); const goToProblemReport = useCallback(() => { - setTroubleshootInfo(undefined); + closeTroubleshootModal(); push(RoutePath.problemReport, { transition: transitions.show }); - }, [push]); - - const closeTroubleshootInfo = useCallback(() => setTroubleshootInfo(undefined), []); + }, [closeTroubleshootModal, push]); let actionComponent: React.ReactElement | undefined; - if (props.action) { - switch (props.action.type) { + if (action) { + switch (action.type) { case 'open-url': actionComponent = <NotificationOpenLinkAction onClick={handleClick} />; break; @@ -208,7 +233,11 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) { } } - const problemReportButton = troubleshootInfo?.buttons ? ( + if (action.type !== 'troubleshoot-dialog') { + return <NotificationActions>{actionComponent}</NotificationActions>; + } + + const problemReportButton = action.troubleshoot?.buttons ? ( <AppButton.BlueButton key="problem-report" onClick={goToProblemReport}> {messages.pgettext('in-app-notifications', 'Send problem report')} </AppButton.BlueButton> @@ -220,17 +249,32 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) { let buttons = [ problemReportButton, - <AppButton.BlueButton key="back" onClick={closeTroubleshootInfo}> + <AppButton.BlueButton key="back" onClick={closeTroubleshootModal}> {messages.gettext('Back')} </AppButton.BlueButton>, ]; - if (troubleshootInfo?.buttons) { - const actionButtons = troubleshootInfo.buttons.map((button) => ( - <AppButton.GreenButton key={button.label} onClick={button.action}> - {button.label} - </AppButton.GreenButton> - )); + if (action.troubleshoot?.buttons) { + const actionButtons = action.troubleshoot.buttons.map(({ variant, label, action }) => { + if (variant === 'success') + return ( + <AppButton.GreenButton key={label} onClick={action}> + {label} + </AppButton.GreenButton> + ); + else if (variant === 'destructive') + return ( + <AppButton.RedButton key={label} onClick={action}> + {label} + </AppButton.RedButton> + ); + else + return ( + <AppButton.BlueButton key={label} onClick={action}> + {label} + </AppButton.BlueButton> + ); + }); buttons = actionButtons.concat(buttons); } @@ -239,14 +283,14 @@ function NotificationActionWrapper(props: INotificationActionWrapperProps) { <> <NotificationActions>{actionComponent}</NotificationActions> <ModalAlert - isOpen={troubleshootInfo !== undefined} + isOpen={isModalOpen} type={ModalAlertType.info} buttons={buttons} - close={closeTroubleshootInfo}> - <ModalMessage>{troubleshootInfo?.details}</ModalMessage> + close={closeTroubleshootModal}> + <ModalMessage>{action.troubleshoot?.details}</ModalMessage> <ModalMessage> <ModalMessageList> - {troubleshootInfo?.steps.map((step) => <li key={step}>{step}</li>)} + {action.troubleshoot?.steps.map((step) => <li key={step}>{step}</li>)} </ModalMessageList> </ModalMessage> <ModalMessage> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx index 4070b7dba5..a24790e66a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx @@ -66,12 +66,10 @@ export default function SplitTunneling() { </NavigationBar> <StyledNavigationScrollbars ref={scrollbarsRef}> - <Flex $flexDirection="column" $flex={1}> - <PlatformSpecificSplitTunnelingSettings - setBrowsing={setBrowsing} - scrollToTop={scrollToTop} - /> - </Flex> + <PlatformSpecificSplitTunnelingSettings + setBrowsing={setBrowsing} + scrollToTop={scrollToTop} + /> </StyledNavigationScrollbars> </NavigationContainer> </SettingsContainer> @@ -315,9 +313,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro needFullDiskPermissions, setSplitTunnelingState, } = useAppContext(); - const splitTunnelingEnabledValue = useSelector( - (state: IReduxState) => state.settings.splitTunneling, - ); + const splitTunnelingEnabled = useSelector((state: IReduxState) => state.settings.splitTunneling); const splitTunnelingApplications = useSelector( (state: IReduxState) => state.settings.splitTunnelingApplications, ); @@ -325,15 +321,18 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro const [searchTerm, setSearchTerm] = useState(''); const [applications, setApplications] = useState<ISplitTunnelingApplication[]>(); + const [loadingDiskPermissions, setLoadingDiskPermissions] = useState(false); const [splitTunnelingAvailable, setSplitTunnelingAvailable] = useState( window.env.platform === 'darwin' ? undefined : true, ); - const splitTunnelingEnabled = splitTunnelingEnabledValue && (splitTunnelingAvailable ?? false); + const canEditSplitTunneling = splitTunnelingEnabled && (splitTunnelingAvailable ?? false); const fetchNeedFullDiskPermissions = useCallback(async () => { + setLoadingDiskPermissions(true); const needPermissions = await needFullDiskPermissions(); setSplitTunnelingAvailable(!needPermissions); + setLoadingDiskPermissions(false); }, [needFullDiskPermissions]); useEffect((): void | (() => void) => { @@ -375,12 +374,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro const addApplication = useCallback( async (application: ISplitTunnelingApplication | string) => { - if (!splitTunnelingEnabled) { + if (!canEditSplitTunneling) { await setSplitTunnelingState(true); } await addSplitTunnelingApplication(application); }, - [addSplitTunnelingApplication, splitTunnelingEnabled, setSplitTunnelingState], + [addSplitTunnelingApplication, canEditSplitTunneling, setSplitTunnelingState], ); const addBrowsedForApplication = useCallback( @@ -403,12 +402,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro const removeApplication = useCallback( async (application: ISplitTunnelingApplication) => { - if (!splitTunnelingEnabled) { + if (!canEditSplitTunneling) { await setSplitTunnelingState(true); } removeSplitTunnelingApplication(application); }, - [removeSplitTunnelingApplication, setSplitTunnelingState, splitTunnelingEnabled], + [removeSplitTunnelingApplication, setSplitTunnelingState, canEditSplitTunneling], ); const filePickerCallback = useFilePicker( @@ -440,9 +439,9 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro [addApplication, forgetManuallyAddedApplicationAndUpdate], ); - const showSplitSection = splitTunnelingEnabled && filteredSplitApplications.length > 0; + const showSplitSection = canEditSplitTunneling && filteredSplitApplications.length > 0; const showNonSplitSection = - splitTunnelingEnabled && + canEditSplitTunneling && (!filteredNonSplitApplications || filteredNonSplitApplications.length > 0); const excludedTitle = ( @@ -462,26 +461,37 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro <HeaderTitle>{strings.splitTunneling}</HeaderTitle> <Switch isOn={splitTunnelingEnabled} - disabled={!splitTunnelingAvailable} + disabled={ + !splitTunnelingEnabled && (!splitTunnelingAvailable || loadingDiskPermissions) + } onChange={setSplitTunnelingState} /> </Flex> - <MacOsSplitTunnelingAvailability - needFullDiskPermissions={ - window.env.platform === 'darwin' && splitTunnelingAvailable === false - } - /> - {splitTunnelingAvailable ? ( - <HeaderSubTitle> - {messages.pgettext( - 'split-tunneling-view', - 'Choose the apps you want to exclude from the VPN tunnel.', + {!loadingDiskPermissions && ( + <> + <MacOsSplitTunnelingAvailability + needFullDiskPermissions={ + window.env.platform === 'darwin' && splitTunnelingAvailable === false + } + /> + {splitTunnelingAvailable && ( + <HeaderSubTitle> + {messages.pgettext( + 'split-tunneling-view', + 'Choose the apps you want to exclude from the VPN tunnel.', + )} + </HeaderSubTitle> )} - </HeaderSubTitle> - ) : null} + </> + )} </SettingsHeader> + {loadingDiskPermissions && ( + <Flex $justifyContent="center" $margin={{ top: Spacings.spacing6 }}> + <ImageView source="icon-spinner" height={48} /> + </Flex> + )} - {splitTunnelingEnabled && ( + {canEditSplitTunneling && ( <StyledSearchBar searchTerm={searchTerm} onSearch={setSearchTerm} /> )} @@ -505,7 +515,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro </Cell.Section> </Accordion> - {splitTunnelingEnabled && searchTerm !== '' && !showSplitSection && !showNonSplitSection && ( + {canEditSplitTunneling && searchTerm !== '' && !showSplitSection && !showNonSplitSection && ( <StyledNoResult> <StyledNoResultText> {formatHtml( @@ -516,7 +526,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro </StyledNoResult> )} - {splitTunnelingEnabled && ( + {canEditSplitTunneling && ( <StyledBrowseButton onClick={addWithFilePicker}> {messages.pgettext('split-tunneling-view', 'Find another app')} </StyledBrowseButton> diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts index af82748d7b..42e550c466 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/error.ts @@ -13,6 +13,7 @@ import { InAppNotification, InAppNotificationAction, InAppNotificationProvider, + InAppNotificationTroubleshootButton, SystemNotification, SystemNotificationCategory, SystemNotificationProvider, @@ -23,6 +24,7 @@ interface ErrorNotificationContext { tunnelState: TunnelState; hasExcludedApps: boolean; showFullDiskAccessSettings?: () => void; + disableSplitTunneling?: () => void; } export class ErrorNotificationProvider @@ -276,12 +278,18 @@ export class ErrorNotificationProvider }, }; } else if (errorState.cause === ErrorStateCause.needFullDiskPermissions) { - let troubleshootButtons = undefined; + let troubleshootButtons: InAppNotificationTroubleshootButton[] | undefined = undefined; if (this.context.showFullDiskAccessSettings) { troubleshootButtons = [ { label: messages.pgettext('troubleshoot', 'Open system settings'), action: () => this.context.showFullDiskAccessSettings?.(), + variant: 'success', + }, + { + label: messages.pgettext('troubleshoot', 'Disable split tunneling'), + action: () => this.context.disableSplitTunneling?.(), + variant: 'destructive', }, ]; } diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts index 921a029a06..48561c555b 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/notification.ts @@ -16,6 +16,7 @@ export interface InAppNotificationTroubleshootInfo { export interface InAppNotificationTroubleshootButton { label: string; action: () => void; + variant?: 'primary' | 'success' | 'destructive'; } export type InAppNotificationAction = |
