Skip to content

Commit

Permalink
refactor(app): Allow user to specify wifi security type if unknown
Browse files Browse the repository at this point in the history
Closes #2553
  • Loading branch information
mcous committed Oct 30, 2018
1 parent 85cc5bd commit 2ba2d58
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 65 deletions.
2 changes: 1 addition & 1 deletion api/src/opentrons/server/endpoints/networking.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def list_networks(request: web.Request) -> web.Response:
signal: int // e.g. 100; arbitrary signal strength, more is better
active: boolean // e.g. true; whether there is a connection active
security: str // e.g. "WPA2 802.1X" raw nmcli security type output
securityType: str // e.g. "wpa-eap"; see blow
securityType: str // e.g. "wpa-eap"; see below
}
]
}
Expand Down
99 changes: 50 additions & 49 deletions app/src/components/RobotSettings/SelectNetwork/ConnectForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import * as React from 'react'
import {Formik} from 'formik'
import get from 'lodash/get'
import find from 'lodash/find'
import map from 'lodash/map'
import set from 'lodash/set'

import {WPA_PSK_SECURITY, WPA_EAP_SECURITY} from '../../../http-api-client'
import {
NO_SECURITY,
WPA_PSK_SECURITY,
WPA_EAP_SECURITY,
SECURITY_TYPE_FIELD,
EAP_CONFIG_FIELD,
EAP_TYPE_FIELD,
} from '../../../http-api-client'

import {DropdownField} from '@opentrons/components'
import {BottomButtonBar} from '../../modals'
import ConnectFormField, {CONNECT_FIELD_ID_PREFIX} from './ConnectFormField'
import FormTable, {FormTableRow} from './FormTable'
import SelectSecurity from './SelectSecurity'
import FormTable from './FormTable'

