diff --git a/CHANGELOG.md b/CHANGELOG.md index bc21701ac..1b2396d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index a2809f66f..b76fdd878 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -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', diff --git a/packages/xo-web/src/common/render-xo-item.js b/packages/xo-web/src/common/render-xo-item.js index 82c0a54f6..7427de351 100644 --- a/packages/xo-web/src/common/render-xo-item.js +++ b/packages/xo-web/src/common/render-xo-item.js @@ -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 ? ( + + {children()} + + ) : ( + children() + ) + ) : ( + {_('errorUnknownItem')} + ) + +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 }) => ( + vm.id)}`} {...props}> + {() => ( + + {' '} + {vm.name_label || vm.id} + {container !== undefined && + ` (${container.name_label || container.id})`} + + )} + + ), +].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 }) => ( + sr.id)}`} {...props}> + {() => ( + + {sr.name_label || sr.id} + {container !== undefined && ( + - {container.name_label} + )} + {isSrWritable(sr) && ( + {` (${formatSize(sr.size - sr.physical_usage)} free)`} + )} + + )} + + ), +].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 }) => ( + + {() => ( + + {remote.name} + + )} + + ), +].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 }) => ( - - {sr.name_label || sr.id} - {container !== undefined && ( - - {container.name_label} - )} - {isSrWritable(sr) && ( - {` (${formatSize(sr.size - sr.physical_usage)} free)`} - )} - - )) -) - -// 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 }) => ( - - {' '} - {vm.name_label || vm.id} - {container && ` (${container.name_label || container.id})`} - - )) -) - const VgpuItem = connectStore(() => ({ vgpuType: createGetObject((_, props) => props.vgpu.vgpuType), }))(({ vgpu, vgpuType }) => ( @@ -104,11 +171,7 @@ const xoItemToRender = { {group.name} ), - remote: remote => ( - - {remote.value.name} - - ), + remote: ({ value: { id } }) => , role: role => {role.name}, user: user => ( @@ -160,14 +223,14 @@ const xoItemToRender = { network: network => , // SR. - SR: sr => , + SR: ({ id }) => , // VM. - VM: vm => , - 'VM-snapshot': vm => , - 'VM-controller': vm => ( + VM: ({ id }) => , + 'VM-snapshot': ({ id }) => , + 'VM-controller': ({ id }) => ( - + ), diff --git a/packages/xo-web/src/xo-app/logs/log-alert-body.js b/packages/xo-web/src/xo-app/logs/log-alert-body.js index 216a890b3..013c09270 100644 --- a/packages/xo-web/src/xo-app/logs/log-alert-body.js +++ b/packages/xo-web/src/xo-app/logs/log-alert-body.js @@ -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 (
  • - {renderXoItemFromId(taskLog.data.id)} ({taskLog.data.id.slice( + ({taskLog.data.id.slice( 4, 8 )}) {' '} @@ -202,17 +202,14 @@ export default [ ) : subTaskLog.data.type === 'remote' ? ( - {get(remotes, subTaskLog.data.id) !== undefined - ? renderXoItem({ - type: 'remote', - value: remotes[subTaskLog.data.id], - }) - : _('errorNoSuchItem')}{' '} - ({subTaskLog.data.id.slice(4, 8)}) + ({subTaskLog.data.id.slice( + 4, + 8 + )}) ) : ( - {renderXoItemFromId(subTaskLog.data.id)} ({subTaskLog.data.id.slice( + ({subTaskLog.data.id.slice( 4, 8 )})