summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2024-05-28 15:41:34 +0200
committerOskar Nyberg <oskar@mullvad.net>2024-05-28 15:41:34 +0200
commit2b04fed8d6a486d97af47f1add45b0eeb1071db8 (patch)
tree0d53951c8d6939526d41173269cba9d57e58b611 /gui/src/renderer
parentb6db0f3487d5096e08b077c3d806c96b50aecf35 (diff)
parent58556c0da8a4f3abc441d18c9dd0c026b1986dfb (diff)
downloadmullvadvpn-2b04fed8d6a486d97af47f1add45b0eeb1071db8.tar.xz
mullvadvpn-2b04fed8d6a486d97af47f1add45b0eeb1071db8.zip
Merge branch 'add-macos-split-tunneling-gui-des-786'
Diffstat (limited to 'gui/src/renderer')
-rw-r--r--gui/src/renderer/app.tsx30
-rw-r--r--gui/src/renderer/components/Settings.tsx4
-rw-r--r--gui/src/renderer/components/SplitTunnelingSettings.tsx80
-rw-r--r--gui/src/renderer/components/Switch.tsx2
-rw-r--r--gui/src/renderer/components/TransitionContainer.tsx1
-rw-r--r--gui/src/renderer/components/cell/Selector.tsx2
-rw-r--r--gui/src/renderer/components/cell/SettingsSelect.tsx1
-rw-r--r--gui/src/renderer/redux/settings/actions.ts6
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts4
-rw-r--r--gui/src/renderer/redux/userinterface/actions.ts16
-rw-r--r--gui/src/renderer/redux/userinterface/reducers.ts8
11 files changed, 92 insertions, 62 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 92c4cb5698..3aa73698dc 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -4,7 +4,10 @@ import { bindActionCreators } from 'redux';
import { StyleSheetManager } from 'styled-components';
import { closeToExpiry, hasExpired } from '../shared/account-expiry';
-import { ILinuxSplitTunnelingApplication, IWindowsApplication } from '../shared/application-types';
+import {
+ ILinuxSplitTunnelingApplication,
+ ISplitTunnelingApplication,
+} from '../shared/application-types';
import {
AccessMethodSetting,
AccountToken,
@@ -185,7 +188,7 @@ export default class AppRenderer {
this.storeAutoStart(autoStart);
});
- IpcRendererEventChannel.windowsSplitTunneling.listen((applications: IWindowsApplication[]) => {
+ IpcRendererEventChannel.splitTunneling.listen((applications: ISplitTunnelingApplication[]) => {
this.reduxActions.settings.setSplitTunnelingApplications(applications);
});
@@ -242,6 +245,7 @@ export default class AppRenderer {
this.storeAutoStart(initialState.autoStart);
this.setChangelog(initialState.changelog, initialState.forceShowChanges);
this.setCurrentApiAccessMethod(initialState.currentApiAccessMethod);
+ this.reduxActions.userInterface.setIsMacOs13OrNewer(initialState.isMacOs13OrNewer);
if (initialState.macOsScrollbarVisibility !== undefined) {
this.reduxActions.userInterface.setMacOsScrollbarVisibility(
@@ -258,9 +262,9 @@ export default class AppRenderer {
this.checkContentHeight(true);
});
- if (initialState.windowsSplitTunnelingApplications) {
+ if (initialState.splitTunnelingApplications) {
this.reduxActions.settings.setSplitTunnelingApplications(
- initialState.windowsSplitTunnelingApplications,
+ initialState.splitTunnelingApplications,
);
}
@@ -334,11 +338,11 @@ export default class AppRenderer {
public launchExcludedApplication = (application: ILinuxSplitTunnelingApplication | string) =>
IpcRendererEventChannel.linuxSplitTunneling.launchApplication(application);
public setSplitTunnelingState = (state: boolean) =>
- IpcRendererEventChannel.windowsSplitTunneling.setState(state);
- public addSplitTunnelingApplication = (application: string | IWindowsApplication) =>
- IpcRendererEventChannel.windowsSplitTunneling.addApplication(application);
- public forgetManuallyAddedSplitTunnelingApplication = (application: IWindowsApplication) =>
- IpcRendererEventChannel.windowsSplitTunneling.forgetManuallyAddedApplication(application);
+ IpcRendererEventChannel.splitTunneling.setState(state);
+ public addSplitTunnelingApplication = (application: string | ISplitTunnelingApplication) =>
+ IpcRendererEventChannel.splitTunneling.addApplication(application);
+ public forgetManuallyAddedSplitTunnelingApplication = (application: ISplitTunnelingApplication) =>
+ IpcRendererEventChannel.splitTunneling.forgetManuallyAddedApplication(application);
public setObfuscationSettings = (obfuscationSettings: ObfuscationSettings) =>
IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings);
public setDaitaSettings = (daitaSettings: IDaitaSettings) =>
@@ -513,12 +517,12 @@ export default class AppRenderer {
return IpcRendererEventChannel.autoStart.set(autoStart);
};
- public getWindowsSplitTunnelingApplications(updateCache = false) {
- return IpcRendererEventChannel.windowsSplitTunneling.getApplications(updateCache);
+ public getSplitTunnelingApplications(updateCache = false) {
+ return IpcRendererEventChannel.splitTunneling.getApplications(updateCache);
}
- public removeSplitTunnelingApplication(application: IWindowsApplication) {
- void IpcRendererEventChannel.windowsSplitTunneling.removeApplication(application);
+ public removeSplitTunnelingApplication(application: ISplitTunnelingApplication) {
+ void IpcRendererEventChannel.splitTunneling.removeApplication(application);
}
public async showLaunchDaemonSettings() {
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index 8f68a1c462..34fb3eb0c1 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -26,8 +26,10 @@ export default function Support() {
const loginState = useSelector((state) => state.account.status);
const connectedToDaemon = useSelector((state) => state.userInterface.connectedToDaemon);
+ const isMacOs13OrNewer = useSelector((state) => state.userInterface.isMacOs13OrNewer);
const showSubSettings = loginState.type === 'ok' && connectedToDaemon;
+ const showSplitTunneling = window.env.platform !== 'darwin' || isMacOs13OrNewer;
return (
<BackAction action={history.pop}>
@@ -59,7 +61,7 @@ export default function Support() {
<VpnSettingsButton />
</Cell.Group>
- {(window.env.platform === 'linux' || window.env.platform === 'win32') && (
+ {showSplitTunneling && (
<Cell.Group>
<SplitTunnelingButton />
</Cell.Group>
diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx
index cb4de70deb..2cee823b3e 100644
--- a/gui/src/renderer/components/SplitTunnelingSettings.tsx
+++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx
@@ -6,7 +6,7 @@ import { colors, strings } from '../../config.json';
import {
IApplication,
ILinuxSplitTunnelingApplication,
- IWindowsApplication,
+ ISplitTunnelingApplication,
} from '../../shared/application-types';
import { messages } from '../../shared/gettext';
import { useAppContext } from '../context';
@@ -91,10 +91,8 @@ function PlatformSpecificSplitTunnelingSettings(props: IPlatformSplitTunnelingSe
switch (window.env.platform) {
case 'linux':
return <LinuxSplitTunnelingSettings {...props} />;
- case 'win32':
- return <WindowsSplitTunnelingSettings {...props} />;
default:
- throw new Error(`Split tunneling not implemented on ${window.env.platform}`);
+ return <SplitTunnelingSettings {...props} />;
}
}
@@ -300,12 +298,12 @@ function LinuxApplicationRow(props: ILinuxApplicationRowProps) {
);
}
-export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsProps) {
+export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsProps) {
const {
addSplitTunnelingApplication,
removeSplitTunnelingApplication,
forgetManuallyAddedSplitTunnelingApplication,
- getWindowsSplitTunnelingApplications,
+ getSplitTunnelingApplications,
setSplitTunnelingState,
} = useAppContext();
const splitTunnelingEnabled = useSelector((state: IReduxState) => state.settings.splitTunneling);
@@ -314,13 +312,13 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
);
const [searchTerm, setSearchTerm] = useState('');
- const [applications, setApplications] = useState<IWindowsApplication[]>();
+ const [applications, setApplications] = useState<ISplitTunnelingApplication[]>();
useAsyncEffect(async () => {
- const { fromCache, applications } = await getWindowsSplitTunnelingApplications();
+ const { fromCache, applications } = await getSplitTunnelingApplications();
setApplications(applications);
if (fromCache) {
- const { applications } = await getWindowsSplitTunnelingApplications(true);
+ const { applications } = await getSplitTunnelingApplications(true);
setApplications(applications);
}
}, []);
@@ -345,7 +343,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
}, [applications, splitTunnelingApplications, searchTerm]);
const addApplication = useCallback(
- async (application: IWindowsApplication | string) => {
+ async (application: ISplitTunnelingApplication | string) => {
if (!splitTunnelingEnabled) {
await setSplitTunnelingState(true);
}
@@ -354,26 +352,26 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
[addSplitTunnelingApplication, splitTunnelingEnabled, setSplitTunnelingState],
);
- const addApplicationAndUpdate = useCallback(
- async (application: IWindowsApplication | string) => {
+ const addBrowsedForApplication = useCallback(
+ async (application: string) => {
await addApplication(application);
- const { applications } = await getWindowsSplitTunnelingApplications();
+ const { applications } = await getSplitTunnelingApplications();
setApplications(applications);
},
- [addApplication, getWindowsSplitTunnelingApplications],
+ [addApplication, getSplitTunnelingApplications],
);
const forgetManuallyAddedApplicationAndUpdate = useCallback(
- async (application: IWindowsApplication) => {
+ async (application: ISplitTunnelingApplication) => {
await forgetManuallyAddedSplitTunnelingApplication(application);
- const { applications } = await getWindowsSplitTunnelingApplications();
+ const { applications } = await getSplitTunnelingApplications();
setApplications(applications);
},
- [forgetManuallyAddedSplitTunnelingApplication, getWindowsSplitTunnelingApplications],
+ [forgetManuallyAddedSplitTunnelingApplication, getSplitTunnelingApplications],
);
const removeApplication = useCallback(
- async (application: IWindowsApplication) => {
+ async (application: ISplitTunnelingApplication) => {
if (!splitTunnelingEnabled) {
await setSplitTunnelingState(true);
}
@@ -385,8 +383,8 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
const filePickerCallback = useFilePicker(
messages.pgettext('split-tunneling-view', 'Add'),
props.setBrowsing,
- addApplicationAndUpdate,
- { name: 'Executables', extensions: ['exe', 'lnk'] },
+ addBrowsedForApplication,
+ getFilePickerOptionsForPlatform(),
);
const addWithFilePicker = useCallback(async () => {
@@ -395,21 +393,17 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
}, [filePickerCallback, props.scrollToTop]);
const excludedRowRenderer = useCallback(
- (application: IWindowsApplication) => (
- <WindowsApplicationRow application={application} onRemove={removeApplication} />
+ (application: ISplitTunnelingApplication) => (
+ <ApplicationRow application={application} onRemove={removeApplication} />
),
[removeApplication],
);
const includedRowRenderer = useCallback(
- (application: IWindowsApplication) => {
+ (application: ISplitTunnelingApplication) => {
const onForget = application.deletable ? forgetManuallyAddedApplicationAndUpdate : undefined;
return (
- <WindowsApplicationRow
- application={application}
- onAdd={addApplication}
- onDelete={onForget}
- />
+ <ApplicationRow application={application} onAdd={addApplication} onDelete={onForget} />
);
},
[addApplication, forgetManuallyAddedApplicationAndUpdate],
@@ -452,6 +446,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
<Accordion expanded={showSplitSection}>
<Cell.Section sectionTitle={excludedTitle}>
<ApplicationList
+ data-testid="split-applications"
applications={filteredSplitApplications}
rowRenderer={excludedRowRenderer}
/>
@@ -461,6 +456,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
<Accordion expanded={showNonSplitSection}>
<Cell.Section sectionTitle={allTitle}>
<ApplicationList
+ data-testid="non-split-applications"
applications={filteredNonSplitApplications}
rowRenderer={includedRowRenderer}
/>
@@ -490,6 +486,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett
interface IApplicationListProps<T extends IApplication> {
applications: T[] | undefined;
rowRenderer: (application: T) => React.ReactElement;
+ 'data-testid'?: string;
}
function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>) {
@@ -501,8 +498,11 @@ function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>
);
} else {
return (
- <StyledListContainer>
- <List items={props.applications} getKey={applicationGetKey}>
+ <StyledListContainer data-testid={props['data-testid']}>
+ <List
+ data-testid={props['data-testid']}
+ items={props.applications.sort((a, b) => a.name.localeCompare(b.name))}
+ getKey={applicationGetKey}>
{props.rowRenderer}
</List>
</StyledListContainer>
@@ -514,14 +514,14 @@ function applicationGetKey<T extends IApplication>(application: T): string {
return application.absolutepath;
}
-interface IWindowsApplicationRowProps {
- application: IWindowsApplication;
- onAdd?: (application: IWindowsApplication) => void;
- onRemove?: (application: IWindowsApplication) => void;
- onDelete?: (application: IWindowsApplication) => void;
+interface IApplicationRowProps {
+ application: ISplitTunnelingApplication;
+ onAdd?: (application: ISplitTunnelingApplication) => void;
+ onRemove?: (application: ISplitTunnelingApplication) => void;
+ onDelete?: (application: ISplitTunnelingApplication) => void;
}
-function WindowsApplicationRow(props: IWindowsApplicationRowProps) {
+function ApplicationRow(props: IApplicationRowProps) {
const onAdd = useCallback(() => {
props.onAdd?.(props.application);
}, [props.onAdd, props.application]);
@@ -576,3 +576,11 @@ function WindowsApplicationRow(props: IWindowsApplicationRowProps) {
function includesSearchTerm(application: IApplication, searchTerm: string) {
return application.name.toLowerCase().includes(searchTerm.toLowerCase());
}
+
+function getFilePickerOptionsForPlatform():
+ | { name: string; extensions: Array<string> }
+ | undefined {
+ return window.env.platform === 'win32'
+ ? { name: 'Executables', extensions: ['exe', 'lnk'] }
+ : undefined;
+}
diff --git a/gui/src/renderer/components/Switch.tsx b/gui/src/renderer/components/Switch.tsx
index f87bf2a52b..595bc422a2 100644
--- a/gui/src/renderer/components/Switch.tsx
+++ b/gui/src/renderer/components/Switch.tsx
@@ -5,9 +5,7 @@ import { colors } from '../../config.json';
interface IProps {
id?: string;
- // eslint-disable-next-line @typescript-eslint/naming-convention
'aria-labelledby'?: string;
- // eslint-disable-next-line @typescript-eslint/naming-convention
'aria-describedby'?: string;
isOn: boolean;
onChange?: (isOn: boolean) => void;
diff --git a/gui/src/renderer/components/TransitionContainer.tsx b/gui/src/renderer/components/TransitionContainer.tsx
index 9773c64512..3e7007e3c8 100644
--- a/gui/src/renderer/components/TransitionContainer.tsx
+++ b/gui/src/renderer/components/TransitionContainer.tsx
@@ -48,7 +48,6 @@ interface StyledTransitionContentProps {
export const StyledTransitionContent = styled.div.attrs<
StyledTransitionContentProps,
- // eslint-disable-next-line @typescript-eslint/naming-convention
{ 'data-testid': string }
>({
'data-testid': 'transition-content',
diff --git a/gui/src/renderer/components/cell/Selector.tsx b/gui/src/renderer/components/cell/Selector.tsx
index 5e5d685a59..6bfeeb3887 100644
--- a/gui/src/renderer/components/cell/Selector.tsx
+++ b/gui/src/renderer/components/cell/Selector.tsx
@@ -16,7 +16,6 @@ export interface SelectorItem<T> {
label: string;
value: T;
disabled?: boolean;
- // eslint-disable-next-line @typescript-eslint/naming-convention
'data-testid'?: string;
}
@@ -136,7 +135,6 @@ interface SelectorCellProps<T> {
onSelect: (value: T) => void;
children: React.ReactNode | Array<React.ReactNode>;
forwardedRef?: React.Ref<HTMLButtonElement>;
- // eslint-disable-next-line @typescript-eslint/naming-convention
'data-testid'?: string;
}
diff --git a/gui/src/renderer/components/cell/SettingsSelect.tsx b/gui/src/renderer/components/cell/SettingsSelect.tsx
index 4286cda1e2..f0d918ded6 100644
--- a/gui/src/renderer/components/cell/SettingsSelect.tsx
+++ b/gui/src/renderer/components/cell/SettingsSelect.tsx
@@ -87,7 +87,6 @@ interface SettingsSelectProps<T extends string> {
items: Array<SettingsSelectItem<T>>;
onUpdate: (value: T) => void;
direction?: 'down' | 'up';
- // eslint-disable-next-line @typescript-eslint/naming-convention
'data-testid'?: string;
}
diff --git a/gui/src/renderer/redux/settings/actions.ts b/gui/src/renderer/redux/settings/actions.ts
index ba6e4ce5a8..d2a3fb1c4a 100644
--- a/gui/src/renderer/redux/settings/actions.ts
+++ b/gui/src/renderer/redux/settings/actions.ts
@@ -1,4 +1,4 @@
-import { IWindowsApplication } from '../../../shared/application-types';
+import { ISplitTunnelingApplication } from '../../../shared/application-types';
import {
AccessMethodSetting,
ApiAccessMethodSettings,
@@ -100,7 +100,7 @@ export interface IUpdateSplitTunnelingStateAction {
export interface ISetSplitTunnelingApplicationsAction {
type: 'SET_SPLIT_TUNNELING_APPLICATIONS';
- applications: IWindowsApplication[];
+ applications: ISplitTunnelingApplication[];
}
export interface ISetObfuscationSettings {
@@ -281,7 +281,7 @@ function updateSplitTunnelingState(enabled: boolean): IUpdateSplitTunnelingState
}
function setSplitTunnelingApplications(
- applications: IWindowsApplication[],
+ applications: ISplitTunnelingApplication[],
): ISetSplitTunnelingApplicationsAction {
return {
type: 'SET_SPLIT_TUNNELING_APPLICATIONS',
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index 18873eea20..eae413fe22 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -1,5 +1,5 @@
import { getDefaultApiAccessMethods } from '../../../main/default-settings';
-import { IWindowsApplication } from '../../../shared/application-types';
+import { ISplitTunnelingApplication } from '../../../shared/application-types';
import {
AccessMethodSetting,
ApiAccessMethodSettings,
@@ -115,7 +115,7 @@ export interface ISettingsReduxState {
};
dns: IDnsOptions;
splitTunneling: boolean;
- splitTunnelingApplications: IWindowsApplication[];
+ splitTunnelingApplications: ISplitTunnelingApplication[];
obfuscationSettings: ObfuscationSettings;
customLists: CustomLists;
apiAccessMethods: ApiAccessMethodSettings;
diff --git a/gui/src/renderer/redux/userinterface/actions.ts b/gui/src/renderer/redux/userinterface/actions.ts
index cc43990980..238835318e 100644
--- a/gui/src/renderer/redux/userinterface/actions.ts
+++ b/gui/src/renderer/redux/userinterface/actions.ts
@@ -56,6 +56,11 @@ export interface ISetSelectLocationView {
selectLocationView: LocationType;
}
+export interface ISetIsMacOs13OrNewer {
+ type: 'SET_IS_MACOS13_OR_NEWER';
+ isMacOs13OrNewer: boolean;
+}
+
export type UserInterfaceAction =
| IUpdateLocaleAction
| IUpdateWindowArrowPositionAction
@@ -67,7 +72,8 @@ export type UserInterfaceAction =
| ISetChangelog
| ISetForceShowChanges
| ISetIsPerformingPostUpgrade
- | ISetSelectLocationView;
+ | ISetSelectLocationView
+ | ISetIsMacOs13OrNewer;
function updateLocale(locale: string): IUpdateLocaleAction {
return {
@@ -147,6 +153,13 @@ function setSelectLocationView(selectLocationView: LocationType): ISetSelectLoca
};
}
+function setIsMacOs13OrNewer(isMacOs13OrNewer: boolean): ISetIsMacOs13OrNewer {
+ return {
+ type: 'SET_IS_MACOS13_OR_NEWER',
+ isMacOs13OrNewer,
+ };
+}
+
export default {
updateLocale,
updateWindowArrowPosition,
@@ -159,4 +172,5 @@ export default {
setForceShowChanges,
setIsPerformingPostUpgrade,
setSelectLocationView,
+ setIsMacOs13OrNewer,
};
diff --git a/gui/src/renderer/redux/userinterface/reducers.ts b/gui/src/renderer/redux/userinterface/reducers.ts
index 622b7814aa..89427e9b06 100644
--- a/gui/src/renderer/redux/userinterface/reducers.ts
+++ b/gui/src/renderer/redux/userinterface/reducers.ts
@@ -15,6 +15,7 @@ export interface IUserInterfaceReduxState {
forceShowChanges: boolean;
isPerformingPostUpgrade: boolean;
selectLocationView: LocationType;
+ isMacOs13OrNewer: boolean;
}
const initialState: IUserInterfaceReduxState = {
@@ -28,6 +29,7 @@ const initialState: IUserInterfaceReduxState = {
forceShowChanges: false,
isPerformingPostUpgrade: false,
selectLocationView: LocationType.exit,
+ isMacOs13OrNewer: true,
};
export default function (
@@ -80,6 +82,12 @@ export default function (
selectLocationView: action.selectLocationView,
};
+ case 'SET_IS_MACOS13_OR_NEWER':
+ return {
+ ...state,
+ isMacOs13OrNewer: action.isMacOs13OrNewer,
+ };
+
default:
return state;
}