diff --git a/src/common/editable/index.js b/src/common/editable/index.js index 00e091220..4cd242b8b 100644 --- a/src/common/editable/index.js +++ b/src/common/editable/index.js @@ -390,7 +390,6 @@ const MAP_TYPE_SELECT = { } @propTypes({ - labelProp: propTypes.string.isRequired, value: propTypes.oneOfType([ propTypes.string, propTypes.object diff --git a/src/common/form/select-plain-object.js b/src/common/form/select-plain-object.js index 976ffcd1f..c5007f771 100644 --- a/src/common/form/select-plain-object.js +++ b/src/common/form/select-plain-object.js @@ -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 diff --git a/src/common/intl/messages.js b/src/common/intl/messages.js index c2e3c5cb7..83d88b089 100644 --- a/src/common/intl/messages.js +++ b/src/common/intl/messages.js @@ -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', diff --git a/src/common/link.js b/src/common/link.js index 3579d9ae5..b47d40424 100644 --- a/src/common/link.js +++ b/src/common/link.js @@ -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 ( diff --git a/src/common/modal.js b/src/common/modal.js index b76949a20..8e4cb18df 100644 --- a/src/common/modal.js +++ b/src/common/modal.js @@ -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
-
- + <ReactModal.Header closeButton> + <ReactModal.Title> {icon ? <span><Icon icon={icon} /> {title}</span> : title } - -
- + + + {body} - -
+ + {map(buttons, ({ label, tooltip, value, icon, ...props - }) => { + }, key) => { const button = - return + return {tooltip !== undefined ? {button} : button @@ -109,7 +106,7 @@ class GenericModal extends Component { {_('genericCancel')} } -
+
} } @@ -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 ( - + {this.state.content} ) diff --git a/src/common/react-novnc.js b/src/common/react-novnc.js index a6dfd335a..28c3d673c 100644 --- a/src/common/react-novnc.js +++ b/src/common/react-novnc.js @@ -41,7 +41,7 @@ export default class NoVnc extends Component { } } - if (state !== 'disconnected') { + if (state !== 'disconnected' || this.refs.canvas == null) { return } diff --git a/src/common/state-button.js b/src/common/state-button.js index 9a7337ede..6a4b0f253 100644 --- a/src/common/state-button.js +++ b/src/common/state-button.js @@ -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 => )` 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`]}; diff --git a/src/common/tab-button.js b/src/common/tab-button.js index d6048fa2c..279edeabd 100644 --- a/src/common/tab-button.js +++ b/src/common/tab-button.js @@ -18,7 +18,9 @@ const TabButton = ({ {...props} size='large' style={STYLE} - >{_(labelId)} + > + {labelId !== undefined && {_(labelId)}} + ) export { TabButton as default } diff --git a/src/common/xo/install-xosan-pack-modal/index.js b/src/common/xo/install-xosan-pack-modal/index.js index fcde3e41d..4d4e7e555 100644 --- a/src/common/xo/install-xosan-pack-modal/index.js +++ b/src/common/xo/install-xosan-pack-modal/index.js @@ -93,15 +93,14 @@ export default class InstallXosanPackModal extends Component { :
-

{_('xosanNoPackFound')}

-

- {_('xosanPackRequirements')} -

    - {map(this._getXosanPacks(), ({ name, requirements }) =>
  • - {name}: {requirements && requirements.xenserver ? requirements.xenserver : '/'} -
  • )} -
-

+ {_('xosanNoPackFound')} +
+ {_('xosanPackRequirements')} +
    + {map(this._getXosanPacks(), ({ name, requirements }, key) =>
  • + {_.keyValue(name, requirements && requirements.xenserver ? requirements.xenserver : '/')} +
  • )} +
} diff --git a/src/icons.scss b/src/icons.scss index eab61ee21..e6e0d257f 100644 --- a/src/icons.scss +++ b/src/icons.scss @@ -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; diff --git a/src/xo-app/backup/new/index.js b/src/xo-app/backup/new/index.js index d2f7e21f1..5cd31a880 100644 --- a/src/xo-app/backup/new/index.js +++ b/src/xo-app/backup/new/index.js @@ -563,131 +563,133 @@ export default class New extends Component { return ( -
-
- - - -
- - -
-
- - -
-
- - -
- {(method === 'vm.rollingDeltaBackup' || method === 'vm.deltaCopy') &&
- {_('backupVersionWarning')} -
} - {backupInfo &&
- {_(backupInfo.label)}} - required - schema={backupInfo.schema} - uiSchema={backupInfo.uiSchema} - onChange={this.linkState('mainParams')} - value={this._getMainParams()} - /> + + +
+ + +
- + + +
+
+ + +
+
+
- {smartBackupMode - ? - + {_('backupVersionWarning')} +
} + {backupInfo &&
+ {_(backupInfo.label)}} + required + schema={backupInfo.schema} + uiSchema={backupInfo.uiSchema} + onChange={this.linkState('mainParams')} + value={this._getMainParams()} + /> +
+ + +
+ {smartBackupMode + ? + {_('vmsToBackup')}} + onChange={this.linkState('vmsParam')} + required + schema={SMART_SCHEMA} + uiSchema={SMART_UI_SCHEMA} + value={vms} + /> + + : {_('vmsToBackup')}} onChange={this.linkState('vmsParam')} required - schema={SMART_SCHEMA} - uiSchema={SMART_UI_SCHEMA} + schema={NO_SMART_SCHEMA} + uiSchema={NO_SMART_UI_SCHEMA} value={vms} /> - - : {_('vmsToBackup')}} - onChange={this.linkState('vmsParam')} - required - schema={NO_SMART_SCHEMA} - uiSchema={NO_SMART_UI_SCHEMA} - value={vms} - /> - } -
} - -
-
-
-
- -
-
- - - - - {process.env.XOA_PLAN < 4 && backupInfo && process.env.XOA_PLAN < REQUIRED_XOA_PLAN[backupInfo.jobKey] - ? - : (smartBackupMode && process.env.XOA_PLAN < 3 - ? - :
- - {_('saveBackupJob')} - - -
) - } - -
-
-
-
+ } + } + + + + +
+ +
+
+ + + + + {process.env.XOA_PLAN < 4 && backupInfo && process.env.XOA_PLAN < REQUIRED_XOA_PLAN[backupInfo.jobKey] + ? + : (smartBackupMode && process.env.XOA_PLAN < 3 + ? + :
+ + {_('saveBackupJob')} + + +
) + } + +
+
+
+ +
) } diff --git a/src/xo-app/backup/overview/index.js b/src/xo-app/backup/overview/index.js index b7bcda225..db68bba7b 100644 --- a/src/xo-app/backup/overview/index.js +++ b/src/xo-app/backup/overview/index.js @@ -212,7 +212,7 @@ export default class Overview extends Component {
-
{_('backupSchedules')}
+ {_('backupSchedules')}
{schedules.length ? ( diff --git a/src/xo-app/backup/restore/index.js b/src/xo-app/backup/restore/index.js index ebe4f5028..4de9855a1 100644 --- a/src/xo-app/backup/restore/index.js +++ b/src/xo-app/backup/restore/index.js @@ -57,7 +57,7 @@ const VM_COLUMNS = [ { name: _('backupTags'), itemRenderer: ({ tagsByRemote }) => - {map(tagsByRemote, ({ tags, remoteName }) => + {map(tagsByRemote, ({ tags, remoteName }, key) => {remoteName} {tags.join(', ')} )} diff --git a/src/xo-app/home/index.js b/src/xo-app/home/index.js index ec86992c3..23bd54389 100644 --- a/src/xo-app/home/index.js +++ b/src/xo-app/home/index.js @@ -550,7 +550,7 @@ export default class Home extends Component { {name} ), - + ]} {map(filters, (filter, label) => this._setFilter(filter)}> @@ -584,7 +584,8 @@ export default class Home extends Component { + to='/vms/new' + > {_('homeNewVm')} @@ -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])} />
)) diff --git a/src/xo-app/host/tab-advanced.js b/src/xo-app/host/tab-advanced.js index ee299bd85..79d661e00 100644 --- a/src/xo-app/host/tab-advanced.js +++ b/src/xo-app/host/tab-advanced.js @@ -112,7 +112,7 @@ export default ({ {_('hostXenServerVersion')} - + {host.license_params.sku_marketing_name} {host.version} ({host.license_params.sku_type}) diff --git a/src/xo-app/host/tab-network.js b/src/xo-app/host/tab-network.js index 0605b00ed..7a9a34b0f 100644 --- a/src/xo-app/host/tab-network.js +++ b/src/xo-app/host/tab-network.js @@ -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 + return {pif.device} {networks[pif.$network].name_label} @@ -238,7 +240,7 @@ export default ({ - {map(pifs, pif => )} + {map(pifs, pif => )} diff --git a/src/xo-app/jobs/new/index.js b/src/xo-app/jobs/new/index.js index 3aaca1dd3..e85bf4fb8 100644 --- a/src/xo-app/jobs/new/index.js +++ b/src/xo-app/jobs/new/index.js @@ -381,7 +381,7 @@ export default class Jobs extends Component { /> - + {action &&
{job &&

{_('jobEditMessage', { name: job.name, id: job.id.slice(4, 8) })}

} diff --git a/src/xo-app/new/sr/index.js b/src/xo-app/new/sr/index.js index 5f857011b..c5ca1ebf2 100644 --- a/src/xo-app/new/sr/index.js +++ b/src/xo-app/new/sr/index.js @@ -491,7 +491,7 @@ export default class New extends Component { > {map(typeGroups, (types, group) => - + {map(types, type => )} diff --git a/src/xo-app/pool/tab-logs.js b/src/xo-app/pool/tab-logs.js index 29eb5c7e9..fe39f515f 100644 --- a/src/xo-app/pool/tab-logs.js +++ b/src/xo-app/pool/tab-logs.js @@ -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 {isEmpty(logs) @@ -43,15 +53,21 @@ export default class TabLogs extends Component { :
- - + + diff --git a/src/xo-app/self/index.js b/src/xo-app/self/index.js index 2e8efb9d3..6c1410474 100644 --- a/src/xo-app/self/index.js +++ b/src/xo-app/self/index.js @@ -507,7 +507,7 @@ class ResourceSet extends Component { } = resourceSet return [ -
  • +
  • , ...map(objectsByType, (objectsSet, type) => ( @@ -515,7 +515,7 @@ class ResourceSet extends Component { {map(objectsSet, object => renderXoItem(object, { className: 'mr-1' }))} )), - !isEmpty(ipPools) &&
  • + !isEmpty(ipPools) &&
  • {map(ipPools, pool => { const resolvedIpPool = resolvedIpPools[pool] const limits = get(resourceSet, `limits[ipPool:${pool}]`) @@ -531,7 +531,7 @@ class ResourceSet extends Component { } )}
  • , -
  • +
  • @@ -613,7 +613,7 @@ class ResourceSet extends Component {
  • , -
  • +
  • {_('editResourceSet')} {_('deleteResourceSet')} diff --git a/src/xo-app/settings/ips/index.js b/src/xo-app/settings/ips/index.js index 303614e00..260dd1797 100644 --- a/src/xo-app/settings/ips/index.js +++ b/src/xo-app/settings/ips/index.js @@ -85,9 +85,9 @@ class IpsCell extends BaseComponent { {_('ipsVifs')} - {ipPool.addresses && map(formatIps(keys(ipPool.addresses)), ip => { + {ipPool.addresses && map(formatIps(keys(ipPool.addresses)), (ip, key) => { if (isObject(ip)) { // Range of IPs - return + return {ip.first} {ip.last} @@ -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 + return {network && vif ? `${network.name_label} #${vif.device}` : {_('ipPoolUnknownVif')} @@ -188,7 +188,7 @@ class NetworksCell extends BaseComponent { const { newNetworks, showNewNetworkForm } = this.state return - {map(ipPool.networks, networkId => + {map(ipPool.networks, networkId => {renderXoItemFromId(networkId)} diff --git a/src/xo-app/user/index.js b/src/xo-app/user/index.js index ee2e101c8..4ab8046c1 100644 --- a/src/xo-app/user/index.js +++ b/src/xo-app/user/index.js @@ -329,7 +329,7 @@ export default class User extends Component {
    {' '} {' '} - {_('vmChooseCoresPerSocket', message => )} + {_('vmChooseCoresPerSocket', message => )} {this._selectedValueIsNotInOptions() && - _('vmCoresPerSocketIncorrectValue', message => ) + _('vmCoresPerSocketIncorrectValue', message => ) } {map( options, - coresPerSocket => _( - 'vmCoresPerSocket', { + coresPerSocket => - ) + })} + )} {' '} @@ -392,7 +394,7 @@ export default ({ {_('osKernel')} - {vm.os_version ? vm.os_version.uname ? vm.os_version.uname : _('unknownOsKernel') : _('unknownOsKernel')} + {(vm.os_version && vm.os_version.uname) || _('unknownOsKernel')} diff --git a/src/xo-app/vm/tab-general.js b/src/xo-app/vm/tab-general.js index 7ff8c0468..23835d0ff 100644 --- a/src/xo-app/vm/tab-general.js +++ b/src/xo-app/vm/tab-general.js @@ -83,12 +83,12 @@ export default connectStore(() => { - - {vm.addresses && vm.addresses['0/ip'] - ? vm.addresses['0/ip'] - : _('noIpv4Record') - } - + {vm.addresses && vm.addresses['0/ip'] + ? + {vm.addresses['0/ip']} + + :

    {_('noIpv4Record')}

    + }
    diff --git a/src/xo-app/vm/tab-network.js b/src/xo-app/vm/tab-network.js index f3d75b2ec..65af3b950 100644 --- a/src/xo-app/vm/tab-network.js +++ b/src/xo-app/vm/tab-network.js @@ -403,7 +403,7 @@ export default class TabNetwork extends BaseComponent { - {map(vm.VIFs, vif => )} + {map(vm.VIFs, vif => )} {vm.addresses && !isEmpty(vm.addresses) diff --git a/src/xo-app/xosan/index.js b/src/xo-app/xosan/index.js index 18332b527..2e7deba4c 100644 --- a/src/xo-app/xosan/index.js +++ b/src/xo-app/xosan/index.js @@ -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 } })