feat(xo-web/backup-ng/logs): link XO objects (#3171)

See #2711 #2605
This commit is contained in:
badrAZ 2018-07-12 13:22:38 +02:00 committed by Pierre Donias
parent 3241c426a2
commit 3370014ddf
4 changed files with 127 additions and 65 deletions

View File

@ -11,6 +11,7 @@
- [Backup NG form] Add offline snapshot info (PR [#3144](https://github.com/vatesfr/xen-orchestra/pull/3144))
- [Backup NG overview] Display concurrency and offline snapshot value [3087](https://github.com/vatesfr/xen-orchestra/issues/3087) (PR [3145](https://github.com/vatesfr/xen-orchestra/pull/3145))
- [VM revert] notify the result of reverting a VM [3095](https://github.com/vatesfr/xen-orchestra/issues/3095) (PR [3150](https://github.com/vatesfr/xen-orchestra/pull/3150))
- [Backup NG logs] Link XO items in the details modal [#2711](https://github.com/vatesfr/xen-orchestra/issues/2711) (PR [#3171](https://github.com/vatesfr/xen-orchestra/pull/3171))
### Bug fixes

View File

@ -12,6 +12,7 @@ const messages = {
statusLoading: 'Loading…',
errorPageNotFound: 'Page not found',
errorNoSuchItem: 'no such item',
errorUnknownItem: 'unknown item',
editableLongClickPlaceholder: 'Long click to edit',
editableClickPlaceholder: 'Click to edit',

View File

@ -1,13 +1,16 @@
import _ from 'intl'
import PropTypes from 'prop-types'
import React from 'react'
import { startsWith } from 'lodash'
import Icon from './icon'
import Link from './link'
import propTypes from './prop-types-decorator'
import { createGetObject } from './selectors'
import { addSubscriptions, connectStore, formatSize } from './utils'
import { createGetObject, createSelector } from './selectors'
import { FormattedDate } from 'react-intl'
import { isSrWritable } from './xo'
import { connectStore, formatSize } from './utils'
import { get } from './xo-defined'
import { isSrWritable, subscribeRemotes } from './xo'
// ===================================================================
@ -17,6 +20,112 @@ const OBJECT_TYPE_TO_ICON = {
network: 'network',
}
const COMMON_PROP_TYPES = {
link: PropTypes.bool,
}
const XoItem = ({ children, item, link, to }) =>
item !== undefined ? (
link ? (
<Link to={to} target='_blank'>
{children()}
</Link>
) : (
children()
)
) : (
<span className='text-muted'>{_('errorUnknownItem')}</span>
)
XoItem.propTypes = {
...COMMON_PROP_TYPES,
item: PropTypes.object,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
}
// ===================================================================
const XO_ITEM_PROP_TYPES = {
...COMMON_PROP_TYPES,
id: PropTypes.string.isRequired,
}
export const VmItem = [
connectStore(() => {
const getVm = createGetObject()
return {
vm: getVm,
container: createGetObject(
createSelector(getVm, vm => get(() => vm.$container))
),
}
}),
({ vm, container, ...props }) => (
<XoItem item={vm} to={`/vms/${get(() => vm.id)}`} {...props}>
{() => (
<span>
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} />{' '}
{vm.name_label || vm.id}
{container !== undefined &&
` (${container.name_label || container.id})`}
</span>
)}
</XoItem>
),
].reduceRight((value, decorator) => decorator(value))
VmItem.propTypes = XO_ITEM_PROP_TYPES
export const SrItem = [
connectStore(() => {
const getSr = createGetObject()
return {
sr: getSr,
container: createGetObject(
createSelector(getSr, sr => get(() => sr.$container))
),
}
}),
({ sr, container, ...props }) => (
<XoItem item={sr} to={`/srs/${get(() => sr.id)}`} {...props}>
{() => (
<span>
<Icon icon='sr' /> {sr.name_label || sr.id}
{container !== undefined && (
<span className='text-muted'> - {container.name_label}</span>
)}
{isSrWritable(sr) && (
<span>{` (${formatSize(sr.size - sr.physical_usage)} free)`}</span>
)}
</span>
)}
</XoItem>
),
].reduceRight((value, decorator) => decorator(value))
SrItem.propTypes = XO_ITEM_PROP_TYPES
export const RemoteItem = [
addSubscriptions(({ id }) => ({
remote: cb =>
subscribeRemotes(remotes => {
cb(get(() => remotes.find(remote => remote.id === id)))
}),
})),
({ remote, ...props }) => (
<XoItem item={remote} to='/settings/remotes' {...props}>
{() => (
<span>
<Icon icon='remote' /> {remote.name}
</span>
)}
</XoItem>
),
].reduceRight((value, decorator) => decorator(value))
RemoteItem.propTypes = XO_ITEM_PROP_TYPES
// ===================================================================
// Host, Network, VM-template.
const PoolObjectItem = propTypes({
object: propTypes.object.isRequired,
@ -40,48 +149,6 @@ const PoolObjectItem = propTypes({
})
)
// SR.
const SrItem = propTypes({
sr: propTypes.object.isRequired,
})(
connectStore(() => {
const getContainer = createGetObject((_, props) => props.sr.$container)
return (state, props) => ({
container: getContainer(state, props),
})
})(({ sr, container }) => (
<span>
<Icon icon='sr' /> {sr.name_label || sr.id}
{container !== undefined && (
<span className='text-muted'> - {container.name_label}</span>
)}
{isSrWritable(sr) && (
<span>{` (${formatSize(sr.size - sr.physical_usage)} free)`}</span>
)}
</span>
))
)
// VM.
const VmItem = propTypes({
vm: propTypes.object.isRequired,
})(
connectStore(() => {
const getContainer = createGetObject((_, props) => props.vm.$container)
return (state, props) => ({
container: getContainer(state, props),
})
})(({ vm, container }) => (
<span>
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} />{' '}
{vm.name_label || vm.id}
{container && ` (${container.name_label || container.id})`}
</span>
))
)
const VgpuItem = connectStore(() => ({
vgpuType: createGetObject((_, props) => props.vgpu.vgpuType),
}))(({ vgpu, vgpuType }) => (
@ -104,11 +171,7 @@ const xoItemToRender = {
<Icon icon='group' /> {group.name}
</span>
),
remote: remote => (
<span>
<Icon icon='remote' /> {remote.value.name}
</span>
),
remote: ({ value: { id } }) => <RemoteItem id={id} />,
role: role => <span>{role.name}</span>,
user: user => (
<span>
@ -160,14 +223,14 @@ const xoItemToRender = {
network: network => <PoolObjectItem object={network} />,
// SR.
SR: sr => <SrItem sr={sr} />,
SR: ({ id }) => <SrItem id={id} />,
// VM.
VM: vm => <VmItem vm={vm} />,
'VM-snapshot': vm => <VmItem vm={vm} />,
'VM-controller': vm => (
VM: ({ id }) => <VmItem id={id} />,
'VM-snapshot': ({ id }) => <VmItem id={id} />,
'VM-controller': ({ id }) => (
<span>
<Icon icon='host' /> <VmItem vm={vm} />
<Icon icon='host' /> <VmItem id={id} />
</span>
),

View File

@ -2,7 +2,6 @@ import _, { FormattedDuration } from 'intl'
import ActionButton from 'action-button'
import Icon from 'icon'
import React from 'react'
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
import Select from 'form/select'
import Tooltip from 'tooltip'
import { addSubscriptions, formatSize, formatSpeed } from 'utils'
@ -10,6 +9,7 @@ import { countBy, filter, get, keyBy, map } from 'lodash'
import { FormattedDate } from 'react-intl'
import { injectState, provideState } from '@julien-f/freactal'
import { runBackupNgJob, subscribeBackupNgLogs, subscribeRemotes } from 'xo'
import { VmItem, SrItem, RemoteItem } from 'render-xo-item'
const TASK_STATUS = {
failure: {
@ -166,7 +166,7 @@ export default [
let globalIsFull
return (
<li key={taskLog.data.id} className='list-group-item'>
{renderXoItemFromId(taskLog.data.id)} ({taskLog.data.id.slice(
<VmItem id={taskLog.data.id} link /> ({taskLog.data.id.slice(
4,
8
)}) <TaskStateInfos status={taskLog.status} />{' '}
@ -202,17 +202,14 @@ export default [
</span>
) : subTaskLog.data.type === 'remote' ? (
<span>
{get(remotes, subTaskLog.data.id) !== undefined
? renderXoItem({
type: 'remote',
value: remotes[subTaskLog.data.id],
})
: _('errorNoSuchItem')}{' '}
({subTaskLog.data.id.slice(4, 8)})
<RemoteItem id={subTaskLog.data.id} link /> ({subTaskLog.data.id.slice(
4,
8
)})
</span>
) : (
<span>
{renderXoItemFromId(subTaskLog.data.id)} ({subTaskLog.data.id.slice(
<SrItem id={subTaskLog.data.id} link /> ({subTaskLog.data.id.slice(
4,
8
)})