import type {
WifiSecurityType,
Expand All @@ -24,7 +30,7 @@ import type {
type Props = {
// TODO(mc, 2018-10-22): optional SSID
ssid: string,
securityType: WifiSecurityType,
securityType: ?WifiSecurityType,
eapOptions: ?WifiEapOptionsList,
keys: ?WifiKeysList,
configure: WifiConfigureRequest => mixed,
Expand All @@ -36,7 +42,7 @@ type State = {|
showPassword: {[name: string]: boolean},
|}

type FormValues = {[string]: ?(string | {[string]: string})}
export type FormValues = {[string]: ?(string | {[string]: string})}

const PSK_FIELD_NAME = 'psk'
const PSK_MIN_LENGTH = 8
Expand All @@ -52,19 +58,17 @@ const WPA_PSK_FIELDS = [

// all eap options go in a sub-object `eapConfig`
// eap method is stored under eapConfig.eapType
const EAP_FIELD_PREFIX = 'eapConfig.'
const EAP_METHOD_DISPLAY_NAME = 'Authentication'
const EAP_METHOD_FIELD = `${EAP_FIELD_PREFIX}eapType`
const EAP_METHOD_FIELD_ID = `${CONNECT_FIELD_ID_PREFIX}${EAP_METHOD_FIELD}`
const getEapMethod = (v: FormValues): ?string => get(v, EAP_METHOD_FIELD)
const SECURITY_TYPE_LABEL = 'Authentication'
const SECURITY_TYPE_ID = `${CONNECT_FIELD_ID_PREFIX}${SECURITY_TYPE_FIELD}`
const getEapType = (v: FormValues): ?string => get(v, EAP_TYPE_FIELD)

export default class ConnectForm extends React.Component<Props, State> {
constructor (props: Props) {
super(props)
this.state = {showPassword: {}}
}

onSubmit = (values: {[name: string]: string}) => {
onSubmit = (values: FormValues) => {
this.props.configure({
ssid: this.props.ssid,
securityType: this.props.securityType,
Expand All @@ -85,6 +89,10 @@ export default class ConnectForm extends React.Component<Props, State> {
}

getValidationSchema = (values: FormValues) => {
const securityType = this.getSecurityType(values)

if (securityType === NO_SECURITY) return {}

const errors = this.getFields(values).reduce((errors, field) => {
const {name, displayName, required} = field
const value = get(values, name, '')
Expand All @@ -98,30 +106,30 @@ export default class ConnectForm extends React.Component<Props, State> {
return errors
}, {})

if (this.getSecurityType(values) === WPA_EAP_SECURITY) {
if (!getEapMethod(values)) {
set(errors, EAP_METHOD_FIELD, `${EAP_METHOD_DISPLAY_NAME} is required`)
}
if (
!securityType ||
(securityType === WPA_EAP_SECURITY && !getEapType(values))
) {
set(errors, SECURITY_TYPE_FIELD, `${SECURITY_TYPE_LABEL} is required`)
}

return errors
}

// TODO(mc, 2018-10-26): allow security type to be pulled from values
// if not in props
getSecurityType (values: FormValues): WifiSecurityType {
return this.props.securityType
getSecurityType (values: FormValues): ?WifiSecurityType {
const formSecurityType: ?WifiSecurityType = (values.securityType: any)
return this.props.securityType || formSecurityType
}

getFields (values: FormValues): Array<WifiAuthField> {
const securityType = this.getSecurityType(values)

if (securityType === WPA_PSK_SECURITY) return WPA_PSK_FIELDS
if (securityType === WPA_EAP_SECURITY) {
const method = find(this.props.eapOptions, {name: getEapMethod(values)})
const method = find(this.props.eapOptions, {name: getEapType(values)})
return get(method, 'options', []).map(field => ({
...field,
name: `${EAP_FIELD_PREFIX}${field.name}`,
name: `${EAP_CONFIG_FIELD}.${field.name}`,
}))
}

Expand All @@ -130,11 +138,7 @@ export default class ConnectForm extends React.Component<Props, State> {

render () {
const {showPassword} = this.state
const {securityType, keys, addKey, close} = this.props
const eapOptions = map(this.props.eapOptions, o => ({
name: o.displayName || o.name,
value: o.name,
}))
const {keys, addKey, close, securityType: knownSecurityType} = this.props

return (
<Formik
Expand All @@ -151,34 +155,31 @@ export default class ConnectForm extends React.Component<Props, State> {
handleBlur,
setFieldTouched,
resetForm,
setValues,
handleSubmit,
} = formProps

return (
<form onSubmit={handleSubmit}>
<FormTable>
{securityType === WPA_EAP_SECURITY && (
<FormTableRow
label={`* ${EAP_METHOD_DISPLAY_NAME}:`}
labelFor={EAP_METHOD_FIELD_ID}
>
<DropdownField
id={EAP_METHOD_FIELD_ID}
name={EAP_METHOD_FIELD}
value={getEapMethod(values)}
options={eapOptions}
onChange={e => {
// reset all other fields on EAP type change
resetForm(set({}, EAP_METHOD_FIELD, e.target.value))
}}
onBlur={handleBlur}
error={
get(touched, EAP_METHOD_FIELD) &&
get(errors, EAP_METHOD_FIELD)
}
/>
</FormTableRow>
)}
<SelectSecurity
id={SECURITY_TYPE_ID}
name={SECURITY_TYPE_FIELD}
label={`* ${SECURITY_TYPE_LABEL}:`}
value={getEapType(values) || this.getSecurityType(values)}
knownSecurityType={knownSecurityType}
touched={get(touched, SECURITY_TYPE_FIELD)}
error={get(errors, SECURITY_TYPE_FIELD)}
eapOptions={this.props.eapOptions}
setValues={nextValues => {
if (nextValues.securityType === NO_SECURITY) {
setValues(nextValues)
} else {
resetForm(nextValues)
}
}}
onLoseFocus={setFieldTouched}
/>
{this.getFields(values).map(field => (
<ConnectFormField
key={field.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {WifiSecurityType} from '../../../http-api-client'

type Props = {
ssid: ?string,
securityType: WifiSecurityType,
securityType: ?WifiSecurityType,
close: () => mixed,
children: React.Node,
}
Expand All @@ -24,6 +24,8 @@ export default function ConnectModal (props: Props) {
body = `Wi-Fi network ${ssid} requires a WPA2 password.`
} else if (securityType === WPA_EAP_SECURITY) {
body = `Wi-Fi network ${ssid} requires 802.1X authentication.`
} else {
body = `Please select the security type for Wi-Fi network ${ssid}.`
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default class SelectKey extends React.Component<Props, State> {
const {id, name, value, error, keys, onLoseFocus} = this.props
const keyOptions = map(keys, k => ({value: k.id, label: k.name}))
const addNewGroup = {
label: null,
options: [
{
value: UPLOAD_KEY_VALUE,
Expand Down
122 changes: 122 additions & 0 deletions app/src/components/RobotSettings/SelectNetwork/SelectSecurity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// @flow
import * as React from 'react'
import find from 'lodash/find'
import set from 'lodash/set'

import {
NO_SECURITY,
WPA_PSK_SECURITY,
WPA_EAP_SECURITY,
EAP_TYPE_FIELD,
} from '../../../http-api-client'
import SelectField from '../../SelectField'
import {FormTableRow} from './FormTable'
import styles from './styles.css'

import type {SelectOption} from '../../SelectField'
import type {
WifiSecurityType,
WifiEapOptionsList,
} from '../../../http-api-client'
import type {FormValues} from './ConnectForm'

type Props = {
id: string,
name: string,
label: string,
value: ?string,
knownSecurityType: ?WifiSecurityType,
touched: ?boolean,
error: ?string,
eapOptions: ?WifiEapOptionsList,
setValues: (values: FormValues) => mixed,
onLoseFocus: (name: string) => mixed,
}

const NO_SECURITY_LABEL = 'None'
const WPA_PSK_SECURITY_LABEL = 'WPA2 Personal'
const PLACEHOLDER = 'Select authentication method'

const BASE_SECURITY_OPTIONS: Array<SelectOption> = [
{
label: null,
options: [{value: NO_SECURITY, label: NO_SECURITY_LABEL}],
},
{
label: null,
options: [{value: WPA_PSK_SECURITY, label: WPA_PSK_SECURITY_LABEL}],
},
]

const eapOptToSelectOpt = o => ({
label: o.displayName || o.name,
value: o.name,
})

export default class SelectSecurity extends React.Component<Props> {
handleValueChange = (name: string, value: string) => {
const {knownSecurityType, eapOptions, setValues} = this.props
const eapType = find(eapOptions, {name: value})
const nextValues = {}

if (eapType) {
if (!knownSecurityType) set(nextValues, name, WPA_EAP_SECURITY)
set(nextValues, EAP_TYPE_FIELD, value)
} else {
set(nextValues, name, value)
}

setValues(nextValues)
}

getOptions (): Array<SelectOption> {
const {knownSecurityType, eapOptions} = this.props
const options = knownSecurityType ? [] : [...BASE_SECURITY_OPTIONS]

if (eapOptions && eapOptions.length) {
options.push({
label: null,
options: eapOptions.map(eapOptToSelectOpt),
})
}

return options
}

render () {
const {
id,
name,
label,
value,
touched,
error,
knownSecurityType,
onLoseFocus,
} = this.props

if (
knownSecurityType === NO_SECURITY ||
knownSecurityType === WPA_PSK_SECURITY
) {
return null
}

return (
<FormTableRow label={label} labelFor={id}>
<SelectField
id={id}
name={name}
options={this.getOptions()}
value={value}
error={touched && error ? error : null}
placeholder={PLACEHOLDER}
onValueChange={this.handleValueChange}
onLoseFocus={onLoseFocus}
className={styles.select_security}
menuPosition="fixed"
/>
</FormTableRow>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function makeNetworkOption (nw: WifiNetwork): OptionType {
)

const securedIcon =
nw.securityType !== 'none' ? (
nw.securityType && nw.securityType !== 'none' ? (
<Icon name="lock" className={styles.wifi_option_icon_right} />
) : (
<span className={styles.wifi_option_icon_right} />
Expand Down
Loading

0 comments on commit 2ba2d58

Please sign in to comment.