fix: multiple fixes (#2272)

This commit is contained in:
Pierre Donias
2017-07-21 10:36:14 +02:00
committed by Julien Fontanet
parent 363b22bffe
commit 8a933c98e3
26 changed files with 241 additions and 205 deletions

View File

@@ -390,7 +390,6 @@ const MAP_TYPE_SELECT = {
}
@propTypes({
labelProp: propTypes.string.isRequired,
value: propTypes.oneOfType([
propTypes.string,
propTypes.object

View File

@@ -15,7 +15,7 @@ import Select from './select'
multi: propTypes.bool,
onChange: propTypes.func,
options: propTypes.array,
placeholder: propTypes.string,
placeholder: propTypes.node,
predicate: propTypes.func,
required: propTypes.bool,
value: propTypes.any

View File

@@ -103,7 +103,7 @@ var messages = {
// ----- Home view ------
homeFetchingData: 'Fetching data…',
homeWelcome: 'Welcome on Xen Orchestra!',
homeWelcome: 'Welcome to Xen Orchestra!',
homeWelcomeText: 'Add your XenServer hosts or pools',
homeConnectServerText: 'Some XenServers have been registered but are not connected',
homeHelp: 'Want some help?',
@@ -542,7 +542,7 @@ var messages = {
hostCpusNumber: 'Core (socket)',
hostManufacturerinfo: 'Manufacturer info',
hostBiosinfo: 'BIOS info',
licenseHostSettingsLabel: 'Licence',
licenseHostSettingsLabel: 'License',
hostLicenseType: 'Type',
hostLicenseSocket: 'Socket',
hostLicenseExpiry: 'Expiry',
@@ -603,7 +603,7 @@ var messages = {
patchStatus: 'Status',
patchStatusApplied: 'Applied',
patchStatusNotApplied: 'Missing patches',
patchNothing: 'No patch detected',
patchNothing: 'No patches detected',
patchReleaseDate: 'Release date',
patchGuidance: 'Guidance',
patchAction: 'Action',
@@ -1242,7 +1242,7 @@ var messages = {
refresh: 'Refresh',
upgrade: 'Upgrade',
noUpdaterCommunity: 'No updater available for Community Edition',
considerSubscribe: 'Please consider subscribe and try it with all features for free during 15 days on {link}.',
considerSubscribe: 'Please consider subscribing and trying it with all the features for free during 15 days on {link}.',
noUpdaterWarning: 'Manual update could break your current installation due to dependencies issues, do it with caution',
currentVersion: 'Current version:',
register: 'Register',

View File

@@ -44,11 +44,21 @@ export class BlockLink extends Component {
}
}
_addAuxClickListener = ref => {
// FIXME: when https://github.com/facebook/react/issues/8529 is fixed,
// remove and use onAuxClickCapture.
// In Chrome ^55, middle-clicking triggers auxclick event instead of click
if (ref !== null) {
ref.addEventListener('auxclick', this._onClickCapture)
}
}
render () {
const { children, tagName = 'div' } = this.props
const Component = tagName
return (
<Component
ref={this._addAuxClickListener}
style={this._style}
onClickCapture={this._onClickCapture}
>

View File

@@ -29,7 +29,7 @@ const modal = (content, onClose) => {
buttons: propTypes.arrayOf(propTypes.shape({
btnStyle: propTypes.string,
icon: propTypes.string,
label: propTypes.string.isRequired,
label: propTypes.node.isRequired,
tooltip: propTypes.node,
value: propTypes.any
})).isRequired,
@@ -58,8 +58,6 @@ class GenericModal extends Component {
}
render () {
const { Body, Footer, Header, Title } = ReactModal
const {
buttons,
icon,
@@ -69,34 +67,33 @@ class GenericModal extends Component {
const body = _addRef(this.props.children, 'body')
return <div>
<Header closeButton>
<Title>
<ReactModal.Header closeButton>
<ReactModal.Title>
{icon
? <span><Icon icon={icon} /> {title}</span>
: title
}
</Title>
</Header>
<Body>
</ReactModal.Title>
</ReactModal.Header>
<ReactModal.Body>
{body}
</Body>
<Footer>
</ReactModal.Body>
<ReactModal.Footer>
{map(buttons, ({
label,
tooltip,
value,
icon,
...props
}) => {
}, key) => {
const button = <Button
onClick={() => this._resolve(value)}
key={value}
{...props}
>
{icon !== undefined && <Icon icon={icon} fixedWidth />}
{label}
</Button>
return <span>
return <span key={key}>
{tooltip !== undefined
? <Tooltip content={tooltip}>{button}</Tooltip>
: button
@@ -109,7 +106,7 @@ class GenericModal extends Component {
{_('genericCancel')}
</Button>
}
</Footer>
</ReactModal.Footer>
</div>
}
}
@@ -209,14 +206,8 @@ export default class Modal extends Component {
}
render () {
const { showModal } = this.state
/* TODO: remove this work-around and use
* ReactModal.Body, ReactModal.Header, ...
* after this issue has been fixed:
* https://phabricator.babeljs.io/T6976
*/
return (
<ReactModal show={showModal} onHide={this._onHide}>
<ReactModal show={this.state.showModal} onHide={this._onHide}>
{this.state.content}
</ReactModal>
)

View File

@@ -41,7 +41,7 @@ export default class NoVnc extends Component {
}
}
if (state !== 'disconnected') {
if (state !== 'disconnected' || this.refs.canvas == null) {
return
}

View File

@@ -1,10 +1,14 @@
import React from 'react'
import styled from 'styled-components'
import {
omit
} from 'lodash'
import ActionButton from './action-button'
import propTypes from './prop-types-decorator'
const Button = styled(ActionButton)`
// do not forward `state` to ActionButton
const Button = styled(p => <ActionButton {...omit(p, 'state')} />)`
background-color: ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateBg`]};
border: 2px solid ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateColor`]};
color: ${p => p.theme[`${p.state ? 'enabled' : 'disabled'}StateColor`]};

View File

@@ -18,7 +18,9 @@ const TabButton = ({
{...props}
size='large'
style={STYLE}
><span className='hidden-md-down'>{_(labelId)}</span></ActionButton>
>
{labelId !== undefined && <span className='hidden-md-down'>{_(labelId)}</span>}
</ActionButton>
)
export { TabButton as default }

View File

@@ -93,15 +93,14 @@ export default class InstallXosanPackModal extends Component {
</div>
</div>
: <div>
<p>{_('xosanNoPackFound')}</p>
<p>
{_('xosanPackRequirements')}
<ul>
{map(this._getXosanPacks(), ({ name, requirements }) => <li>
{name}: <strong>{requirements && requirements.xenserver ? requirements.xenserver : '/'}</strong>
</li>)}
</ul>
</p>
{_('xosanNoPackFound')}
<br />
{_('xosanPackRequirements')}
<ul>
{map(this._getXosanPacks(), ({ name, requirements }, key) => <li key={key}>
{_.keyValue(name, requirements && requirements.xenserver ? requirements.xenserver : '/')}
</li>)}
</ul>
</div>
}
</div>

View File

@@ -79,6 +79,14 @@
@extend .fa;
@extend .fa-ellipsis-v;
},
&-previous {
@extend .fa;
@extend .fa-chevron-left;
},
&-next {
@extend .fa;
@extend .fa-chevron-right;
},
&-caret {
@extend .fa;
@extend .fa-caret-down;

View File

@@ -563,131 +563,133 @@ export default class New extends Component {
return (
<Upgrade place='newBackup' required={2}>
<Wizard><form id='form-new-vm-backup'>
<Section icon='backup' title={this.props.job ? 'editVmBackup' : 'newVmBackup'}>
<Container>
<Row>
<Col>
<fieldset className='form-group'>
<label>{_('backupOwner')}</label>
<SelectSubject
onChange={this.linkState('job.userId', 'id')}
predicate={this._subjectPredicate}
required
value={this._getValue('job', 'userId', this.props.currentUser.id)}
/>
</fieldset>
<fieldset className='form-group'>
<label>{_('jobTimeoutPlaceHolder')}</label>
<TimeoutInput
className='form-control'
onChange={this.linkState('job.timeout')}
value={this._getValue('job', 'timeout')}
/>
</fieldset>
<fieldset className='form-group'>
<label htmlFor='selectBackup'>{_('newBackupSelection')}</label>
<select
className='form-control'
id='selectBackup'
onChange={this.linkState('job.method')}
required
value={method}
>
{_('noSelectedValue', message => <option value=''>{message}</option>)}
{map(BACKUP_METHOD_TO_INFO, (info, key) =>
_(info.label, message => <option key={key} value={key}>{message}</option>)
)}
</select>
</fieldset>
{(method === 'vm.rollingDeltaBackup' || method === 'vm.deltaCopy') && <div className='alert alert-warning' role='alert'>
<Icon icon='error' /> {_('backupVersionWarning')}
</div>}
{backupInfo && <div>
<GenericInput
label={<span><Icon icon={backupInfo.icon} /> {_(backupInfo.label)}</span>}
required
schema={backupInfo.schema}
uiSchema={backupInfo.uiSchema}
onChange={this.linkState('mainParams')}
value={this._getMainParams()}
/>
<form id='form-new-vm-backup'>
<Wizard>
<Section icon='backup' title={this.props.job ? 'editVmBackup' : 'newVmBackup'}>
<Container>
<Row>
<Col>
<fieldset className='form-group'>
<label htmlFor='smartMode'>{_('smartBackupModeSelection')}</label>
<label>{_('backupOwner')}</label>
<SelectSubject
onChange={this.linkState('job.userId', 'id')}
predicate={this._subjectPredicate}
required
value={this._getValue('job', 'userId', this.props.currentUser.id)}
/>
</fieldset>
<fieldset className='form-group'>
<label>{_('jobTimeoutPlaceHolder')}</label>
<TimeoutInput
className='form-control'
onChange={this.linkState('job.timeout')}
value={this._getValue('job', 'timeout')}
/>
</fieldset>
<fieldset className='form-group'>
<label htmlFor='selectBackup'>{_('newBackupSelection')}</label>
<select
className='form-control'
id='smartMode'
onChange={this._handleSmartBackupMode}
id='selectBackup'
onChange={this.linkState('job.method')}
required
value={smartBackupMode ? 'smart' : 'normal'}
value={method}
>
{_('normalBackup', message => <option value='normal'>{message}</option>)}
{_('smartBackup', message => <option value='smart'>{message}</option>)}
{_('noSelectedValue', message => <option value=''>{message}</option>)}
{map(BACKUP_METHOD_TO_INFO, (info, key) =>
_({ key }, info.label, message => <option value={key}>{message}</option>)
)}
</select>
</fieldset>
{smartBackupMode
? <Upgrade place='newBackup' required={3}>
<GenericInput
{(method === 'vm.rollingDeltaBackup' || method === 'vm.deltaCopy') && <div className='alert alert-warning' role='alert'>
<Icon icon='error' /> {_('backupVersionWarning')}
</div>}
{backupInfo && <div>
<GenericInput
label={<span><Icon icon={backupInfo.icon} /> {_(backupInfo.label)}</span>}
required
schema={backupInfo.schema}
uiSchema={backupInfo.uiSchema}
onChange={this.linkState('mainParams')}
value={this._getMainParams()}
/>
<fieldset className='form-group'>
<label htmlFor='smartMode'>{_('smartBackupModeSelection')}</label>
<select
className='form-control'
id='smartMode'
onChange={this._handleSmartBackupMode}
required
value={smartBackupMode ? 'smart' : 'normal'}
>
{_('normalBackup', message => <option value='normal'>{message}</option>)}
{_('smartBackup', message => <option value='smart'>{message}</option>)}
</select>
</fieldset>
{smartBackupMode
? <Upgrade place='newBackup' required={3}>
<GenericInput
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
onChange={this.linkState('vmsParam')}
required
schema={SMART_SCHEMA}
uiSchema={SMART_UI_SCHEMA}
value={vms}
/>
</Upgrade>
: <GenericInput
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
onChange={this.linkState('vmsParam')}
required
schema={SMART_SCHEMA}
uiSchema={SMART_UI_SCHEMA}
schema={NO_SMART_SCHEMA}
uiSchema={NO_SMART_UI_SCHEMA}
value={vms}
/>
</Upgrade>
: <GenericInput
label={<span><Icon icon='vm' /> {_('vmsToBackup')}</span>}
onChange={this.linkState('vmsParam')}
required
schema={NO_SMART_SCHEMA}
uiSchema={NO_SMART_UI_SCHEMA}
value={vms}
/>
}
</div>}
</Col>
</Row>
</Container>
</Section>
<Section icon='schedule' title='schedule'>
<Scheduler
onChange={this.linkState('scheduling')}
value={scheduling}
/>
</Section>
<Section icon='preview' title='preview' summary>
<Container>
<Row>
<Col>
<SchedulePreview cronPattern={scheduling.cronPattern} />
{process.env.XOA_PLAN < 4 && backupInfo && process.env.XOA_PLAN < REQUIRED_XOA_PLAN[backupInfo.jobKey]
? <Upgrade place='newBackup' available={REQUIRED_XOA_PLAN[backupInfo.jobKey]} />
: (smartBackupMode && process.env.XOA_PLAN < 3
? <Upgrade place='newBackup' available={3} />
: <fieldset className='pull-right pt-1'>
<ActionButton
btnStyle='primary'
className='mr-1'
disabled={!backupInfo}
form='form-new-vm-backup'
handler={this._handleSubmit}
icon='save'
redirectOnSuccess='/backup/overview'
size='large'
>
{_('saveBackupJob')}
</ActionButton>
<Button onClick={this._handleReset} size='large'>
{_('selectTableReset')}
</Button>
</fieldset>)
}
</Col>
</Row>
</Container>
</Section>
</form></Wizard>
}
</div>}
</Col>
</Row>
</Container>
</Section>
<Section icon='schedule' title='schedule'>
<Scheduler
onChange={this.linkState('scheduling')}
value={scheduling}
/>
</Section>
<Section icon='preview' title='preview' summary>
<Container>
<Row>
<Col>
<SchedulePreview cronPattern={scheduling.cronPattern} />
{process.env.XOA_PLAN < 4 && backupInfo && process.env.XOA_PLAN < REQUIRED_XOA_PLAN[backupInfo.jobKey]
? <Upgrade place='newBackup' available={REQUIRED_XOA_PLAN[backupInfo.jobKey]} />
: (smartBackupMode && process.env.XOA_PLAN < 3
? <Upgrade place='newBackup' available={3} />
: <fieldset className='pull-right pt-1'>
<ActionButton
btnStyle='primary'
className='mr-1'
disabled={!backupInfo}
form='form-new-vm-backup'
handler={this._handleSubmit}
icon='save'
redirectOnSuccess='/backup/overview'
size='large'
>
{_('saveBackupJob')}
</ActionButton>
<Button onClick={this._handleReset} size='large'>
{_('selectTableReset')}
</Button>
</fieldset>)
}
</Col>
</Row>
</Container>
</Section>
</Wizard>
</form>
</Upgrade>
)
}

View File

@@ -212,7 +212,7 @@ export default class Overview extends Component {
<div>
<Card>
<CardHeader>
<h5><Icon icon='schedule' /> {_('backupSchedules')}</h5>
<Icon icon='schedule' /> {_('backupSchedules')}
</CardHeader>
<CardBlock>
{schedules.length ? (

View File

@@ -57,7 +57,7 @@ const VM_COLUMNS = [
{
name: _('backupTags'),
itemRenderer: ({ tagsByRemote }) => <Container>
{map(tagsByRemote, ({ tags, remoteName }) => <Row>
{map(tagsByRemote, ({ tags, remoteName }, key) => <Row key={key}>
<Col mediumSize={3}><strong>{remoteName}</strong></Col>
<Col mediumSize={9}>{tags.join(', ')}</Col>
</Row>)}

View File

@@ -550,7 +550,7 @@ export default class Home extends Component {
{name}
</MenuItem>
),
<MenuItem divider />
<MenuItem key='divider' divider />
]}
{map(filters, (filter, label) =>
<MenuItem key={label} onClick={() => this._setFilter(filter)}>
@@ -584,7 +584,8 @@ export default class Home extends Component {
<Col mediumSize={3} className='text-xs-right'>
<Link
className='btn btn-success'
to='/vms/new'>
to='/vms/new'
>
<Icon icon='vm-new' /> {_('homeNewVm')}
</Link>
</Col>
@@ -859,7 +860,7 @@ export default class Home extends Component {
item={item}
key={item.id}
onSelect={this.toggleState(`selectedItems.${item.id}`)}
selected={selectedItems[item.id]}
selected={Boolean(selectedItems[item.id])}
/>
</div>
))

View File

@@ -112,7 +112,7 @@ export default ({
</tr>
<tr>
<th>{_('hostXenServerVersion')}</th>
<Copiable tagName='td'>
<Copiable tagName='td' data={host.version}>
{host.license_params.sku_marketing_name} {host.version} ({host.license_params.sku_type})
</Copiable>
</tr>

View File

@@ -90,6 +90,8 @@ class ConfigureIpModal extends Component {
vifsByNetwork: createGetObjectsOfType('VIF').groupBy('$network')
}))
class PifItem extends Component {
state = { configModes: [] }
componentWillMount () {
getIpv4ConfigModes().then(configModes =>
this.setState({ configModes })
@@ -126,7 +128,7 @@ class PifItem extends Component {
const pifInUse = some(vifsByNetwork[pif.$network], vif => vif.attached)
return <tr key={pif.id}>
return <tr>
<td>{pif.device}</td>
<td>{networks[pif.$network].name_label}</td>
<td>
@@ -238,7 +240,7 @@ export default ({
</tr>
</thead>
<tbody>
{map(pifs, pif => <PifItem pif={pif} networks={networks} />)}
{map(pifs, pif => <PifItem key={pif.id} pif={pif} networks={networks} />)}
</tbody>
</table>
</span>

View File

@@ -381,7 +381,7 @@ export default class Jobs extends Component {
/>
<input type='text' ref='name' className='form-control mb-1 mt-1' placeholder={formatMessage(messages.jobNamePlaceholder)} pattern='[^_]+' required />
<SelectPlainObject ref='method' options={actions} optionKey='method' onChange={this._handleSelectMethod} placeholder={_('jobActionPlaceHolder')} />
<input type='number' onChange={this.linkState('timeout')} value={state.timeout} className='form-control mb-1 mt-1' placeholder='Job timeout (seconds)' />
<input type='number' onChange={this.linkState('timeout')} value={state.timeout || ''} className='form-control mb-1 mt-1' placeholder={formatMessage(messages.jobTimeoutPlaceHolder)} />
{action && <fieldset>
<GenericInput ref='params' schema={action.info} uiSchema={action.uiSchema} label={action.method} required />
{job && <p className='text-warning'>{_('jobEditMessage', { name: job.name, id: job.id.slice(4, 8) })}</p>}

View File

@@ -491,7 +491,7 @@ export default class New extends Component {
>
<option value={null}>{formatMessage(messages.noSelectedValue)}</option>
{map(typeGroups, (types, group) =>
<optgroup label={SR_GROUP_TO_LABEL[group]}>
<optgroup key={group} label={SR_GROUP_TO_LABEL[group]}>
{map(types, type =>
<option key={type} value={type}>{SR_TYPE_TO_LABEL[type]}</option>
)}

View File

@@ -1,14 +1,18 @@
import _ from 'intl'
import ActionRow from 'action-row-button'
import Button from 'button'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import React, { Component } from 'react'
import TabButton from 'tab-button'
import { deleteMessage } from 'xo'
import { createPager } from 'selectors'
import { createPager, createSelector } from 'selectors'
import { FormattedRelative, FormattedTime } from 'react-intl'
import { Container, Row, Col } from 'grid'
import {
ceil,
isEmpty,
map
} from 'lodash'
const LOGS_PER_PAGE = 10
export default class TabLogs extends Component {
constructor () {
@@ -17,7 +21,12 @@ export default class TabLogs extends Component {
this.getLogs = createPager(
() => this.props.logs,
() => this.state.page,
10
LOGS_PER_PAGE
)
this.getNPages = createSelector(
() => this.props.logs ? this.props.logs.length : 0,
nLogs => ceil(nLogs / LOGS_PER_PAGE)
)
this.state = {
@@ -26,11 +35,12 @@ export default class TabLogs extends Component {
}
_deleteAllLogs = () => map(this.props.logs, deleteMessage)
_nextPage = () => this.setState({ page: this.state.page + 1 })
_previousPage = () => this.setState({ page: this.state.page - 1 })
_nextPage = () => this.setState({ page: Math.min(this.state.page + 1, this.getNPages()) })
_previousPage = () => this.setState({ page: Math.max(this.state.page - 1, 1) })
render () {
const logs = this.getLogs()
const { page } = this.state
return <Container>
{isEmpty(logs)
@@ -43,15 +53,21 @@ export default class TabLogs extends Component {
: <div>
<Row>
<Col className='text-xs-right'>
<Button size='large' onClick={this._previousPage}>
&lt;
</Button>
<Button size='large' onClick={this._nextPage}>
&gt;
</Button>
<TabButton
btnStyle='secondary'
disabled={page === 1}
handler={this._previousPage}
icon='previous'
/>
<TabButton
btnStyle='secondary'
disabled={page === this.getNPages()}
handler={this._nextPage}
icon='next'
/>
<TabButton
btnStyle='danger'
handler={this._removeAllLogs}
handler={this._removeAllLogs} // FIXME: define this method
icon='delete'
labelId='logRemoveAll'
/>

View File

@@ -507,7 +507,7 @@ class ResourceSet extends Component {
} = resourceSet
return [
<li className='list-group-item'>
<li key='subjects' className='list-group-item'>
<Subjects subjects={subjects} />
</li>,
...map(objectsByType, (objectsSet, type) => (
@@ -515,7 +515,7 @@ class ResourceSet extends Component {
{map(objectsSet, object => renderXoItem(object, { className: 'mr-1' }))}
</li>
)),
!isEmpty(ipPools) && <li className='list-group-item'>
!isEmpty(ipPools) && <li key='ipPools' className='list-group-item'>
{map(ipPools, pool => {
const resolvedIpPool = resolvedIpPools[pool]
const limits = get(resourceSet, `limits[ipPool:${pool}]`)
@@ -531,7 +531,7 @@ class ResourceSet extends Component {
}
)}
</li>,
<li className='list-group-item'>
<li key='graphs' className='list-group-item'>
<Row>
<Col mediumSize={4}>
<Card>
@@ -613,7 +613,7 @@ class ResourceSet extends Component {
</Col>
</Row>
</li>,
<li className='list-group-item text-xs-center'>
<li key='actions' className='list-group-item text-xs-center'>
<div className='btn-toolbar'>
<ActionButton btnStyle='primary' icon='edit' handler={this.toggleState('editionMode')}>{_('editResourceSet')}</ActionButton>
<ActionButton btnStyle='danger' icon='delete' handler={deleteResourceSet} handlerParam={resourceSet}>{_('deleteResourceSet')}</ActionButton>

View File

@@ -85,9 +85,9 @@ class IpsCell extends BaseComponent {
<Row>
<Col mediumSize={6} offset={5}><strong>{_('ipsVifs')}</strong></Col>
</Row>
{ipPool.addresses && map(formatIps(keys(ipPool.addresses)), ip => {
{ipPool.addresses && map(formatIps(keys(ipPool.addresses)), (ip, key) => {
if (isObject(ip)) { // Range of IPs
return <Row>
return <Row key={key}>
<Col mediumSize={5}>
<strong>{ip.first} <Icon icon='arrow-right' /> {ip.last}</strong>
</Col>
@@ -109,7 +109,7 @@ class IpsCell extends BaseComponent {
? map(addressVifs, (vifId, index) => {
const vif = vifs[vifId] && vifs[vifId][0]
const network = vif && networks[vif.$network] && networks[vif.$network][0]
return <span className='mr-1'>
return <span key={index} className='mr-1'>
{network && vif
? `${network.name_label} #${vif.device}`
: <em>{_('ipPoolUnknownVif')}</em>
@@ -188,7 +188,7 @@ class NetworksCell extends BaseComponent {
const { newNetworks, showNewNetworkForm } = this.state
return <Container>
{map(ipPool.networks, networkId => <Row>
{map(ipPool.networks, networkId => <Row key={networkId}>
<Col mediumSize={11}>
{renderXoItemFromId(networkId)}
</Col>

View File

@@ -329,7 +329,7 @@ export default class User extends Component {
<Col smallSize={10}>
<form className='form-inline' id='changePassword'>
<input
autocomplete='off'
autoComplete='off'
className='form-control'
onChange={this._handleOldPasswordChange}
placeholder={formatMessage(messages.oldPasswordPlaceholder)}
@@ -339,7 +339,7 @@ export default class User extends Component {
/>
{' '}
<input type='password'
autocomplete='off'
autoComplete='off'
className='form-control'
onChange={this._handleNewPasswordChange}
placeholder={formatMessage(messages.newPasswordPlaceholder)}
@@ -348,7 +348,7 @@ export default class User extends Component {
/>
{' '}
<input
autocomplete='off'
autoComplete='off'
className='form-control'
onChange={this._handleConfirmPasswordChange}
placeholder={formatMessage(messages.confirmPasswordPlaceholder)}

View File

@@ -145,19 +145,21 @@ class CoresPerSocket extends Component {
onChange={this._onChange}
value={selectedCoresPerSocket || ''}
>
{_('vmChooseCoresPerSocket', message => <option value=''>{message}</option>)}
{_('vmChooseCoresPerSocket', message => <option key='none' value=''>{message}</option>)}
{this._selectedValueIsNotInOptions() &&
_('vmCoresPerSocketIncorrectValue', message => <option value={selectedCoresPerSocket}> {message}</option>)
_('vmCoresPerSocketIncorrectValue', message => <option key='incorrect' value={selectedCoresPerSocket}> {message}</option>)
}
{map(
options,
coresPerSocket => _(
'vmCoresPerSocket', {
coresPerSocket => <option
key={coresPerSocket}
value={coresPerSocket}
>
{_('vmCoresPerSocket', {
nSockets: vm.CPUs.number / coresPerSocket,
nCores: coresPerSocket
},
message => <option key={coresPerSocket} value={coresPerSocket}>{message}</option>
)
})}
</option>
)}
</select>
{' '}
@@ -392,7 +394,7 @@ export default ({
</tr>
<tr>
<th>{_('osKernel')}</th>
<td>{vm.os_version ? vm.os_version.uname ? vm.os_version.uname : _('unknownOsKernel') : _('unknownOsKernel')}</td>
<td>{(vm.os_version && vm.os_version.uname) || _('unknownOsKernel')}</td>
</tr>
</tbody>
</table>

View File

@@ -83,12 +83,12 @@ export default connectStore(() => {
</Col>
<Col mediumSize={3}>
<BlockLink to={`/vms/${vm.id}/network`}>
<Copiable tagName='p'>
{vm.addresses && vm.addresses['0/ip']
? vm.addresses['0/ip']
: _('noIpv4Record')
}
</Copiable>
{vm.addresses && vm.addresses['0/ip']
? <Copiable tagName='p'>
{vm.addresses['0/ip']}
</Copiable>
: <p>{_('noIpv4Record')}</p>
}
</BlockLink>
</Col>
<Col mediumSize={3}>

View File

@@ -403,7 +403,7 @@ export default class TabNetwork extends BaseComponent {
</tr>
</thead>
<tbody>
{map(vm.VIFs, vif => <VifItem vifId={vif} isVmRunning={isVmRunning(vm)} resourceSet={vm.resourceSet} />)}
{map(vm.VIFs, vif => <VifItem key={vif} vifId={vif} isVmRunning={isVmRunning(vm)} resourceSet={vm.resourceSet} />)}
</tbody>
</table>
{vm.addresses && !isEmpty(vm.addresses)

View File

@@ -148,9 +148,9 @@ export class XosanVolumesTable extends Component {
const _findLatestTemplate = templates => {
let latestTemplate = templates[0]
forEach(templates, pack => {
if (compareVersions(pack.version, latestTemplate.version) > 0) {
latestTemplate = pack
forEach(templates, template => {
if (compareVersions(template.version, latestTemplate.version) > 0) {
latestTemplate = template
}
})