feat(xo-web/vm/advanced): ACL management from VM view (#3774)

Fixes #3040
This commit is contained in:
Rajaa.BARHTAOUI 2019-01-07 15:35:24 +01:00 committed by Pierre Donias
parent 100dd38c33
commit 1af42617c2
4 changed files with 181 additions and 2 deletions

View File

@ -6,6 +6,7 @@
- [Backup NG] Restore logs moved to restore tab [#3772](https://github.com/vatesfr/xen-orchestra/issues/3772) (PR [#3802](https://github.com/vatesfr/xen-orchestra/pull/3802))
- [Remotes] New SMB implementation that provides better stability and performance [#2257](https://github.com/vatesfr/xen-orchestra/issues/2257) (PR [#3708](https://github.com/vatesfr/xen-orchestra/pull/3708))
- [VM/advanced] ACL management from VM view [#3040](https://github.com/vatesfr/xen-orchestra/issues/3727) (PR [#3040](https://github.com/vatesfr/xen-orchestra/pull/3774))
### Bug fixes

View File

@ -1078,6 +1078,12 @@ const messages = {
vmVgpuNone: 'None',
vmAddVgpu: 'Add vGPU',
vmSelectVgpuType: 'Select vGPU type',
vmAcls: 'ACLs',
vmAddAcls: 'Add ACLs',
addAclsErrorTitle: 'Failed to add ACL(s)',
addAclsErrorMessage: 'User(s)/group(s) and role are required.',
removeAcl: 'Delete',
moreAcls: '{nAcls, number} more…',
// ----- VM placeholders -----

View File

@ -129,6 +129,7 @@ const AclTable = decorate([
actions={ACL_ACTIONS}
collection={state.acls}
columns={ACL_COLUMNS}
stateUrlParam='s'
/>
),
])

View File

@ -1,28 +1,50 @@
import _ from 'intl'
import ActionButton from 'action-button'
import Component from 'base-component'
import decorate from 'apply-decorators'
import defined from '@xen-orchestra/defined'
import getEventValue from 'get-event-value'
import Icon from 'icon'
import Link from 'link'
import React from 'react'
import renderXoItem from 'render-xo-item'
import TabButton from 'tab-button'
import Tooltip from 'tooltip'
import { assign, every, find, includes, isEmpty, map, uniq } from 'lodash'
import { error } from 'notification'
import { confirm } from 'modal'
import { Container, Row, Col } from 'grid'
import { injectState, provideState } from 'reaclette'
import { Number, Size, Text, XoSelect } from 'editable'
import { Select, Toggle } from 'form'
import { SelectResourceSet, SelectVgpuType } from 'select-objects'
import {
SelectResourceSet,
SelectRole,
SelectSubject,
SelectVgpuType,
} from 'select-objects'
import {
addSubscriptions,
connectStore,
formatSize,
getCoresPerSocketPossibilities,
getVirtualizationModeLabel,
noop,
osFamily,
} from 'utils'
import {
assign,
every,
filter,
find,
includes,
isEmpty,
keyBy,
map,
some,
uniq,
} from 'lodash'
import {
addAcl,
changeVirtualizationMode,
cloneVm,
convertVmToTemplate,
@ -34,11 +56,15 @@ import {
isVmRunning,
pauseVm,
recoveryStartVm,
removeAcl,
restartVm,
shareVm,
startVm,
stopVm,
subscribeAcls,
subscribeGroups,
subscribeResourceSets,
subscribeUsers,
suspendVm,
XEN_DEFAULT_CPU_CAP,
XEN_DEFAULT_CPU_WEIGHT,
@ -49,6 +75,11 @@ import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors'
// Button's height = react-select's height(36 px) + react-select's border-width(1 px) * 2
// https://github.com/JedWatson/react-select/blob/916ab0e62fc7394be8e24f22251c399a68de8b1c/less/select.less#L21, L22
const SHARE_BUTTON_STYLE = { height: '38px' }
const LEVELS = {
admin: 'danger',
operator: 'primary',
viewer: 'success',
}
const forceReboot = vm => restartVm(vm, true)
const forceShutdown = vm => stopVm(vm, true)
@ -288,6 +319,138 @@ class CoresPerSocket extends Component {
}
}
class AddAclsModal extends Component {
get value() {
return this.state
}
_getPredicate = createSelector(
() => this.props.acls,
() => this.props.vm,
(acls, object) => ({ id: subject, permission }) =>
permission !== 'admin' && !some(acls, { object, subject })
)
render() {
const { action, subjects } = this.state
return (
<form>
<div className='form-group'>
<SelectSubject
multi
onChange={this.linkState('subjects')}
predicate={this._getPredicate()}
value={subjects}
/>
</div>
<div className='form-group'>
<SelectRole onChange={this.linkState('action')} value={action} />
</div>
</form>
)
}
}
const Acls = decorate([
addSubscriptions({
acls: subscribeAcls,
groups: cb => subscribeGroups(groups => cb(keyBy(groups, 'id'))),
users: cb => subscribeUsers(users => cb(keyBy(users, 'id'))),
}),
provideState({
effects: {
addAcls: () => (state, { acls, vm }) =>
confirm({
title: _('vmAddAcls'),
body: <AddAclsModal acls={acls} vm={vm} />,
})
.then(({ action, subjects }) => {
if (action == null || isEmpty(subjects)) {
return error(_('addAclsErrorTitle'), _('addAclsErrorMessage'))
}
return (
Promise.all(
map(subjects, subject =>
addAcl({ subject, object: vm, action })
)
),
noop
)
})
.catch(
err =>
err && error(_('addAclsErrorTitle'), err.message || String(err))
),
removeAcl: (_, { currentTarget: { dataset } }) => (_, { vm: object }) =>
removeAcl({
action: dataset.action,
object,
subject: dataset.subject,
}),
},
computed: {
rawAcls: (_, { acls, vm }) => filter(acls, { object: vm }),
resolvedAcls: ({ rawAcls }, { users, groups }) => {
if (users === undefined && groups === undefined) {
return []
}
return rawAcls.map(({ subject, ...acl }) => ({
...acl,
subject:
(users !== undefined && users[subject]) ||
(groups !== undefined && groups[subject]),
}))
},
},
}),
injectState,
({ state: { resolvedAcls }, effects, vm }) => (
<Container>
{resolvedAcls.slice(0, 5).map(({ subject, action }) => (
<Row key={`${subject.id}.${action}`}>
<Col>
<span>{renderXoItem(subject)}</span>{' '}
<span className={`tag tag-pill tag-${LEVELS[action]}`}>
{action}
</span>{' '}
<Tooltip content={_('removeAcl')}>
<a
data-action={action}
data-subject={subject.id}
onClick={effects.removeAcl}
role='button'
>
<Icon icon='remove' />
</a>
</Tooltip>
</Col>
</Row>
))}
{resolvedAcls.length > 5 && (
<Row>
<Col>
<Link to={`settings/acls?s=object:${vm}`}>
{_('moreAcls', { nAcls: resolvedAcls.length - 5 })}
</Link>
</Col>
</Row>
)}
<Row>
<Col>
<ActionButton
btnStyle='primary'
handler={effects.addAcls}
icon='add'
size='small'
tooltip={_('vmAddAcls')}
/>
</Col>
</Row>
</Container>
),
])
const NIC_TYPE_OPTIONS = [
{
label: 'Realtek RTL819',
@ -777,6 +940,14 @@ export default class TabAdvanced extends Component {
)}
</td>
</tr>
{isAdmin && (
<tr>
<th>{_('vmAcls')}</th>
<td>
<Acls vm={vm.id} />
</td>
</tr>
)}
</tbody>
</table>
</Col>