Skip to content

Commit 110e037

Browse files
committedFeb 15, 2024
Merge branch 'add-server-ip-override-ui-des-422'
2 parents d42287a + 3edd659 commit 110e037

30 files changed

+845
-52
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Line wrap the file at 100 chars. Th
3232
circumvent censorship by proxying API traffic.
3333
- Add confirmation dialog when deleting a custom list.
3434
- Add support for custom SOCKS5 OpenVPN bridges running locally.
35+
- Add ability to import server IP overrides in GUI.
3536

3637
#### Android
3738
- Add support for all screen orientations.

‎gui/assets/images/icon-checkmark.svg

+3
Loading

‎gui/assets/images/icon-cross.svg

+3
Loading

‎gui/locales/messages.pot

+78
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ msgstr ""
145145
msgid "Got it!"
146146
msgstr ""
147147

148+
msgid "Import"
149+
msgstr ""
150+
148151
msgid "IPv4"
149152
msgstr ""
150153

@@ -1324,6 +1327,77 @@ msgctxt "select-location-view"
13241327
msgid "While connected, your traffic will be routed through two secure locations, the entry point and the exit point (needs to be two different VPN servers)."
13251328
msgstr ""
13261329

1330+
msgctxt "settings-import"
1331+
msgid "Clear all overrides"
1332+
msgstr ""
1333+
1334+
msgctxt "settings-import"
1335+
msgid "Clear all overrides?"
1336+
msgstr ""
1337+
1338+
msgctxt "settings-import"
1339+
msgid "Clearing the imported overrides changes the server IPs, in the Select location view, back to default."
1340+
msgstr ""
1341+
1342+
msgctxt "settings-import"
1343+
msgid "If you are having issues connecting to VPN servers, please contact support."
1344+
msgstr ""
1345+
1346+
msgctxt "settings-import"
1347+
msgid "Import file"
1348+
msgstr ""
1349+
1350+
msgctxt "settings-import"
1351+
msgid "Import files or text with new IP addresses for the servers in the Select location view."
1352+
msgstr ""
1353+
1354+
msgctxt "settings-import"
1355+
msgid "Import of file %(fileName)s was successful, overrides are now active."
1356+
msgstr ""
1357+
1358+
msgctxt "settings-import"
1359+
msgid "Import of file %(fileName)s was unsuccessful, please try again."
1360+
msgstr ""
1361+
1362+
msgctxt "settings-import"
1363+
msgid "Import of text was successful, overrides are now active."
1364+
msgstr ""
1365+
1366+
msgctxt "settings-import"
1367+
msgid "Import of text was unsuccessful, please try again."
1368+
msgstr ""
1369+
1370+
msgctxt "settings-import"
1371+
msgid "IMPORT SUCCESSFUL"
1372+
msgstr ""
1373+
1374+
#. Title label in navigation bar
1375+
msgctxt "settings-import"
1376+
msgid "Import via text"
1377+
msgstr ""
1378+
1379+
msgctxt "settings-import"
1380+
msgid "NO OVERRIDES IMPORTED"
1381+
msgstr ""
1382+
1383+
msgctxt "settings-import"
1384+
msgid "On some networks, where various types of censorship are being used, our server IP addresses are sometimes blocked."
1385+
msgstr ""
1386+
1387+
msgctxt "settings-import"
1388+
msgid "OVERRIDES ACTIVE"
1389+
msgstr ""
1390+
1391+
#. Title label in navigation bar. This is for a feature that lets
1392+
#. users import server IP settings.
1393+
msgctxt "settings-import"
1394+
msgid "Server IP override"
1395+
msgstr ""
1396+
1397+
msgctxt "settings-import"
1398+
msgid "To circumvent this you can import a file or a text, provided by our support team, with new IP addresses that override the default addresses of the servers in the Select location view."
1399+
msgstr ""
1400+
13271401
#. Navigation button to the 'API access methods' view
13281402
msgctxt "settings-view"
13291403
msgid "API access"
@@ -1768,6 +1842,10 @@ msgctxt "vpn-settings-view"
17681842
msgid "Malware"
17691843
msgstr ""
17701844

1845+
msgctxt "vpn-settings-view"
1846+
msgid "Server IP override"
1847+
msgstr ""
1848+
17711849
#. Label for settings that enables block of social media.
17721850
msgctxt "vpn-settings-view"
17731851
msgid "Social media"

