fix: multiple fixes (#2272)
This commit is contained in:
committed by
Julien Fontanet
parent
363b22bffe
commit
8a933c98e3
@@ -390,7 +390,6 @@ const MAP_TYPE_SELECT = {
|
||||
}
|
||||
|
||||
@propTypes({
|
||||
labelProp: propTypes.string.isRequired,
|
||||
value: propTypes.oneOfType([
|
||||
propTypes.string,
|
||||
propTypes.object
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
2
src/common/react-novnc.js
vendored
2
src/common/react-novnc.js
vendored
@@ -41,7 +41,7 @@ export default class NoVnc extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (state !== 'disconnected') {
|
||||
if (state !== 'disconnected' || this.refs.canvas == null) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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`]};
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>)}
|
||||
|
||||
@@ -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>
|
||||
))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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}>
|
||||
<
|
||||
</Button>
|
||||
<Button size='large' onClick={this._nextPage}>
|
||||
>
|
||||
</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'
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user