diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-01-03 12:43:36 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-01-03 12:43:36 +0100 |
| commit | d19f790931beca0dcc62ae84f16d45b74d69a9c8 (patch) | |
| tree | 42f2ddc38820c72517d1598835da718b583d07d9 /app | |
| parent | d9f182e02742f9266941e4263e3e8f9cd92f6d32 (diff) | |
| parent | 9c1589392d08c31bd4cc95f1e727c705bfb6900a (diff) | |
| download | mullvadvpn-d19f790931beca0dcc62ae84f16d45b74d69a9c8.tar.xz mullvadvpn-d19f790931beca0dcc62ae84f16d45b74d69a9c8.zip | |
Merge branch 'add-lan-sharing'
Diffstat (limited to 'app')
| -rw-r--r-- | app/components/Preferences.js | 57 | ||||
| -rw-r--r-- | app/components/PreferencesStyles.js | 103 | ||||
| -rw-r--r-- | app/components/Settings.js | 18 | ||||
| -rw-r--r-- | app/containers/PreferencesPage.js | 26 | ||||
| -rw-r--r-- | app/containers/SettingsPage.js | 1 | ||||
| -rw-r--r-- | app/lib/backend.js | 30 | ||||
| -rw-r--r-- | app/lib/ipc-facade.js | 32 | ||||
| -rw-r--r-- | app/redux/settings/actions.js | 20 | ||||
| -rw-r--r-- | app/redux/settings/reducers.js | 7 | ||||
| -rw-r--r-- | app/routes.js | 4 | ||||
| -rw-r--r-- | app/transitions.js | 3 |
11 files changed, 283 insertions, 18 deletions
diff --git a/app/components/Preferences.js b/app/components/Preferences.js new file mode 100644 index 0000000000..f0b65d4a06 --- /dev/null +++ b/app/components/Preferences.js @@ -0,0 +1,57 @@ +// @flow +import React from 'react'; +import { Component, Text, Button, View } from 'reactxp'; +import { Layout, Container, Header } from './Layout'; +import Img from './Img'; +import Switch from './Switch'; +import styles from './PreferencesStyles'; + +export type PreferencesProps = { + allowLan: boolean; + onChangeAllowLan: (boolean) => void; + onClose: () => void; +}; + +export default class Preferences extends Component { + props: PreferencesProps; + + render() { + return ( + <Layout> + <Header hidden={ true } style={ 'defaultDark' } /> + <Container> + <View style={ styles.preferences }> + <Button style={ styles.preferences__close } cursor='default' onPress={ this.props.onClose } testName='closeButton'> + <View style={ styles.preferences__close_content }> + <Img style={ styles.preferences__close_icon } source="icon-back" /> + <Text style={ styles.preferences__close_title }>Settings</Text> + </View> + </Button> + <View style={ styles.preferences__container }> + + <View style={ styles.preferences__header }> + <Text style={ styles.preferences__title }>Preferences</Text> + </View> + + <View style={ styles.preferences__content }> + <View style={ styles.preferences__cell }> + <View style={ styles.preferences__cell_label_container }> + <Text style={ styles.preferences__cell_label }>Local network sharing</Text> + </View> + <View style={ styles.preferences__cell_accessory }> + <Switch isOn={ this.props.allowLan } onChange={ this.props.onChangeAllowLan } /> + </View> + </View> + <View style={ styles.preferences__cell_footer }> + <Text style={ styles.preferences__cell_footer_label }> + { 'Allows access to other devices on the same network for sharing, printing etc.' } + </Text> + </View> + </View> + </View> + </View> + </Container> + </Layout> + ); + } +} diff --git a/app/components/PreferencesStyles.js b/app/components/PreferencesStyles.js new file mode 100644 index 0000000000..64d900b09a --- /dev/null +++ b/app/components/PreferencesStyles.js @@ -0,0 +1,103 @@ +// @flow + +import { createViewStyles, createTextStyles } from '../lib/styles'; + +export default { + ...createViewStyles({ + preferences: { + background: '#192E45', + height: '100%', + }, + preferences__container: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, + preferences__header: { + flexGrow: 0, + flexShrink: 0, + flexBasis: 'auto', + paddingTop: 40, + paddingRight: 24, + paddingLeft: 24, + paddingBottom: 24, + position: 'relative' /* anchor for close button */ + }, + preferences__close: { + position: 'absolute', + top: 0, + left: 12, + borderWidth: 0, + padding: 0, + margin: 0, + zIndex: 1, /* part of .preferences__container covers the button */ + cursor: 'default', + }, + preferences__close_content: { + flexDirection: 'row', + alignItems: 'center', + }, + preferences__close_icon: { + opacity: 0.6, + marginRight: 8, + }, + preferences__content: { + flexDirection: 'column', + flexGrow: 1, + flexShrink: 1, + flexBasis: 'auto', + }, + preferences__cell: { + backgroundColor: 'rgba(41,71,115,1)', + flexDirection: 'row', + alignItems: 'center', + }, + preferences__cell_accessory: { + marginRight: 12, + }, + preferences__cell_footer: { + paddingTop: 8, + paddingRight: 24, + paddingBottom: 24, + paddingLeft: 24, + }, + preferences__cell_label_container: { + paddingTop: 15, + paddingRight: 12, + paddingBottom: 15, + paddingLeft: 24, + flexGrow: 1, + }, + }), + ...createTextStyles({ + preferences__close_title: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: 600, + color: 'rgba(255, 255, 255, 0.6)', + }, + preferences__title: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: 900, + lineHeight: 40, + color: '#fff', + }, + preferences__cell_label: { + fontFamily: 'DINPro', + fontSize: 20, + fontWeight: 900, + lineHeight: 26, + letterSpacing: -0.2, + color: '#fff', + }, + preferences__cell_footer_label: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: 600, + lineHeight: 20, + letterSpacing: -0.2, + color: 'rgba(255,255,255,0.8)' + } + }) +};
\ No newline at end of file diff --git a/app/components/Settings.js b/app/components/Settings.js index fe73ffd0af..b0b758b58c 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -18,6 +18,7 @@ export type SettingsProps = { onClose: () => void, onViewAccount: () => void, onViewSupport: () => void, + onViewPreferences: () => void, onViewAdvancedSettings: () => void, onExternalLink: (type: string) => void }; @@ -58,6 +59,7 @@ export default class Settings extends Component { {/* show account options when logged in */} {isLoggedIn ? ( <View style={styles.settings_account} testName='settings__account'> + <Button onPress={ this.props.onViewAccount } testName='settings__view_account'> <View style={styles.settings__cell}> <Text style={styles.settings__cell_label}>Account</Text> @@ -75,15 +77,27 @@ export default class Settings extends Component { ) : null} {isLoggedIn ? ( - <Button onPress={ this.props.onViewAdvancedSettings }> + <Button onPress={ this.props.onViewPreferences } testName='settings__preferences'> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Preferences</Text> + <Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor' /> + </View> + </Button> + ) : null} + + {isLoggedIn ? ( + <Button onPress={ this.props.onViewAdvancedSettings } testName="settings__advanced"> <View style={styles.settings__cell}> <Text style={styles.settings__cell_label}>Advanced</Text> <Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor'/> </View> - <View style={styles.settings__cell_spacer}></View> </Button> ) : null} + {isLoggedIn ? ( + <View style={styles.settings__cell_spacer}></View> + ) : null} + <Button onPress={ this.props.onExternalLink.bind(this, 'faq') } testName='settings__external_link'> <View style={styles.settings__cell}> <Text style={styles.settings__cell_label}>FAQs</Text> diff --git a/app/containers/PreferencesPage.js b/app/containers/PreferencesPage.js new file mode 100644 index 0000000000..793848d35b --- /dev/null +++ b/app/containers/PreferencesPage.js @@ -0,0 +1,26 @@ +// @flow + +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { push } from 'react-router-redux'; +import Preferences from '../components/Preferences'; + +import type { ReduxState, ReduxDispatch } from '../redux/store'; +import type { SharedRouteProps } from '../routes'; + +const mapStateToProps = (state: ReduxState) => ({ + allowLan: state.settings.allowLan +}); + +const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => { + const { backend } = props; + const { push: pushHistory } = bindActionCreators({ push }, dispatch); + return { + onClose: () => pushHistory('/settings'), + onChangeAllowLan: (allowLan) => { + backend.setAllowLan(allowLan); + }, + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Preferences); diff --git a/app/containers/SettingsPage.js b/app/containers/SettingsPage.js index 938d306fee..bfe30ee566 100644 --- a/app/containers/SettingsPage.js +++ b/app/containers/SettingsPage.js @@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) = onClose: () => pushHistory('/connect'), onViewAccount: () => pushHistory('/settings/account'), onViewSupport: () => pushHistory('/settings/support'), + onViewPreferences: () => pushHistory('/settings/preferences'), onViewAdvancedSettings: () => pushHistory('/settings/advanced'), onExternalLink: (type) => open(links[type]), }; diff --git a/app/lib/backend.js b/app/lib/backend.js index f78d359926..da87deced4 100644 --- a/app/lib/backend.js +++ b/app/lib/backend.js @@ -139,6 +139,12 @@ export class Backend { log.error('Failed to fetch the location: ', e.message); } + try { + await this._fetchAllowLan(); + } catch(e) { + log.error('Failed to fetch the LAN sharing policy: ', e.message); + } + await this._fetchAccountHistory(); } @@ -339,8 +345,6 @@ export class Backend { } } - - async _fetchRelayLocations() { await this._ensureAuthenticated(); @@ -395,6 +399,28 @@ export class Backend { ); } + async setAllowLan(allowLan: boolean) { + try { + await this._ensureAuthenticated(); + await this._ipc.setAllowLan(allowLan); + + this._store.dispatch( + settingsActions.updateAllowLan(allowLan) + ); + } catch(e) { + log.error('Failed to change the LAN sharing policy: ', e.message); + } + } + + async _fetchAllowLan() { + await this._ensureAuthenticated(); + + const allowLan = await this._ipc.getAllowLan(); + this._store.dispatch( + settingsActions.updateAllowLan(allowLan) + ); + } + /** * Start reachability monitoring for online/offline detection * This is currently done via HTML5 APIs but will be replaced later diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js index 567a6ab8f1..b2676193ee 100644 --- a/app/lib/ipc-facade.js +++ b/app/lib/ipc-facade.js @@ -142,11 +142,13 @@ const RelayListSchema = object({ export interface IpcFacade { setConnectionString(string): void, getAccountData(AccountToken): Promise<AccountData>, + getRelayLocations(): Promise<RelayList>, getAccount(): Promise<?AccountToken>, setAccount(accountToken: ?AccountToken): Promise<void>, updateRelaySettings(RelaySettingsUpdate): Promise<void>, getRelaySettings(): Promise<RelaySettings>, - getRelayLocations(): Promise<RelayList>, + setAllowLan(boolean): Promise<void>, + getAllowLan(): Promise<boolean>, connect(): Promise<void>, disconnect(): Promise<void>, shutdown(): Promise<void>, @@ -186,6 +188,16 @@ export class RealIpc implements IpcFacade { }); } + async getRelayLocations(): Promise<RelayList> { + const raw = await this._ipc.send('get_relay_locations'); + try { + const validated: any = validate(RelayListSchema, raw); + return (validated: RelayList); + } catch (e) { + throw new InvalidReply(raw, e); + } + } + getAccount(): Promise<?AccountToken> { return this._ipc.send('get_account') .then( raw => { @@ -223,13 +235,17 @@ export class RealIpc implements IpcFacade { }); } - async getRelayLocations(): Promise<RelayList> { - const raw = await this._ipc.send('get_relay_locations'); - try { - const validated: any = validate(RelayListSchema, raw); - return (validated: RelayList); - } catch (e) { - throw new InvalidReply(raw, e); + setAllowLan(allowLan: boolean): Promise<void> { + return this._ipc.send('set_allow_lan', [allowLan]) + .then(this._ignoreResponse); + } + + async getAllowLan(): Promise<boolean> { + const raw = await this._ipc.send('get_allow_lan'); + if(typeof(raw) === 'boolean') { + return raw; + } else { + throw new InvalidReply(raw, 'Expected a boolean'); } } diff --git a/app/redux/settings/actions.js b/app/redux/settings/actions.js index 783eb042d2..02debd5b77 100644 --- a/app/redux/settings/actions.js +++ b/app/redux/settings/actions.js @@ -9,10 +9,15 @@ export type UpdateRelayAction = { export type UpdateRelayLocationsAction = { type: 'UPDATE_RELAY_LOCATIONS', - relayLocations: Array<RelayLocationRedux> -} + relayLocations: Array<RelayLocationRedux>, +}; + +export type UpdateAllowLanAction = { + type: 'UPDATE_ALLOW_LAN', + allowLan: boolean, +}; -export type SettingsAction = UpdateRelayAction | UpdateRelayLocationsAction; +export type SettingsAction = UpdateRelayAction | UpdateRelayLocationsAction | UpdateAllowLanAction; function updateRelay(relay: RelaySettingsRedux): UpdateRelayAction { return { @@ -28,4 +33,11 @@ function updateRelayLocations(relayLocations: Array<RelayLocationRedux>): Update }; } -export default { updateRelay, updateRelayLocations }; +function updateAllowLan(allowLan: boolean): UpdateAllowLanAction { + return { + type: 'UPDATE_ALLOW_LAN', + allowLan: allowLan, + }; +} + +export default { updateRelay, updateRelayLocations, updateAllowLan }; diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js index 21258a80a7..59f51c2a66 100644 --- a/app/redux/settings/reducers.js +++ b/app/redux/settings/reducers.js @@ -34,6 +34,7 @@ export type RelayLocationRedux = { export type SettingsReduxState = { relaySettings: RelaySettingsRedux, relayLocations: Array<RelayLocationRedux>, + allowLan: boolean, }; const initialState: SettingsReduxState = { @@ -45,6 +46,7 @@ const initialState: SettingsReduxState = { } }, relayLocations: [], + allowLan: false, }; export default function(state: SettingsReduxState = initialState, action: ReduxAction): SettingsReduxState { @@ -60,6 +62,11 @@ export default function(state: SettingsReduxState = initialState, action: ReduxA relayLocations: action.relayLocations, }; + case 'UPDATE_ALLOW_LAN': + return { ...state, + allowLan: action.allowLan, + }; + default: return state; } diff --git a/app/routes.js b/app/routes.js index f428c8093b..9caea27884 100644 --- a/app/routes.js +++ b/app/routes.js @@ -9,6 +9,7 @@ import ConnectPage from './containers/ConnectPage'; import SettingsPage from './containers/SettingsPage'; import AdvancedSettingsPage from './containers/AdvancedSettingsPage'; import AccountPage from './containers/AccountPage'; +import PreferencesPage from './containers/PreferencesPage'; import SupportPage from './containers/SupportPage'; import SelectLocationPage from './containers/SelectLocationPage'; import { getTransitionProps } from './transitions'; @@ -97,8 +98,9 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar <PrivateRoute exact path="/connect" component={ ConnectPage } /> <PublicRoute exact path="/settings" component={ SettingsPage } /> <PrivateRoute exact path="/settings/account" component={ AccountPage } /> - <PublicRoute exact path="/settings/support" component={ SupportPage } /> + <PublicRoute exact path="/settings/preferences" component={ PreferencesPage } /> <PublicRoute exact path="/settings/advanced" component={ AdvancedSettingsPage } /> + <PublicRoute exact path="/settings/support" component={ SupportPage } /> <PrivateRoute exact path="/select-location" component={ SelectLocationPage } /> </Switch> </CSSTransitionGroup> diff --git a/app/transitions.js b/app/transitions.js index fe6da53563..58e692f99c 100644 --- a/app/transitions.js +++ b/app/transitions.js @@ -49,8 +49,9 @@ const transitions: TransitionMap = { */ const transitionRules = [ r('/settings', '/settings/account', transitions.push), - r('/settings', '/settings/support', transitions.push), + r('/settings', '/settings/preferences', transitions.push), r('/settings', '/settings/advanced', transitions.push), + r('/settings', '/settings/support', transitions.push), r(null, '/settings', transitions.slide), r(null, '/select-location', transitions.slide) ]; |