‎gui/src/main/daemon-rpc.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,14 @@ export class DaemonRpc {
697697
return result.getValue();
698698
}
699699

700+
public async applyJsonSettings(settings: string): Promise<void> {
701+
await this.callString(this.client.applyJsonSettings, settings);
702+
}
703+
704+
public async clearAllRelayOverrides(): Promise<void> {
705+
await this.callEmpty(this.client.clearAllRelayOverrides);
706+
}
707+
700708
private subscriptionId(): number {
701709
const current = this.nextSubscriptionId;
702710
this.nextSubscriptionId += 1;
@@ -711,11 +719,14 @@ export class DaemonRpc {
711719
return Date.now() + CHANNEL_STATE_TIMEOUT;
712720
}
713721

714-
private callEmpty<R>(fn: CallFunctionArgument<Empty, R>): Promise<R> {
722+
private callEmpty<R = Empty>(fn: CallFunctionArgument<Empty, R>): Promise<R> {
715723
return this.call<Empty, R>(fn, new Empty());
716724
}
717725

718-
private callString<R>(fn: CallFunctionArgument<StringValue, R>, value?: string): Promise<R> {
726+
private callString<R = Empty>(
727+
fn: CallFunctionArgument<StringValue, R>,
728+
value?: string,
729+
): Promise<R> {
719730
const googleString = new StringValue();
720731

721732
if (value !== undefined) {
@@ -1164,6 +1175,7 @@ function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefine
11641175
const obfuscationSettings = convertFromObfuscationSettings(settingsObject.obfuscationSettings);
11651176
const customLists = convertFromCustomListSettings(settings.getCustomLists());
11661177
const apiAccessMethods = convertFromApiAccessMethodSettings(settings.getApiAccessMethods()!);
1178+
const relayOverrides = settingsObject.relayOverridesList;
11671179
return {
11681180
...settings.toObject(),
11691181
bridgeState,
@@ -1174,6 +1186,7 @@ function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefine
11741186
obfuscationSettings,
11751187
customLists,
11761188
apiAccessMethods,
1189+
relayOverrides,
11771190
};
11781191
}
11791192

‎gui/src/main/default-settings.ts

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export function getDefaultSettings(): ISettings {
7777
},
7878
customLists: [],
7979
apiAccessMethods: getDefaultApiAccessMethods(),
80+
relayOverrides: [],
8081
};
8182
}
8283

‎gui/src/main/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,9 @@ class ApplicationMain
822822
await shell.openExternal(url);
823823
}
824824
});
825+
IpcMainEventChannel.app.handleGetPathBaseName((filePath) =>
826+
Promise.resolve(path.basename(filePath)),
827+
);
825828

826829
IpcMainEventChannel.navigation.handleSetHistory((history) => {
827830
this.navigationHistory = history;

‎gui/src/main/settings.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import fs from 'fs/promises';
2+
13
import BridgeSettingsBuilder from '../shared/bridge-settings-builder';
24
import { ISettings } from '../shared/daemon-rpc-types';
35
import { ICurrentAppVersionInfo } from '../shared/ipc-types';
@@ -90,6 +92,17 @@ export default class Settings implements Readonly<ISettings> {
9092
return this.daemonRpc.testCustomApiAccessMethod(method);
9193
});
9294

95+
IpcMainEventChannel.settings.handleClearAllRelayOverrides(() => {
96+
return this.daemonRpc.clearAllRelayOverrides();
97+
});
98+
IpcMainEventChannel.settings.handleImportText((text) => {
99+
return this.daemonRpc.applyJsonSettings(text);
100+
});
101+
IpcMainEventChannel.settings.handleImportFile(async (path) => {
102+
const settings = await fs.readFile(path);
103+
return this.daemonRpc.applyJsonSettings(settings.toString());
104+
});
105+
93106
IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => {
94107
this.guiSettings.enableSystemNotifications = flag;
95108
});
@@ -160,6 +173,9 @@ export default class Settings implements Readonly<ISettings> {
160173
public get apiAccessMethods() {
161174
return this.settingsValue.apiAccessMethods;
162175
}
176+
public get relayOverrides() {
177+
return this.settingsValue.relayOverrides;
178+
}
163179

164180
public get gui() {
165181
return this.guiSettings;

‎gui/src/main/user-interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export default class UserInterface implements WindowControllerDelegate {
6969
...options,
7070
});
7171
this.browsingFiles = false;
72+
this.showWindow();
7273
return response;
7374
});
7475

