summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-10-16 11:31:17 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-10-16 11:31:17 -0300
commitdf0022a3977fb6ee7557773a03aa1c85e2c4d63f (patch)
tree62683bb3df6af7b6d9b680306eb7471c85ef8484 /gui
parent43dfaeddf5e70c8cb667b5385eba1645553a5c3e (diff)
parent12abe68cb03edb0342f9ed7d412aa6e299c632cc (diff)
downloadmullvadvpn-df0022a3977fb6ee7557773a03aa1c85e2c4d63f.tar.xz
mullvadvpn-df0022a3977fb6ee7557773a03aa1c85e2c4d63f.zip
Merge branch 'in-out-ip-in-gui'
Diffstat (limited to 'gui')
-rw-r--r--gui/packages/components/src/ConnectionInfo.tsx105
-rw-r--r--gui/packages/components/src/index.ts1
-rw-r--r--gui/packages/desktop/src/renderer/app.js14
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js307
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js46
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js10
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js4
7 files changed, 337 insertions, 150 deletions
diff --git a/gui/packages/components/src/ConnectionInfo.tsx b/gui/packages/components/src/ConnectionInfo.tsx
new file mode 100644
index 0000000000..b22c510c23
--- /dev/null
+++ b/gui/packages/components/src/ConnectionInfo.tsx
@@ -0,0 +1,105 @@
+import * as React from 'react';
+import { Component, Styles, Text, View } from 'reactxp';
+import { default as Accordion } from './Accordion';
+
+const styles = {
+ toggle: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 14,
+ fontWeight: '800',
+ color: 'rgb(255, 255, 255, 0.4)',
+ paddingBottom: 2,
+ }),
+ content: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ fontWeight: '800',
+ color: 'rgb(255, 255, 255)',
+ paddingBottom: 2,
+ }),
+};
+
+interface IInAddress {
+ ip: string;
+ port: number;
+ protocol: string;
+}
+
+interface IOutAddress {
+ ipv4: string | null;
+ ipv6: string | null;
+}
+
+interface IProps {
+ inAddress: IInAddress;
+ outAddress: IOutAddress;
+ startExpanded: boolean;
+ onToggle: (expanded: boolean) => void;
+}
+
+interface IState {
+ expanded: boolean;
+}
+
+export default class ConnectionInfo extends Component<IProps, IState> {
+ public static defaultProps = {
+ inAddress: {
+ ip: null,
+ port: null,
+ protocol: null,
+ },
+ outAddress: null,
+ startExpanded: false,
+ onToggle: (_: boolean) => {},
+ };
+
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ expanded: props.startExpanded,
+ };
+ }
+
+ public render() {
+ return (
+ <View>
+ <Accordion height={this.state.expanded ? 'auto' : 0}>
+ <Text style={styles.content}>{this.inAddress()}</Text>
+ <Text style={styles.content}>{this.outAddress()}</Text>
+ </Accordion>
+ <Text style={styles.toggle} onPress={() => this.toggle()}>
+ {this.state.expanded ? 'LESS' : 'MORE'}
+ </Text>
+ </View>
+ );
+ }
+
+ private toggle() {
+ this.setState((state, props) => {
+ const expanded = !state.expanded;
+
+ props.onToggle(expanded);
+
+ return { expanded };
+ });
+ }
+
+ private inAddress() {
+ const { ip, port, protocol } = this.props.inAddress;
+
+ return (
+ 'IN: ' + (ip || '<unknown>') + (port ? `:${port}` : '') + (protocol ? ` - ${protocol}` : '')
+ );
+ }
+
+ private outAddress() {
+ const { ipv4, ipv6 } = this.props.outAddress;
+
+ if (ipv4 || ipv6) {
+ return `OUT: ${ipv4}` + (ipv6 ? ` / ${ipv6}` : '');
+ } else {
+ return '';
+ }
+ }
+}
diff --git a/gui/packages/components/src/index.ts b/gui/packages/components/src/index.ts
index 7d5ad156f0..648ddabdf9 100644
--- a/gui/packages/components/src/index.ts
+++ b/gui/packages/components/src/index.ts
@@ -1,3 +1,4 @@
export { default as Accordion } from './Accordion';
export { default as ClipboardLabel } from './ClipboardLabel';
+export { default as ConnectionInfo } from './ConnectionInfo';
export { default as SecuredLabel, SecuredDisplayStyle } from './SecuredLabel';
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 1ef54ca4e9..2ae7b66f9c 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -229,7 +229,7 @@ export default class AppRenderer {
if (tunnelState.state === 'disconnected' || tunnelState.state === 'blocked') {
// switch to connecting state immediately to prevent a lag that may be caused by RPC
// communication delay
- actions.connection.connecting();
+ actions.connection.connecting(null);
await this._daemonRpc.connectTunnel();
}
@@ -478,12 +478,6 @@ export default class AppRenderer {
await this._setStartView();
try {
- await this._fetchLocation();
- } catch (error) {
- log.error(`Cannot fetch the location: ${error.message}`);
- }
-
- try {
await this._fetchLatestVersionInfo();
} catch (error) {
log.error(`Cannot fetch the latest version information: ${error.message}`);
@@ -602,7 +596,7 @@ export default class AppRenderer {
}
async _updateUserLocation(tunnelState: TunnelState) {
- if (tunnelState === 'connecting' || tunnelState === 'disconnected') {
+ if (['connected', 'connecting', 'disconnected'].includes(tunnelState)) {
try {
await this._fetchLocation();
} catch (error) {
@@ -616,11 +610,11 @@ export default class AppRenderer {
switch (stateTransition.state) {
case 'connecting':
- actions.connection.connecting();
+ actions.connection.connecting(stateTransition.details);
break;
case 'connected':
- actions.connection.connected();
+ actions.connection.connected(stateTransition.details);
break;
case 'disconnecting':
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index a50227b289..3602410c86 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -3,7 +3,7 @@
import moment from 'moment';
import * as React from 'react';
import { Component, Text, View, Types } from 'reactxp';
-import { SecuredLabel, SecuredDisplayStyle } from '@mullvad/components';
+import { ConnectionInfo, SecuredLabel, SecuredDisplayStyle } from '@mullvad/components';
import { Layout, Container, Header } from './Layout';
import { SettingsBarButton, Brand } from './HeaderBar';
import NotificationArea from './NotificationArea';
@@ -136,6 +136,19 @@ export default class Connect extends Component<Props> {
}
renderMap() {
+ const tunnelState = this.props.connection.status.state;
+ const details = this.props.connection.status.details;
+
+ let relayIp = null;
+ let relayPort = null;
+ let relayProtocol = null;
+
+ if ((tunnelState === 'connecting' || tunnelState === 'connected') && details) {
+ relayIp = details.address;
+ relayPort = details.tunnel.openvpn.port;
+ relayProtocol = details.tunnel.openvpn.protocol;
+ }
+
return (
<View style={styles.connect}>
<View style={styles.map}>
@@ -143,19 +156,23 @@ export default class Connect extends Component<Props> {
</View>
<View style={styles.container}>
{/* show spinner when connecting */}
- {this.props.connection.status.state === 'connecting' ? (
+ {tunnelState === 'connecting' ? (
<View style={styles.status_icon}>
<Img source="icon-spinner" height={60} width={60} alt="" />
</View>
) : null}
<TunnelControl
- style={styles.tunnel_control}
tunnelState={this.props.connection.status.state}
selectedRelayName={this.props.selectedRelayName}
city={this.props.connection.city}
country={this.props.connection.country}
hostname={this.props.connection.hostname}
+ relayIp={relayIp}
+ relayPort={relayPort}
+ relayProtocol={relayProtocol}
+ outIpv4={this.props.connection.ip}
+ outIpv6={null}
onConnect={this.props.onConnect}
onDisconnect={this.props.onDisconnect}
onSelectLocation={this.props.onSelectLocation}
@@ -220,144 +237,190 @@ type TunnelControlProps = {
city: ?string,
country: ?string,
hostname: ?string,
+ relayIp: ?string,
+ relayPort: ?number,
+ relayProtocol: ?string,
+ outIpv4: ?string,
+ outIpv6: ?string,
onConnect: () => void,
onDisconnect: () => void,
onSelectLocation: () => void,
- style: Types.ViewStyleRuleSet,
};
-export function TunnelControl(props: TunnelControlProps) {
- const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
- const City = () => <Text style={styles.status_location_text}>{props.city}</Text>;
- const Country = () => <Text style={styles.status_location_text}>{props.country}</Text>;
- const Hostname = () => <Text style={styles.status_hostname}>{props.hostname || ''}</Text>;
+type TunnelControlState = {
+ showConnectionInfo: boolean,
+};
- const SwitchLocation = () => {
- return (
+class TunnelControl extends Component<TunnelControlProps, TunnelControlState> {
+ state = {
+ showConnectionInfo: false,
+ };
+
+ render() {
+ const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
+ const City = () => <Text style={styles.status_location_text}>{this.props.city}</Text>;
+ const Country = () => <Text style={styles.status_location_text}>{this.props.country}</Text>;
+ const Hostname = () => <Text style={styles.status_hostname}>{this.props.hostname || ''}</Text>;
+
+ const SwitchLocation = () => {
+ return (
+ <AppButton.TransparentButton
+ style={styles.switch_location_button}
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{'Switch location'}</AppButton.Label>
+ </AppButton.TransparentButton>
+ );
+ };
+
+ const SelectedLocation = () => (
<AppButton.TransparentButton
style={styles.switch_location_button}
- onPress={props.onSelectLocation}>
- <AppButton.Label>{'Switch location'}</AppButton.Label>
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{this.props.selectedRelayName}</AppButton.Label>
+ <Img height={12} width={7} source="icon-chevron" />
</AppButton.TransparentButton>
);
- };
- const SelectedLocation = () => (
- <AppButton.TransparentButton
- style={styles.switch_location_button}
- onPress={props.onSelectLocation}>
- <AppButton.Label>{props.selectedRelayName}</AppButton.Label>
- <Img height={12} width={7} source="icon-chevron" />
- </AppButton.TransparentButton>
- );
+ const Connect = () => (
+ <AppButton.GreenButton onPress={this.props.onConnect}>
+ {'Secure my connection'}
+ </AppButton.GreenButton>
+ );
- const Connect = () => (
- <AppButton.GreenButton onPress={props.onConnect}>
- {'Secure my connection'}
- </AppButton.GreenButton>
- );
+ const Disconnect = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Disconnect'}
+ </AppButton.RedTransparentButton>
+ );
- const Disconnect = () => (
- <AppButton.RedTransparentButton onPress={props.onDisconnect}>
- {'Disconnect'}
- </AppButton.RedTransparentButton>
- );
+ const Cancel = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Cancel'}
+ </AppButton.RedTransparentButton>
+ );
- const Cancel = () => (
- <AppButton.RedTransparentButton onPress={props.onDisconnect}>
- {'Cancel'}
- </AppButton.RedTransparentButton>
- );
+ const Secured = ({ displayStyle }) => (
+ <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
+ );
+ const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
- const Secured = ({ displayStyle }) => (
- <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
- );
- const Wrapper = ({ children }) => <View style={props.style}>{children}</View>;
- const Body = ({ children }) => <View style={styles.body}>{children}</View>;
- const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
+ const connectionInfoProps = {
+ inAddress: {
+ ip: this.props.relayIp,
+ port: this.props.relayPort,
+ protocol: this.props.relayProtocol,
+ },
+ outAddress: {
+ ipv4: this.props.outIpv4,
+ ipv6: this.props.outIpv6,
+ },
+ startExpanded: this.state.showConnectionInfo,
+ onToggle: (expanded) => {
+ this.setState({ showConnectionInfo: expanded });
+ },
+ };
- switch (props.tunnelState) {
- case 'connecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.securing} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
- case 'connected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- </Body>
- <Footer>
- <SwitchLocation />
- <Disconnect />
- </Footer>
- </Wrapper>
- );
+ switch (this.props.tunnelState) {
+ case 'connecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.securing} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ <Hostname />
+ <ConnectionInfo {...connectionInfoProps} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
+ case 'connected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ <Hostname />
+ <ConnectionInfo {...connectionInfoProps} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Disconnect />
+ </Footer>
+ </Wrapper>
+ );
- case 'blocked':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.blocked} />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
+ case 'blocked':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.blocked} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
- case 'disconnecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
+ case 'disconnecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
- case 'disconnected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.unsecured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
+ case 'disconnected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.unsecured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
+
+ default:
+ throw new Error(`Unknown TunnelState: ${(this.props.tunnelState: empty)}`);
+ }
+ }
+}
+
+type ContainerProps = {
+ children?: Types.ReactNode,
+};
- default:
- throw new Error(`Unknown TunnelState: ${(props.tunnelState: empty)}`);
+class Wrapper extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.tunnel_control}>{this.props.children}</View>;
+ }
+}
+
+class Body extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.body}>{this.props.children}</View>;
}
}
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
index 572954b26b..594a33fdf6 100644
--- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
+++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
@@ -25,6 +25,7 @@ export type AccountData = { expiry: string };
export type AccountToken = string;
export type Ip = string;
export type Location = {
+ ip: ?string,
country: string,
city: ?string,
latitude: number,
@@ -33,6 +34,7 @@ export type Location = {
hostname: ?string,
};
const LocationSchema = object({
+ ip: maybe(string),
country: string,
city: maybe(string),
latitude: number,
@@ -55,8 +57,22 @@ export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
export type TunnelState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'blocked';
+export type TunnelEndpoint = {
+ address: string,
+ tunnel: TunnelEndpointData,
+};
+
+export type TunnelEndpointData = {
+ openvpn: {
+ port: number,
+ protocol: RelayProtocol,
+ },
+};
+
export type TunnelStateTransition =
- | { state: 'disconnected' | 'connecting' | 'connected' }
+ | { state: 'disconnected' }
+ | { state: 'connecting', details: ?TunnelEndpoint }
+ | { state: 'connected', details: TunnelEndpoint }
| { state: 'disconnecting', details: AfterDisconnect }
| { state: 'blocked', details: BlockReason };
@@ -91,12 +107,7 @@ type RelaySettingsNormal<TTunnelConstraints> = {
// types describing the structure of RelaySettings
export type RelaySettingsCustom = {
host: string,
- tunnel: {
- openvpn: {
- port: number,
- protocol: RelayProtocol,
- },
- },
+ tunnel: TunnelEndpointData,
};
export type RelaySettings =
| {|
@@ -127,6 +138,13 @@ const constraint = <T>(constraintValue: SchemaNode<T>) => {
);
};
+const TunnelEndpointDataSchema = object({
+ openvpn: object({
+ port: number,
+ protocol: enumeration('udp', 'tcp'),
+ }),
+});
+
const RelaySettingsSchema = oneOf(
object({
normal: object({
@@ -156,12 +174,7 @@ const RelaySettingsSchema = oneOf(
object({
custom_tunnel_endpoint: object({
host: string,
- tunnel: object({
- openvpn: object({
- port: number,
- protocol: enumeration('udp', 'tcp'),
- }),
- }),
+ tunnel: TunnelEndpointDataSchema,
}),
}),
);
@@ -240,6 +253,13 @@ const TunnelStateTransitionSchema = oneOf(
details: enumeration('nothing', 'block', 'reconnect'),
}),
object({
+ state: enumeration('connecting', 'connected'),
+ details: object({
+ address: string,
+ tunnel: TunnelEndpointDataSchema,
+ }),
+ }),
+ object({
state: enumeration('blocked'),
details: oneOf(
object({
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js
index 668eb2a67b..6f8a297678 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js
@@ -1,13 +1,15 @@
// @flow
-import type { AfterDisconnect, BlockReason } from '../../lib/daemon-rpc';
+import type { AfterDisconnect, BlockReason, TunnelEndpoint } from '../../lib/daemon-rpc';
type ConnectingAction = {
type: 'CONNECTING',
+ tunnelEndpoint: ?TunnelEndpoint,
};
type ConnectedAction = {
type: 'CONNECTED',
+ tunnelEndpoint: TunnelEndpoint,
};
type DisconnectedAction = {
@@ -54,15 +56,17 @@ export type ConnectionAction =
| OnlineAction
| OfflineAction;
-function connecting(): ConnectingAction {
+function connecting(tunnelEndpoint: ?TunnelEndpoint): ConnectingAction {
return {
type: 'CONNECTING',
+ tunnelEndpoint,
};
}
-function connected(): ConnectedAction {
+function connected(tunnelEndpoint: TunnelEndpoint): ConnectedAction {
return {
type: 'CONNECTED',
+ tunnelEndpoint,
};
}
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
index b90f285e4f..622663dc14 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
@@ -32,10 +32,10 @@ export default function(
return { ...state, ...action.newLocation };
case 'CONNECTING':
- return { ...state, status: { state: 'connecting' } };
+ return { ...state, status: { state: 'connecting', details: action.tunnelEndpoint } };
case 'CONNECTED':
- return { ...state, status: { state: 'connected' } };
+ return { ...state, status: { state: 'connected', details: action.tunnelEndpoint } };
case 'DISCONNECTED':
return { ...state, status: { state: 'disconnected' } };