Compare commits
16 Commits
xo-web/v5.
...
v5.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb8f96c2e2 | ||
|
|
95d4cc9055 | ||
|
|
cb84a85f8b | ||
|
|
0a8aa2ecf5 | ||
|
|
5941321e84 | ||
|
|
8cf62280f4 | ||
|
|
4cea142b57 | ||
|
|
64d9245bc4 | ||
|
|
2d78c0c4c3 | ||
|
|
aa585e2d25 | ||
|
|
325ab17dcc | ||
|
|
443ea44bcd | ||
|
|
07958d8efa | ||
|
|
f19affe599 | ||
|
|
f7b7c27b6c | ||
|
|
c7af5b384c |
10
README.md
10
README.md
@@ -60,8 +60,8 @@ Otherwise, please consider using the [bugtracker of the general repository](http
|
||||
## Process for new release
|
||||
|
||||
```bash
|
||||
# Switch to the master branch.
|
||||
git checkout master
|
||||
# Switch to the stable branch.
|
||||
git checkout stable
|
||||
|
||||
# Fetches latest changes.
|
||||
git pull --ff-only
|
||||
@@ -75,12 +75,12 @@ npm version minor
|
||||
# Go back to the next-release branch.
|
||||
git checkout next-release
|
||||
|
||||
# Fetches the last changes (the merge and version bump) from master to
|
||||
# Fetches the last changes (the merge and version bump) from stable to
|
||||
# next-release.
|
||||
git merge --ff-only master
|
||||
git merge --ff-only stable
|
||||
|
||||
# Push the changes on git.
|
||||
git push --follow-tags origin master next-release
|
||||
git push --follow-tags origin stable next-release
|
||||
|
||||
# Publish this release to npm.
|
||||
npm publish
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"private": false,
|
||||
"name": "xo-web",
|
||||
"version": "5.0.1",
|
||||
"version": "5.0.2",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Web interface client for Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -111,7 +111,7 @@
|
||||
"superagent": "^2.0.0",
|
||||
"vinyl": "^1.1.1",
|
||||
"watchify": "^3.7.0",
|
||||
"xo-acl-resolver": "^0.2.0",
|
||||
"xo-acl-resolver": "^0.2.1",
|
||||
"xo-lib": "^0.8.0-1",
|
||||
"xo-remote-parser": "^0.3"
|
||||
},
|
||||
|
||||
2062
src/common/intl/locales/es.js
Normal file
2062
src/common/intl/locales/es.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -367,7 +367,6 @@ export default {
|
||||
restartVmsModalTitle: 'Redémarrer {vms, plural, one {la} other {les}} VM{vms, plural, one {} other {s}}',
|
||||
restartVmsModalMessage: 'Voulez-vous vraiment redémarrer {vms} VM{vms, plural, one {} other {s}} ?',
|
||||
migrateVmModalTitle: 'Migrer la VM',
|
||||
migrateVmModalBody: 'Voulez-vous vraiment migrer cette VM sur {hostName} ?',
|
||||
migrateVmAdvancedModalSelectHost: 'Sélectionnez un hôte de destination:',
|
||||
migrateVmAdvancedModalSelectNetwork: 'Sélectionnez un réseau pour la migration:',
|
||||
migrateVmAdvancedModalSelectSrs: 'Pour chaque VDI, sélectionnez un SR:',
|
||||
|
||||
@@ -1715,9 +1715,6 @@ export default {
|
||||
// Original text: "Migrate VM"
|
||||
migrateVmModalTitle: undefined,
|
||||
|
||||
// Original text: "Are you sure you want to migrate this VM to {hostName}?"
|
||||
migrateVmModalBody: undefined,
|
||||
|
||||
// Original text: "Select a destination host:"
|
||||
migrateVmAdvancedModalSelectHost: undefined,
|
||||
|
||||
|
||||
@@ -1712,9 +1712,6 @@ export default {
|
||||
// Original text: "Migrate VM"
|
||||
migrateVmModalTitle: 'Migrar VM',
|
||||
|
||||
// Original text: "Are you sure you want to migrate this VM to {hostName}?"
|
||||
migrateVmModalBody: 'Você tem certeza que deseja migrar esta VM para {hostName}?',
|
||||
|
||||
// Original text: "Select a destination host:"
|
||||
migrateVmAdvancedModalSelectHost: 'Selecionar um host de destino:',
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ var messages = {
|
||||
jobsNewPage: 'New',
|
||||
jobsSchedulingPage: 'Scheduling',
|
||||
customJob: 'Custom Job',
|
||||
userPage: 'User',
|
||||
|
||||
// ----- Sign in/out -----
|
||||
usernameLabel: 'Username:',
|
||||
@@ -677,7 +678,6 @@ var messages = {
|
||||
deleteVmModalMessage: 'Are you sure you want to delete this VM? ALL VM DISKS WILL BE REMOVED',
|
||||
deleteVmsModalMessage: 'Are you sure you want to delete {vms} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',
|
||||
migrateVmModalTitle: 'Migrate VM',
|
||||
migrateVmModalBody: 'Are you sure you want to migrate this VM to {hostName}?',
|
||||
migrateVmAdvancedModalSelectHost: 'Select a destination host:',
|
||||
migrateVmAdvancedModalSelectNetwork: 'Select a migration network:',
|
||||
migrateVmAdvancedModalSelectSrs: 'For each VDI, select an SR:',
|
||||
@@ -793,7 +793,22 @@ var messages = {
|
||||
disconnectPif: 'Disconnect PIF',
|
||||
disconnectPifConfirm: 'Are you sure you want to disconnect this PIF?',
|
||||
deletePif: 'Delete PIF',
|
||||
deletePifConfirm: 'Are you sure you want to delete this PIF?'
|
||||
deletePifConfirm: 'Are you sure you want to delete this PIF?',
|
||||
|
||||
// ----- User -----
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
language: 'Language',
|
||||
oldPasswordPlaceholder: 'Old password',
|
||||
newPasswordPlaceholder: 'New password',
|
||||
confirmPasswordPlaceholder: 'Confirm new password',
|
||||
confirmationPasswordError: 'Confirmation password incorrect',
|
||||
confirmationPasswordErrorBody: 'Password does not match the confirm password.',
|
||||
pwdChangeSuccess: 'Password changed',
|
||||
pwdChangeSuccessBody: 'Your password has been successfully changed.',
|
||||
pwdChangeError: 'Incorrect password',
|
||||
pwdChangeErrorBody: 'The old password provided is incorrect. Your password has not been changed.',
|
||||
changePasswordOk: 'OK'
|
||||
}
|
||||
forEach(messages, function (message, id) {
|
||||
if (isString(message)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Icon from 'icon'
|
||||
import React, { Component } from 'react'
|
||||
import { createGetObject } from 'selectors'
|
||||
|
||||
import { isSrWritable } from 'xo'
|
||||
import {
|
||||
connectStore,
|
||||
formatSize,
|
||||
@@ -53,7 +53,7 @@ export const SrItem = propTypes({
|
||||
})(({ sr, container }) => {
|
||||
let label = `${sr.name_label || sr.id}`
|
||||
|
||||
if (sr.content_type === 'user') {
|
||||
if (isSrWritable(sr)) {
|
||||
label += ` (${formatSize(sr.size)})`
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from 'utils'
|
||||
|
||||
import {
|
||||
isSrWritable,
|
||||
subscribeGroups,
|
||||
subscribeRemotes,
|
||||
subscribeRoles,
|
||||
@@ -325,11 +326,9 @@ export const SelectPool = makeStoreSelect(() => ({
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const userSrPredicate = sr => sr.content_type === 'user'
|
||||
|
||||
export const SelectSr = makeStoreSelect(() => {
|
||||
const getSrsByContainer = createGetObjectsOfType('SR').filter(
|
||||
(_, { predicate }) => predicate || userSrPredicate
|
||||
(_, { predicate }) => predicate || isSrWritable
|
||||
).sort().groupBy('$container')
|
||||
|
||||
const getContainerIds = createSelector(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,8 @@ import {
|
||||
createSelector
|
||||
} from '../../selectors'
|
||||
|
||||
import { isSrWritable } from '../'
|
||||
|
||||
import styles from './index.css'
|
||||
|
||||
@connectStore(() => {
|
||||
@@ -76,7 +78,7 @@ export default class MigrateVmModalBody extends Component {
|
||||
this._getSrPredicate = createSelector(
|
||||
() => this.state.host,
|
||||
host => (host
|
||||
? sr => sr.content_type !== 'iso' && (sr.$container === host.id || sr.$container === host.$pool)
|
||||
? sr => isSrWritable(sr) && (sr.$container === host.id || sr.$container === host.$pool)
|
||||
: () => false
|
||||
)
|
||||
)
|
||||
@@ -101,6 +103,10 @@ export default class MigrateVmModalBody extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._selectHost(this.props.host)
|
||||
}
|
||||
|
||||
get value () {
|
||||
return {
|
||||
targetHost: this.state.host && this.state.host.id,
|
||||
@@ -154,13 +160,14 @@ export default class MigrateVmModalBody extends Component {
|
||||
_selectMigrationNetwork = network => this.setState({ network })
|
||||
|
||||
render () {
|
||||
const { vdis, vifs, networks } = this.props
|
||||
const { host, vdis, vifs, networks } = this.props
|
||||
return <div>
|
||||
<div className={styles.firstBlock}>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('migrateVmAdvancedModalSelectHost')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectHost
|
||||
defaultValue={host}
|
||||
onChange={this._selectHost}
|
||||
predicate={this._getHostPredicate()}
|
||||
/>
|
||||
|
||||
@@ -343,6 +343,18 @@
|
||||
@extend .xo-status-busy;
|
||||
}
|
||||
|
||||
// Task
|
||||
&-task {
|
||||
&-cancel {
|
||||
@extend .fa;
|
||||
@extend .fa-ban;
|
||||
}
|
||||
&-destroy {
|
||||
@extend .fa;
|
||||
@extend .fa-trash;
|
||||
}
|
||||
}
|
||||
|
||||
// SR
|
||||
&-sr, &-vdi {
|
||||
&-reconnect-all {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { SelectSr } from 'select-objects'
|
||||
import {
|
||||
importBackup,
|
||||
importDeltaBackup,
|
||||
isSrWritable,
|
||||
listRemote,
|
||||
startVm,
|
||||
subscribeRemotes
|
||||
@@ -43,7 +44,7 @@ const backupOptionRenderer = backup => <span>
|
||||
|
||||
@connectStore(() => ({
|
||||
writableSrs: createGetObjectsOfType('SR').filter(
|
||||
[ sr => sr.content_type !== 'iso' ]
|
||||
[ isSrWritable ]
|
||||
).sort()
|
||||
}))
|
||||
export default class Restore extends Component {
|
||||
@@ -102,8 +103,10 @@ export default class Restore extends Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
backupInfoByVm[backup.name] || (backupInfoByVm[backup.name] = [])
|
||||
backupInfoByVm[backup.name].push(backup)
|
||||
if (backup) {
|
||||
backupInfoByVm[backup.name] || (backupInfoByVm[backup.name] = [])
|
||||
backupInfoByVm[backup.name].push(backup)
|
||||
}
|
||||
})
|
||||
for (let vm in backupInfoByVm) {
|
||||
const bks = backupInfoByVm[vm]
|
||||
@@ -208,12 +211,11 @@ const BK_COLUMNS = [
|
||||
}
|
||||
]
|
||||
|
||||
const srWritablePredicate = sr => sr.content_type !== 'iso'
|
||||
const notifyImportStart = () => info(_('importBackupTitle'), _('importBackupMessage'))
|
||||
|
||||
@connectStore(() => ({
|
||||
writableSrs: createGetObjectsOfType('SR').filter(
|
||||
[ sr => sr.content_type !== 'iso' ]
|
||||
[ isSrWritable ]
|
||||
).sort()
|
||||
}), { withRef: true })
|
||||
class _ModalBody extends Component {
|
||||
@@ -274,7 +276,7 @@ class _ModalBody extends Component {
|
||||
|
||||
render () {
|
||||
return <div>
|
||||
<SelectSr ref='sr' predicate={srWritablePredicate} />
|
||||
<SelectSr ref='sr' predicate={isSrWritable} />
|
||||
<br />
|
||||
<SelectPlainObject ref='backup' options={this.state.options} optionKey='path' optionRenderer={backupOptionRenderer} placeholder={this.props.intl.formatMessage(messages.importBackupModalSelectBackup)} />
|
||||
<br />
|
||||
|
||||
@@ -8,7 +8,7 @@ import TabButton from 'tab-button'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import React, { Component } from 'react'
|
||||
import { confirm } from 'modal'
|
||||
import { deleteMessage, deleteVdi, deleteVm } from 'xo'
|
||||
import { deleteMessage, deleteVdi, deleteVm, isSrWritable } from 'xo'
|
||||
import { FormattedRelative, FormattedTime } from 'react-intl'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
@@ -182,7 +182,7 @@ const ALARM_COLUMNS = [
|
||||
.filter([ snapshot => !snapshot.$snapshot_of ])
|
||||
.sort()
|
||||
const getUserSrs = createGetObjectsOfType('SR')
|
||||
.filter([ sr => sr.content_type === 'user' ])
|
||||
.filter([ isSrWritable ])
|
||||
const getVdiSrs = createGetObjectsOfType('SR')
|
||||
.pick(createSelector(
|
||||
getOrphanVdiSnapshots,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
formatSize
|
||||
} from 'utils'
|
||||
import {
|
||||
isSrWritable,
|
||||
subscribeUsers
|
||||
} from 'xo'
|
||||
|
||||
@@ -46,7 +47,7 @@ import {
|
||||
|
||||
const userSrs = createTop(
|
||||
createGetObjectsOfType('SR').filter(
|
||||
[ sr => sr.content_type === 'user' ]
|
||||
[ isSrWritable ]
|
||||
),
|
||||
[ sr => sr.physical_usage / sr.size ],
|
||||
5
|
||||
|
||||
@@ -44,7 +44,7 @@ export default ({
|
||||
const sr = srs[pbd.SR]
|
||||
return <BlockLink key={pbd.id} to={`/srs/${sr.id}/general`} tagName='tr'>
|
||||
<td>
|
||||
<Text value={sr.name_label} onChange={value => editSr(sr, { name_label: value })} />
|
||||
<Text value={sr.name_label} onChange={nameLabel => editSr(sr, { nameLabel })} useLongClick />
|
||||
</td>
|
||||
<td>{sr.SR_type}</td>
|
||||
<td>{formatSize(sr.size)}</td>
|
||||
|
||||
@@ -67,3 +67,7 @@
|
||||
.refreshNames {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.customConfig {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ export default class NewVm extends BaseComponent {
|
||||
|
||||
_getSrPredicate = createSelector(
|
||||
() => this.state.state.pool.id,
|
||||
poolId => disk => disk.$pool === poolId && disk.content_type === 'user'
|
||||
poolId => disk => disk.$pool === poolId && disk.content_type !== 'iso' && disk.size > 0
|
||||
)
|
||||
|
||||
_getDefaultNetworkId = () => {
|
||||
@@ -260,15 +260,17 @@ export default class NewVm extends BaseComponent {
|
||||
const existingDisks = {}
|
||||
forEach(template.$VBDs, vbdId => {
|
||||
const vbd = getObject(storeState, vbdId)
|
||||
if (vbd.is_cd_drive) {
|
||||
if (!vbd || vbd.is_cd_drive) {
|
||||
return
|
||||
}
|
||||
const vdi = getObject(storeState, vbd.VDI)
|
||||
existingDisks[this.getUniqueId()] = {
|
||||
name_label: vdi.name_label,
|
||||
name_description: vdi.name_description,
|
||||
size: vdi.size,
|
||||
$SR: vdi.$SR
|
||||
if (vdi) {
|
||||
existingDisks[this.getUniqueId()] = {
|
||||
name_label: vdi.name_label,
|
||||
name_description: vdi.name_description,
|
||||
size: vdi.size,
|
||||
$SR: vdi.$SR
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -301,6 +303,7 @@ export default class NewVm extends BaseComponent {
|
||||
cpuWeight: 1,
|
||||
// installation
|
||||
installMethod: template.install_methods && template.install_methods[0] || state.installMethod,
|
||||
customConfig: '#cloud-config\n#hostname: myhostname\n#ssh_authorized_keys:\n# - ssh-rsa <myKey>\n#packages:\n# - htop\n',
|
||||
// interfaces
|
||||
VIFs,
|
||||
// disks
|
||||
@@ -361,7 +364,11 @@ export default class NewVm extends BaseComponent {
|
||||
value = isArray(value) ? [ ...value ] : { ...value }
|
||||
let eventValue = getEventValue(event)
|
||||
eventValue = targetObjectProp ? eventValue[targetObjectProp] : eventValue
|
||||
stateObjectProp ? value[index][stateObjectProp] = eventValue : value[index] = eventValue
|
||||
if (value[index] && stateObjectProp) {
|
||||
value[index][stateObjectProp] = eventValue
|
||||
} else {
|
||||
value[index] = eventValue
|
||||
}
|
||||
} else {
|
||||
value = getEventValue(event)
|
||||
}
|
||||
@@ -599,7 +606,7 @@ export default class NewVm extends BaseComponent {
|
||||
<span>{_('newVmCustomConfig')}</span>
|
||||
{' '}
|
||||
<DebounceInput
|
||||
className='form-control'
|
||||
className={classNames('form-control', styles.customConfig)}
|
||||
debounceTimeout={DEBOUNCE_TIMEOUT}
|
||||
disabled={installMethod !== 'customConfig'}
|
||||
element='textarea'
|
||||
|
||||
@@ -7,7 +7,7 @@ import pick from 'lodash/pick'
|
||||
import React, { cloneElement, Component } from 'react'
|
||||
import { NavLink, NavTabs } from 'nav'
|
||||
import { Text } from 'editable'
|
||||
import { editPool } from 'xo'
|
||||
import { editPool, isSrWritable } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
connectStore,
|
||||
@@ -63,7 +63,7 @@ import TabStorage from './tab-storage'
|
||||
const getPoolSrs = createGetObjectsOfType('SR').filter(
|
||||
createSelector(
|
||||
getPool,
|
||||
({ id }) => sr => sr.content_type === 'user' && sr.$pool === id
|
||||
({ id }) => sr => isSrWritable(sr) && sr.$pool === id
|
||||
)
|
||||
).sort()
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export default ({
|
||||
{map(srs, sr => {
|
||||
return <tr key={sr.id}>
|
||||
<td>
|
||||
<Text value={sr.name_label} onChange={value => editSr(sr, { name_label: value })} />
|
||||
<Text value={sr.name_label} onChange={nameLabel => editSr(sr, { nameLabel })} />
|
||||
</td>
|
||||
<td>{sr.SR_type}</td>
|
||||
<td>{formatSize(sr.size)}</td>
|
||||
|
||||
@@ -40,7 +40,7 @@ const USER_COLUMNS = [
|
||||
name: _('userPermissionColumn'),
|
||||
itemRenderer: user => <Select
|
||||
clearable={false}
|
||||
value={user.permission}
|
||||
value={user.permission || permissions.none.value}
|
||||
ref='permission'
|
||||
onChange={permission => editUser(user, {permission: permission.value})}
|
||||
options={map(permissions)}
|
||||
@@ -62,13 +62,17 @@ const USER_COLUMNS = [
|
||||
})
|
||||
@injectIntl
|
||||
export default class Users extends Component {
|
||||
componentWillMount () {
|
||||
this._selectPermission(permissions.none)
|
||||
}
|
||||
|
||||
_create = () => {
|
||||
const {email, password} = this.refs
|
||||
const { permission } = this.state
|
||||
return createUser(email.value, password.value, permission.value)
|
||||
.then(() => {
|
||||
email.value = password.value = ''
|
||||
this.setState({permission: undefined})
|
||||
this._selectPermission(permissions.none)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,7 +98,7 @@ export default class Users extends Component {
|
||||
clearable={false}
|
||||
ref='permission'
|
||||
options={map(permissions)}
|
||||
value={this.state.permission || permissions.none}
|
||||
value={this.state.permission}
|
||||
placeholder={intl.formatMessage(messages.selectPermission)}
|
||||
onChange={this._selectPermission}
|
||||
required
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import _ from 'intl'
|
||||
import ActionRowButton from 'action-row-button'
|
||||
import CenterPanel from 'center-panel'
|
||||
import Icon from 'icon'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import keys from 'lodash/keys'
|
||||
import Link from 'react-router/lib/Link'
|
||||
import map from 'lodash/map'
|
||||
import Page from '../page'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import { ButtonGroup } from 'react-bootstrap-4/lib'
|
||||
import { Card, CardBlock, CardHeader } from 'card'
|
||||
import { connectStore } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
|
||||
import {
|
||||
createGetObject,
|
||||
createGetObjectsOfType,
|
||||
createSelector
|
||||
} from 'selectors'
|
||||
import {
|
||||
cancelTask,
|
||||
destroyTask
|
||||
} from 'xo'
|
||||
|
||||
import Page from '../page'
|
||||
|
||||
const HEADER = <Container>
|
||||
<Row>
|
||||
@@ -25,16 +32,38 @@ const HEADER = <Container>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
const TASK_ITEM_STYLE = {
|
||||
// Remove all margin, otherwise it breaks vertical alignment.
|
||||
margin: 0
|
||||
}
|
||||
|
||||
export const TaskItem = connectStore(() => ({
|
||||
host: createGetObject((_, props) => props.task.$host)
|
||||
}))(({ task, host }) => <Row>
|
||||
}))(({ task, host }) => <SingleLineRow className='m-b-1'>
|
||||
<Col mediumSize={6}>
|
||||
{task.name_label} (on <Link to={`/hosts/${host.id}`}>{host.name_label}</Link>)
|
||||
{' ' + Math.round(task.progress * 100)}%
|
||||
</Col>
|
||||
<Col mediumSize={6}>
|
||||
<progress className='progress' value={task.progress * 100} max='100'></progress>
|
||||
<Col mediumSize={4}>
|
||||
<progress style={TASK_ITEM_STYLE} className='progress' value={task.progress * 100} max='100'></progress>
|
||||
</Col>
|
||||
</Row>)
|
||||
<Col mediumSize={2}>
|
||||
<ButtonGroup>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={cancelTask}
|
||||
handlerParam={task}
|
||||
icon='task-cancel'
|
||||
/>
|
||||
<ActionRowButton
|
||||
btnStyle='default'
|
||||
handler={destroyTask}
|
||||
handlerParam={task}
|
||||
icon='task-destroy'
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</SingleLineRow>)
|
||||
|
||||
export default connectStore(() => {
|
||||
const getPendingTasksByPool = createGetObjectsOfType('task').filter(
|
||||
@@ -71,17 +100,19 @@ export default connectStore(() => {
|
||||
}
|
||||
|
||||
return <Page header={HEADER}>
|
||||
<Card>
|
||||
<Container>
|
||||
{map(pools, pool =>
|
||||
<span>
|
||||
<CardHeader key={pool.id}><Link to={`/pools/${pool.id}`}>{pool.name_label}</Link></CardHeader>
|
||||
<CardBlock>
|
||||
{map(pendingTasksByPool[pool.id], task =>
|
||||
<TaskItem key={task.id} task={task} />
|
||||
)}
|
||||
</CardBlock>
|
||||
</span>
|
||||
<Row>
|
||||
<Card>
|
||||
<CardHeader key={pool.id}><Link to={`/pools/${pool.id}`}>{pool.name_label}</Link></CardHeader>
|
||||
<CardBlock>
|
||||
{map(pendingTasksByPool[pool.id], task =>
|
||||
<TaskItem key={task.id} task={task} />
|
||||
)}
|
||||
</CardBlock>
|
||||
</Card>
|
||||
</Row>
|
||||
)}
|
||||
</Card>
|
||||
</Container>
|
||||
</Page>
|
||||
})
|
||||
|
||||
@@ -1,30 +1,101 @@
|
||||
import React, { Component } from 'react'
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import BaseComponent from 'base-component'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import { alert } from 'modal'
|
||||
import { autobind, connectStore } from 'utils'
|
||||
import { changePassword } from 'xo'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { getLang, getUser } from 'selectors'
|
||||
import { injectIntl } from 'react-intl'
|
||||
|
||||
import Page from '../page'
|
||||
|
||||
const HEADER = <Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<h2><Icon icon='user' /> {_('userPage')}</h2>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
@connectStore({
|
||||
lang: getLang,
|
||||
user: getUser
|
||||
})
|
||||
export default class User extends Component {
|
||||
@injectIntl
|
||||
export default class User extends BaseComponent {
|
||||
@autobind
|
||||
handleSelectLang (event) {
|
||||
this.props.selectLang(event.target.value)
|
||||
}
|
||||
_handleSavePassword = () => {
|
||||
const { oldPassword, newPassword, confirmPassword } = this.state
|
||||
if (newPassword !== confirmPassword) {
|
||||
return alert(_('confirmationPasswordError'), _('confirmationPasswordErrorBody'))
|
||||
}
|
||||
return changePassword(oldPassword, newPassword).then(() => this.setState({
|
||||
oldPassword: undefined,
|
||||
newPassword: undefined,
|
||||
confirmPassword: undefined
|
||||
}))
|
||||
}
|
||||
|
||||
_handleOldPasswordChange = event => this.setState({ oldPassword: event.target.value })
|
||||
_handleNewPasswordChange = event => this.setState({ newPassword: event.target.value })
|
||||
_handleConfirmPasswordChange = event => this.setState({ confirmPassword: event.target.value })
|
||||
|
||||
render () {
|
||||
const { formatMessage } = this.props.intl
|
||||
const {
|
||||
lang,
|
||||
user
|
||||
} = this.props
|
||||
const {
|
||||
confirmPassword,
|
||||
newPassword,
|
||||
oldPassword
|
||||
} = this.state
|
||||
|
||||
return <div>
|
||||
{user && user.email}
|
||||
<select className='form-control' onChange={this.handleSelectLang} value={lang} style={{width: '10em'}}>
|
||||
<option value='en'>English</option>
|
||||
<option value='fr'>Français</option>
|
||||
<option value='he'>עברי</option>
|
||||
<option value='pt'>Português</option>
|
||||
</select>
|
||||
</div>
|
||||
return <Page header={HEADER}>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col smallSize={2}><strong>{_('username')}</strong></Col>
|
||||
<Col smallSize={10}>
|
||||
{user && user.email}
|
||||
</Col>
|
||||
</Row>
|
||||
<br />
|
||||
<Row>
|
||||
<Col smallSize={2}><strong>{_('password')}</strong></Col>
|
||||
<Col smallSize={10}>
|
||||
<form className='form-inline' id='changePassword'>
|
||||
<input type='password' onChange={this._handleOldPasswordChange} value={oldPassword} placeholder={formatMessage(messages.oldPasswordPlaceholder)} className='form-control' required />
|
||||
{' '}
|
||||
<input type='password' onChange={this._handleNewPasswordChange} value={newPassword} placeholder={formatMessage(messages.newPasswordPlaceholder)} className='form-control' required />
|
||||
{' '}
|
||||
<input type='password' onChange={this._handleConfirmPasswordChange} value={confirmPassword} placeholder={formatMessage(messages.confirmPasswordPlaceholder)} className='form-control' required />
|
||||
{' '}
|
||||
<ActionButton icon='save' form='changePassword' btnStyle='primary' handler={this._handleSavePassword}>
|
||||
{_('changePasswordOk')}
|
||||
</ActionButton>
|
||||
</form>
|
||||
</Col>
|
||||
</Row>
|
||||
<br />
|
||||
<Row>
|
||||
<Col smallSize={2}><strong>{_('language')}</strong></Col>
|
||||
<Col smallSize={10}>
|
||||
<select className='form-control' onChange={this.handleSelectLang} value={lang} style={{width: '10em'}}>
|
||||
<option value='en'>English</option>
|
||||
<option value='fr'>Français</option>
|
||||
<option value='he'>עברי</option>
|
||||
<option value='pt'>Português</option>
|
||||
</select>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Page>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import filter from 'lodash/filter'
|
||||
import map from 'lodash/map'
|
||||
import Upgrade from 'xoa-upgrade'
|
||||
import { Container, Col, Row } from 'grid'
|
||||
import { importVms } from 'xo'
|
||||
import { importVms, isSrWritable } from 'xo'
|
||||
import {
|
||||
SelectPool,
|
||||
SelectSr
|
||||
@@ -52,7 +52,7 @@ export default class Import extends Component {
|
||||
|
||||
_handleSelectedPool = pool => {
|
||||
const srPredicate = pool !== ''
|
||||
? sr => sr.$pool === pool.id && sr.content_type === 'user'
|
||||
? sr => sr.$pool === pool.id && isSrWritable(sr)
|
||||
: undefined
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
deleteVdi,
|
||||
disconnectVbd,
|
||||
editVdi,
|
||||
isSrWritable,
|
||||
migrateVdi,
|
||||
setBootableVbd,
|
||||
setVmBootOrder
|
||||
@@ -87,7 +88,7 @@ class NewDisk extends Component {
|
||||
const { vm } = this.props
|
||||
return vm && vm.$pool
|
||||
},
|
||||
poolId => sr => sr.$pool === poolId && sr.content_type === 'user'
|
||||
poolId => sr => sr.$pool === poolId && isSrWritable(sr)
|
||||
)
|
||||
|
||||
render () {
|
||||
@@ -135,7 +136,7 @@ class AttachDisk extends Component {
|
||||
const { vm } = this.props
|
||||
return vm && vm.$pool
|
||||
},
|
||||
poolId => sr => sr.$pool === poolId && sr.content_type === 'user'
|
||||
poolId => sr => sr.$pool === poolId && isSrWritable(sr)
|
||||
)
|
||||
|
||||
_selectVdi = vdi => this.setState({vdi})
|
||||
@@ -408,7 +409,7 @@ export default class TabDisks extends Component {
|
||||
<XoSelect
|
||||
onChange={sr => migrateVdi(vdi, sr)}
|
||||
xoType='SR'
|
||||
predicate={sr => (sr.$pool === vm.$pool) && (sr.content_type === 'user')}
|
||||
predicate={sr => sr.$pool === vm.$pool && isSrWritable(sr)}
|
||||
labelProp='name_label'
|
||||
value={sr}
|
||||
useLongClick
|
||||
|
||||
Reference in New Issue
Block a user