‎gui/src/renderer/app.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ export default class AppRenderer {
345345
public viewLog = (path: string) => IpcRendererEventChannel.problemReport.viewLog(path);
346346
public quit = () => IpcRendererEventChannel.app.quit();
347347
public openUrl = (url: string) => IpcRendererEventChannel.app.openUrl(url);
348+
public getPathBaseName = (path: string) => IpcRendererEventChannel.app.getPathBaseName(path);
348349
public showOpenDialog = (options: Electron.OpenDialogOptions) =>
349350
IpcRendererEventChannel.app.showOpenDialog(options);
350351
public createCustomList = (name: string) =>
@@ -365,6 +366,9 @@ export default class AppRenderer {
365366
IpcRendererEventChannel.settings.testApiAccessMethodById(id);
366367
public testCustomApiAccessMethod = (method: CustomProxy) =>
367368
IpcRendererEventChannel.settings.testCustomApiAccessMethod(method);
369+
public importSettingsFile = (path: string) => IpcRendererEventChannel.settings.importFile(path);
370+
public importSettingsText = (text: string) => IpcRendererEventChannel.settings.importText(text);
371+
public clearAllRelayOverrides = () => IpcRendererEventChannel.settings.clearAllRelayOverrides();
368372
public getMapData = () => IpcRendererEventChannel.map.getData();
369373
public setAnimateMap = (displayMap: boolean): void =>
370374
IpcRendererEventChannel.guiSettings.setAnimateMap(displayMap);
@@ -812,6 +816,7 @@ export default class AppRenderer {
812816
reduxSettings.updateObfuscationSettings(newSettings.obfuscationSettings);
813817
reduxSettings.updateCustomLists(newSettings.customLists);
814818
reduxSettings.updateApiAccessMethods(newSettings.apiAccessMethods);
819+
reduxSettings.updateRelayOverrides(newSettings.relayOverrides);
815820

816821
this.setReduxRelaySettings(newSettings.relaySettings);
817822
this.setBridgeSettings(newSettings.bridgeSettings);

‎gui/src/renderer/components/ApiAccessMethods.tsx

+24-24
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import InfoButton from './InfoButton';
2424
import { BackAction } from './KeyboardNavigation';
2525
import { Layout, SettingsContainer } from './Layout';
2626
import { ModalAlert, ModalAlertType } from './Modal';
27-
import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar';
27+
import {
28+
NavigationBar,
29+
NavigationContainer,
30+
NavigationInfoButton,
31+
NavigationItems,
32+
TitleBarItem,
33+
} from './NavigationBar';
2834
import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader';
2935
import { StyledContent, StyledNavigationScrollbars, StyledSettingsContent } from './SettingsStyles';
3036
import { SmallButton, SmallButtonColor, SmallButtonGroup } from './SmallButton';
@@ -33,10 +39,6 @@ const StyledContextMenuButton = styled(Cell.Icon)({
3339
marginRight: '8px',
3440
});
3541

36-
const StyledTitleInfoButton = styled(InfoButton)({
37-
marginLeft: '12px',
38-
});
39-
4042
const StyledMethodInfoButton = styled(InfoButton)({
4143
marginRight: '11px',
4244
});
@@ -90,31 +92,29 @@ export default function ApiAccessMethods() {
9092
messages.pgettext('navigation-bar', 'API access')
9193
}
9294
</TitleBarItem>
95+
<NavigationInfoButton
96+
message={[
97+
messages.pgettext(
98+
'api-access-methods-view',
99+
'The app needs to communicate with a Mullvad API server to log you in, fetch server lists, and other critical operations.',
100+
),
101+
messages.pgettext(
102+
'api-access-methods-view',
103+
'On some networks, where various types of censorship are being used, the API servers might not be directly reachable.',
104+
),
105+
messages.pgettext(
106+
'api-access-methods-view',
107+
'This feature allows you to circumvent that censorship by adding custom ways to access the API via proxies and similar methods.',
108+
),
109+
]}
110+
/>
93111
</NavigationItems>
94112
</NavigationBar>
95113

96114
<StyledNavigationScrollbars fillContainer>
97115
<StyledContent>
98116
<SettingsHeader>
99-
<HeaderTitle>
100-
{messages.pgettext('navigation-bar', 'API access')}
101-
<StyledTitleInfoButton
102-
message={[
103-
messages.pgettext(
104-
'api-access-methods-view',
105-
'The app needs to communicate with a Mullvad API server to log you in, fetch server lists, and other critical operations.',
106-
),
107-
messages.pgettext(
108-
'api-access-methods-view',
109-
'On some networks, where various types of censorship are being used, the API servers might not be directly reachable.',
110-
),
111-
messages.pgettext(
112-
'api-access-methods-view',
113-
'This feature allows you to circumvent that censorship by adding custom ways to access the API via proxies and similar methods.',
114-
),
115-
]}
116-
/>
117-
</HeaderTitle>
117+
<HeaderTitle>{messages.pgettext('navigation-bar', 'API access')}</HeaderTitle>
118118
<HeaderSubTitle>
119119
{messages.pgettext(
120120
'api-access-methods-view',

‎gui/src/renderer/components/AppRouter.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import OpenVpnSettings from './OpenVpnSettings';
2626
import ProblemReport from './ProblemReport';
2727
import SelectLanguage from './SelectLanguage';
2828
import Settings from './Settings';
29+
import SettingsImport from './SettingsImport';
30+
import SettingsTextImport from './SettingsTextImport';
2931
import SplitTunnelingSettings from './SplitTunnelingSettings';
3032
import Support from './Support';
3133
import TooManyDevices from './TooManyDevices';
@@ -83,6 +85,8 @@ export default function AppRouter() {
8385
<Route exact path={RoutePath.openVpnSettings} component={OpenVpnSettings} />
8486
<Route exact path={RoutePath.splitTunneling} component={SplitTunnelingSettings} />
8587
<Route exact path={RoutePath.apiAccessMethods} component={ApiAccessMethods} />
88+
<Route exact path={RoutePath.settingsImport} component={SettingsImport} />
89+
<Route exact path={RoutePath.settingsTextImport} component={SettingsTextImport} />
8690
<Route exact path={RoutePath.editApiAccessMethods} component={EditApiAccessMethod} />
8791
<Route exact path={RoutePath.support} component={Support} />
8892
<Route exact path={RoutePath.problemReport} component={ProblemReport} />

‎gui/src/renderer/components/InfoButton.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,33 @@ const StyledInfoButton = styled.button({
1818
interface IInfoIconProps {
1919
className?: string;
2020
size?: number;
21+
tintColor?: string;
22+
tintHoverColor?: string;
2123
}
2224

2325
export function InfoIcon(props: IInfoIconProps) {
2426
return (
2527
<ImageView
2628
source="icon-info"
2729
width={props.size ?? 18}
28-
tintColor={colors.white}
29-
tintHoverColor={colors.white80}
30+
tintColor={props.tintColor ?? colors.white}
31+
tintHoverColor={props.tintHoverColor ?? colors.white80}
3032
className={props.className}
3133
/>
3234
);
3335
}
3436

35-
interface IInfoButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
37+
export interface IInfoButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
3638
message?: string | Array<string>;
3739
children?: React.ReactNode;
40+
title?: string;
3841
size?: number;
42+
tintColor?: string;
43+
tintHoverColor?: string;
3944
}
4045

4146
export default function InfoButton(props: IInfoButtonProps) {
42-
const { message, children, size, ...otherProps } = props;
47+
const { message, children, size, tintColor, tintHoverColor, ...otherProps } = props;
4348
const [isOpen, show, hide] = useBoolean(false);
4449

4550
return (
@@ -48,10 +53,11 @@ export default function InfoButton(props: IInfoButtonProps) {
4853
onClick={show}
4954
aria-label={messages.pgettext('accessibility', 'More information')}
5055
{...otherProps}>
51-
<InfoIcon size={size} />
56+
<InfoIcon size={size} tintColor={tintColor} tintHoverColor={tintHoverColor} />
5257
</StyledInfoButton>
5358
<ModalAlert
5459
isOpen={isOpen}
60+
title={props.title}
5561
message={props.message}
5662
type={ModalAlertType.info}
5763
buttons={[

0 commit comments

Comments
 (0)
Failed to load comments.