summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-12-14 16:38:34 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-01-03 13:48:03 +0100
commit41185171995cc427c2a4021dbe4056bfd5c3939c (patch)
tree9dbfb7608ffe2f6cd7fc7174f6c42753edcd4015
parent436aa6ccc94fcc2a3f4eefc1e3bf922594746fc4 (diff)
downloadmullvadvpn-41185171995cc427c2a4021dbe4056bfd5c3939c.tar.xz
mullvadvpn-41185171995cc427c2a4021dbe4056bfd5c3939c.zip
Disable server that is used as other endpoint
-rw-r--r--gui/src/renderer/app.tsx31
-rw-r--r--gui/src/renderer/components/BridgeLocations.tsx2
-rw-r--r--gui/src/renderer/components/LocationList.tsx192
-rw-r--r--gui/src/renderer/components/LocationRow.tsx6
-rw-r--r--gui/src/renderer/components/Locations.tsx5
-rw-r--r--gui/src/renderer/components/SelectLocation.tsx24
-rw-r--r--gui/src/renderer/containers/SelectLocationPage.tsx1
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts2
8 files changed, 222 insertions, 41 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index a7eb2f3be3..0419bb0919 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -10,7 +10,7 @@ import { AppContext } from './context';
import accountActions from './redux/account/actions';
import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
-import { IRelayLocationRedux, IWgKey } from './redux/settings/reducers';
+import { IWgKey } from './redux/settings/reducers';
import configureStore from './redux/store';
import userInterfaceActions from './redux/userinterface/actions';
import versionActions from './redux/version/actions';
@@ -33,7 +33,6 @@ import {
IAppVersionInfo,
IDnsOptions,
ILocation,
- IRelayList,
ISettings,
IWireguardPublicKey,
KeygenEvent,
@@ -88,7 +87,6 @@ export default class AppRenderer {
userInterface: bindActionCreators(userInterfaceActions, this.reduxStore.dispatch),
};
- private locale = 'en';
private location?: Partial<ILocation>;
private lastDisconnectedLocation?: Partial<ILocation>;
private relayListPair!: IRelayListPair;
@@ -597,7 +595,6 @@ export default class AppRenderer {
}
private setLocale(locale: string) {
- this.locale = locale;
this.reduxActions.userInterface.updateLocale(locale);
}
@@ -837,36 +834,14 @@ export default class AppRenderer {
}
}
- private convertRelayListToLocationList(relayList: IRelayList): IRelayLocationRedux[] {
- return relayList.countries
- .map((country) => ({
- name: country.name,
- code: country.code,
- hasActiveRelays: country.cities.some((city) => city.relays.some((relay) => relay.active)),
- cities: country.cities
- .map((city) => ({
- name: city.name,
- code: city.code,
- latitude: city.latitude,
- longitude: city.longitude,
- hasActiveRelays: city.relays.some((relay) => relay.active),
- relays: city.relays.sort((relayA, relayB) =>
- relayA.hostname.localeCompare(relayB.hostname, this.locale, { numeric: true }),
- ),
- }))
- .sort((cityA, cityB) => cityA.name.localeCompare(cityB.name, this.locale)),
- }))
- .sort((countryA, countryB) => countryA.name.localeCompare(countryB.name, this.locale));
- }
-
private setRelayListPair(relayListPair: IRelayListPair) {
this.relayListPair = relayListPair;
this.propagateRelayListPairToRedux();
}
private propagateRelayListPairToRedux() {
- const relays = this.convertRelayListToLocationList(this.relayListPair.relays);
- const bridges = this.convertRelayListToLocationList(this.relayListPair.bridges);
+ const relays = this.relayListPair.relays.countries;
+ const bridges = this.relayListPair.bridges.countries;
this.reduxActions.settings.updateRelayLocations(relays);
this.reduxActions.settings.updateBridgeLocations(bridges);
diff --git a/gui/src/renderer/components/BridgeLocations.tsx b/gui/src/renderer/components/BridgeLocations.tsx
index 802797eb77..a62368e166 100644
--- a/gui/src/renderer/components/BridgeLocations.tsx
+++ b/gui/src/renderer/components/BridgeLocations.tsx
@@ -17,6 +17,7 @@ export enum SpecialBridgeLocationType {
interface IBridgeLocationsProps {
source: IRelayLocationRedux[];
+ locale: string;
defaultExpandedLocations?: RelayLocation[];
selectedValue?: LiftedConstraint<RelayLocation>;
selectedElementRef?: React.Ref<React.ReactInstance>;
@@ -53,6 +54,7 @@ const BridgeLocations = React.forwardRef(function BridgeLocationsT(
</SpecialLocations>
<RelayLocations
source={props.source}
+ locale={props.locale}
onWillExpand={props.onWillExpand}
onTransitionEnd={props.onTransitionEnd}
/>
diff --git a/gui/src/renderer/components/LocationList.tsx b/gui/src/renderer/components/LocationList.tsx
index 6389784ecd..479d547bf8 100644
--- a/gui/src/renderer/components/LocationList.tsx
+++ b/gui/src/renderer/components/LocationList.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { sprintf } from 'sprintf-js';
import styled from 'styled-components';
import { colors } from '../../config.json';
import {
@@ -7,8 +8,12 @@ import {
RelayLocation,
relayLocationComponents,
} from '../../shared/daemon-rpc-types';
-import { relayLocations } from '../../shared/gettext';
-import { IRelayLocationRedux } from '../redux/settings/reducers';
+import { messages, relayLocations } from '../../shared/gettext';
+import {
+ IRelayLocationRedux,
+ IRelayLocationCityRedux,
+ IRelayLocationRelayRedux,
+} from '../redux/settings/reducers';
import * as Cell from './cell';
import LocationRow from './LocationRow';
@@ -257,11 +262,19 @@ export class SpecialLocation<T> extends React.Component<ISpecialLocationProps<T>
};
}
+export enum DisabledReason {
+ entry,
+ exit,
+ inactive,
+}
+
interface IRelayLocationsProps {
source: IRelayLocationRedux[];
+ locale: string;
selectedLocation?: RelayLocation;
selectedElementRef?: React.Ref<React.ReactInstance>;
expandedItems?: RelayLocation[];
+ disabledLocation?: { location: RelayLocation; reason: DisabledReason };
onSelect?: (location: RelayLocation) => void;
onExpand?: (location: RelayLocation, expand: boolean) => void;
onWillExpand?: (locationRect: DOMRect, expandedContentHeight: number) => void;
@@ -278,14 +291,15 @@ export class RelayLocations extends React.PureComponent<IRelayLocationsProps> {
public render() {
return (
<>
- {this.props.source.map((relayCountry) => {
+ {this.prepareRelaysForPresentation(this.props.source).map((relayCountry) => {
const countryLocation: RelayLocation = { country: relayCountry.code };
return (
<LocationRow
key={getLocationKey(countryLocation)}
- name={relayLocations.gettext(relayCountry.name)}
- active={relayCountry.hasActiveRelays}
+ name={relayCountry.label}
+ active={relayCountry.active}
+ disabled={relayCountry.disabled}
expanded={this.isExpanded(countryLocation)}
onSelect={this.handleSelection}
onExpand={this.handleExpand}
@@ -300,8 +314,9 @@ export class RelayLocations extends React.PureComponent<IRelayLocationsProps> {
return (
<LocationRow
key={getLocationKey(cityLocation)}
- name={relayLocations.gettext(relayCity.name)}
- active={relayCity.hasActiveRelays}
+ name={relayCity.label}
+ active={relayCity.active}
+ disabled={relayCity.disabled}
expanded={this.isExpanded(cityLocation)}
onSelect={this.handleSelection}
onExpand={this.handleExpand}
@@ -316,8 +331,9 @@ export class RelayLocations extends React.PureComponent<IRelayLocationsProps> {
return (
<LocationRow
key={getLocationKey(relayLocation)}
- name={relay.hostname}
+ name={relay.label}
active={relay.active}
+ disabled={relay.disabled}
onSelect={this.handleSelection}
{...this.getCommonCellProps(relayLocation)}
/>
@@ -333,6 +349,166 @@ export class RelayLocations extends React.PureComponent<IRelayLocationsProps> {
);
}
+ private prepareRelaysForPresentation(relayList: IRelayLocationRedux[]) {
+ return relayList
+ .map((country) => {
+ const countryDisabled = this.isCountryDisabled(country, country.code);
+ const countryLocation = { country: country.code };
+
+ return {
+ ...country,
+ label: this.formatRowName(country.name, countryLocation, countryDisabled),
+ active: countryDisabled !== DisabledReason.inactive,
+ disabled: countryDisabled !== undefined,
+ cities: country.cities
+ .map((city) => {
+ const cityDisabled =
+ countryDisabled ?? this.isCityDisabled(city, [country.code, city.code]);
+ const cityLocation: RelayLocation = { city: [country.code, city.code] };
+
+ return {
+ ...city,
+ label: this.formatRowName(city.name, cityLocation, cityDisabled),
+ active: cityDisabled !== DisabledReason.inactive,
+ disabled: cityDisabled !== undefined,
+ relays: city.relays
+ .map((relay) => {
+ const relayDisabled =
+ countryDisabled ??
+ cityDisabled ??
+ this.isRelayDisabled(relay, [country.code, city.code, relay.hostname]);
+ const relayLocation: RelayLocation = {
+ hostname: [country.code, city.code, relay.hostname],
+ };
+
+ return {
+ ...relay,
+ label: this.formatRowName(relay.hostname, relayLocation, relayDisabled),
+ disabled: relayDisabled !== undefined,
+ };
+ })
+ .sort((a, b) =>
+ a.hostname.localeCompare(b.hostname, this.props.locale, { numeric: true }),
+ ),
+ };
+ })
+ .sort((a, b) => a.name.localeCompare(b.name, this.props.locale)),
+ };
+ })
+ .sort((a, b) => a.name.localeCompare(b.name, this.props.locale));
+ }
+
+ private formatRowName(
+ name: string,
+ location: RelayLocation,
+ disabledReason?: DisabledReason,
+ ): string {
+ const translatedName = 'hostname' in location ? name : relayLocations.gettext(name);
+ const disabledLocation = this.props.disabledLocation;
+ const matchDisabledLocation = compareRelayLocationLoose(location, disabledLocation?.location);
+
+ let info: string | undefined;
+ if (
+ disabledReason === DisabledReason.entry ||
+ (matchDisabledLocation && disabledLocation?.reason === DisabledReason.entry)
+ ) {
+ info = messages.pgettext('select-location-view', 'Entry');
+ } else if (
+ disabledReason === DisabledReason.exit ||
+ (matchDisabledLocation && disabledLocation?.reason === DisabledReason.exit)
+ ) {
+ info = messages.pgettext('select-location-view', 'Exit');
+ }
+
+ return info !== undefined
+ ? sprintf(messages.pgettext('select-location-view', '%(location)s (%(info)s)'), {
+ location: translatedName,
+ info,
+ })
+ : translatedName;
+ }
+
+ private isRelayDisabled(
+ relay: IRelayLocationRelayRedux,
+ location: [string, string, string],
+ ): DisabledReason | undefined {
+ if (!relay.active) {
+ return DisabledReason.inactive;
+ } else if (
+ this.props.disabledLocation &&
+ compareRelayLocation({ hostname: location }, this.props.disabledLocation.location)
+ ) {
+ return this.props.disabledLocation.reason;
+ } else {
+ return undefined;
+ }
+ }
+
+ private isCityDisabled(
+ city: IRelayLocationCityRedux,
+ location: [string, string],
+ ): DisabledReason | undefined {
+ const relaysDisabled = city.relays.map((relay) =>
+ this.isRelayDisabled(relay, [...location, relay.hostname]),
+ );
+ if (relaysDisabled.every((status) => status === DisabledReason.inactive)) {
+ return DisabledReason.inactive;
+ }
+
+ const disabledDueToSelection = relaysDisabled.find(
+ (status) => status === DisabledReason.entry || status === DisabledReason.exit,
+ );
+
+ if (
+ relaysDisabled.every((status) => status !== undefined) &&
+ disabledDueToSelection !== undefined
+ ) {
+ return disabledDueToSelection;
+ }
+
+ if (
+ this.props.disabledLocation &&
+ compareRelayLocation({ city: location }, this.props.disabledLocation.location) &&
+ city.relays.filter((relay) => relay.active).length <= 1
+ ) {
+ return this.props.disabledLocation.reason;
+ }
+
+ return undefined;
+ }
+
+ private isCountryDisabled(
+ country: IRelayLocationRedux,
+ location: string,
+ ): DisabledReason | undefined {
+ const citiesDisabled = country.cities.map((city) =>
+ this.isCityDisabled(city, [location, city.code]),
+ );
+ if (citiesDisabled.every((status) => status === DisabledReason.inactive)) {
+ return DisabledReason.inactive;
+ }
+
+ const disabledDueToSelection = citiesDisabled.find(
+ (status) => status === DisabledReason.entry || status === DisabledReason.exit,
+ );
+ if (
+ citiesDisabled.every((status) => status !== undefined) &&
+ disabledDueToSelection !== undefined
+ ) {
+ return disabledDueToSelection;
+ }
+
+ if (
+ this.props.disabledLocation &&
+ compareRelayLocation({ country: location }, this.props.disabledLocation.location) &&
+ country.cities.flatMap((city) => city.relays).filter((relay) => relay.active).length <= 1
+ ) {
+ return this.props.disabledLocation.reason;
+ }
+
+ return undefined;
+ }
+
private isExpanded(relayLocation: RelayLocation) {
return (this.props.expandedItems || []).some((location) =>
compareRelayLocation(location, relayLocation),
diff --git a/gui/src/renderer/components/LocationRow.tsx b/gui/src/renderer/components/LocationRow.tsx
index cf7bc5942c..980ffaae3e 100644
--- a/gui/src/renderer/components/LocationRow.tsx
+++ b/gui/src/renderer/components/LocationRow.tsx
@@ -67,6 +67,7 @@ const Label = styled(Cell.Label)({
interface IProps {
name: string;
active: boolean;
+ disabled: boolean;
location: RelayLocation;
selected: boolean;
expanded?: boolean;
@@ -105,13 +106,13 @@ function LocationRow(props: IProps, ref: React.Ref<HTMLDivElement>) {
<Container
ref={ref}
selected={props.selected}
- disabled={!props.active}
+ disabled={props.disabled}
location={props.location}>
<Button
ref={buttonRef}
onClick={handleClick}
location={props.location}
- disabled={!props.active}>
+ disabled={props.disabled}>
<RelayStatusIndicator active={props.active} selected={props.selected} />
<Label>{props.name}</Label>
</Button>
@@ -149,6 +150,7 @@ function compareProps(oldProps: IProps, nextProps: IProps): boolean {
React.Children.count(oldProps.children) === React.Children.count(nextProps.children) &&
oldProps.name === nextProps.name &&
oldProps.active === nextProps.active &&
+ oldProps.disabled === nextProps.disabled &&
oldProps.selected === nextProps.selected &&
oldProps.expanded === nextProps.expanded &&
oldProps.onSelect === nextProps.onSelect &&
diff --git a/gui/src/renderer/components/Locations.tsx b/gui/src/renderer/components/Locations.tsx
index 2de2f6072c..901d27c9ec 100644
--- a/gui/src/renderer/components/Locations.tsx
+++ b/gui/src/renderer/components/Locations.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import { RelayLocation } from '../../shared/daemon-rpc-types';
import { IRelayLocationRedux } from '../redux/settings/reducers';
import LocationList, {
+ DisabledReason,
LocationSelection,
LocationSelectionType,
RelayLocations,
@@ -9,8 +10,10 @@ import LocationList, {
interface ILocationsProps {
source: IRelayLocationRedux[];
+ locale: string;
defaultExpandedLocations?: RelayLocation[];
selectedValue?: RelayLocation;
+ disabledLocation?: { location: RelayLocation; reason: DisabledReason };
selectedElementRef?: React.Ref<React.ReactInstance>;
onSelect?: (value: LocationSelection<never>) => void;
onWillExpand?: (locationRect: DOMRect, expandedContentHeight: number) => void;
@@ -31,6 +34,8 @@ function Locations(props: ILocationsProps, ref: React.Ref<LocationList<never>>)
onSelect={props.onSelect}>
<RelayLocations
source={props.source}
+ locale={props.locale}
+ disabledLocation={props.disabledLocation}
onWillExpand={props.onWillExpand}
onTransitionEnd={props.onTransitionEnd}
/>
diff --git a/gui/src/renderer/components/SelectLocation.tsx b/gui/src/renderer/components/SelectLocation.tsx
index 902ca27c80..a2e5375e54 100644
--- a/gui/src/renderer/components/SelectLocation.tsx
+++ b/gui/src/renderer/components/SelectLocation.tsx
@@ -9,7 +9,11 @@ import { CustomScrollbarsRef } from './CustomScrollbars';
import { EntryLocations, ExitLocations } from './Locations';
import ImageView from './ImageView';
import { Layout } from './Layout';
-import LocationList, { LocationSelection, LocationSelectionType } from './LocationList';
+import LocationList, {
+ DisabledReason,
+ LocationSelection,
+ LocationSelectionType,
+} from './LocationList';
import {
CloseBarItem,
NavigationBar,
@@ -36,6 +40,7 @@ import {
import { HeaderSubTitle, HeaderTitle } from './SettingsHeader';
interface IProps {
+ locale: string;
selectedExitLocation?: RelayLocation;
selectedEntryLocation?: RelayLocation;
selectedBridgeLocation?: LiftedConstraint<RelayLocation>;
@@ -283,26 +288,42 @@ export default class SelectLocation extends React.Component<IProps, IState> {
private renderLocationList() {
if (this.state.locationScope === LocationScope.exit) {
+ const disabledLocation = this.props.selectedEntryLocation
+ ? {
+ location: this.props.selectedEntryLocation,
+ reason: DisabledReason.entry,
+ }
+ : undefined;
return (
<ExitLocations
ref={this.exitLocationList}
source={this.props.relayLocations}
+ locale={this.props.locale}
defaultExpandedLocations={this.getExpandedLocationsFromSnapshot()}
selectedValue={this.props.selectedExitLocation}
selectedElementRef={this.selectedExitLocationRef}
+ disabledLocation={disabledLocation}
onSelect={this.onSelectExitLocation}
onWillExpand={this.onWillExpand}
onTransitionEnd={this.resetHeight}
/>
);
} else if (this.props.tunnelProtocol === 'any' || this.props.tunnelProtocol === 'wireguard') {
+ const disabledLocation = this.props.selectedExitLocation
+ ? {
+ location: this.props.selectedExitLocation,
+ reason: DisabledReason.exit,
+ }
+ : undefined;
return (
<EntryLocations
ref={this.entryLocationList}
source={this.props.relayLocations}
+ locale={this.props.locale}
defaultExpandedLocations={this.getExpandedLocationsFromSnapshot()}
selectedValue={this.props.selectedEntryLocation}
selectedElementRef={this.selectedEntryLocationRef}
+ disabledLocation={disabledLocation}
onSelect={this.onSelectEntryLocation}
onWillExpand={this.onWillExpand}
onTransitionEnd={this.resetHeight}
@@ -313,6 +334,7 @@ export default class SelectLocation extends React.Component<IProps, IState> {
<BridgeLocations
ref={this.bridgeLocationList}
source={this.props.bridgeLocations}
+ locale={this.props.locale}
defaultExpandedLocations={this.getExpandedLocationsFromSnapshot()}
selectedValue={this.props.selectedBridgeLocation}
selectedElementRef={this.selectedBridgeLocationRef}
diff --git a/gui/src/renderer/containers/SelectLocationPage.tsx b/gui/src/renderer/containers/SelectLocationPage.tsx
index a4f78f87a5..1effeac473 100644
--- a/gui/src/renderer/containers/SelectLocationPage.tsx
+++ b/gui/src/renderer/containers/SelectLocationPage.tsx
@@ -45,6 +45,7 @@ const mapStateToProps = (state: IReduxState, props: IHistoryProps & IAppContext)
const providers = 'normal' in relaySettings ? relaySettings.normal.providers : [];
return {
+ locale: state.userInterface.locale,
selectedExitLocation,
selectedEntryLocation,
selectedBridgeLocation,
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index 986954b5ff..5ac19cadf6 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -64,14 +64,12 @@ export interface IRelayLocationCityRedux {
code: string;
latitude: number;
longitude: number;
- hasActiveRelays: boolean;
relays: IRelayLocationRelayRedux[];
}
export interface IRelayLocationRedux {
name: string;
code: string;
- hasActiveRelays: boolean;
cities: IRelayLocationCityRedux[];
}