diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-12-14 16:38:34 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-01-03 13:48:03 +0100 |
| commit | 41185171995cc427c2a4021dbe4056bfd5c3939c (patch) | |
| tree | 9dbfb7608ffe2f6cd7fc7174f6c42753edcd4015 | |
| parent | 436aa6ccc94fcc2a3f4eefc1e3bf922594746fc4 (diff) | |
| download | mullvadvpn-41185171995cc427c2a4021dbe4056bfd5c3939c.tar.xz mullvadvpn-41185171995cc427c2a4021dbe4056bfd5c3939c.zip | |
Disable server that is used as other endpoint
| -rw-r--r-- | gui/src/renderer/app.tsx | 31 | ||||
| -rw-r--r-- | gui/src/renderer/components/BridgeLocations.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/LocationList.tsx | 192 | ||||
| -rw-r--r-- | gui/src/renderer/components/LocationRow.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/Locations.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/SelectLocation.tsx | 24 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SelectLocationPage.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/redux/settings/reducers.ts | 2 |
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[]; } |
