feat(xo-web/metadata-backups): metadata logs implementation (#4014)
Fixes #4005
This commit is contained in:
parent
88160bae1d
commit
865d2df124
@ -8,6 +8,7 @@
|
|||||||
- [Backup NG/Overview] Make backup list title clearer [#4111](https://github.com/vatesfr/xen-orchestra/issues/4111) (PR [#4129](https://github.com/vatesfr/xen-orchestra/pull/4129))
|
- [Backup NG/Overview] Make backup list title clearer [#4111](https://github.com/vatesfr/xen-orchestra/issues/4111) (PR [#4129](https://github.com/vatesfr/xen-orchestra/pull/4129))
|
||||||
- [Dashboard] Hide "Report" section for non-admins [#4123](https://github.com/vatesfr/xen-orchestra/issues/4123) (PR [#4126](https://github.com/vatesfr/xen-orchestra/pull/4126))
|
- [Dashboard] Hide "Report" section for non-admins [#4123](https://github.com/vatesfr/xen-orchestra/issues/4123) (PR [#4126](https://github.com/vatesfr/xen-orchestra/pull/4126))
|
||||||
- [VM migration] Auto select default SR and collapse optional actions [#3326](https://github.com/vatesfr/xen-orchestra/issues/3326) (PR [#4121](https://github.com/vatesfr/xen-orchestra/pull/4121))
|
- [VM migration] Auto select default SR and collapse optional actions [#3326](https://github.com/vatesfr/xen-orchestra/issues/3326) (PR [#4121](https://github.com/vatesfr/xen-orchestra/pull/4121))
|
||||||
|
- [Metadata backup] Logs [#4005](https://github.com/vatesfr/xen-orchestra/issues/4005) (PR [#4014](https://github.com/vatesfr/xen-orchestra/pull/4014))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ export const LogStatus = ({ log, tooltip = _('logDisplayDetails') }) => {
|
|||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
btnStyle={className}
|
btnStyle={className}
|
||||||
disabled={log.status !== 'failure' && isEmpty(log.tasks)}
|
|
||||||
handler={showTasks}
|
handler={showTasks}
|
||||||
handlerParam={log.id}
|
handlerParam={log.id}
|
||||||
icon='preview'
|
icon='preview'
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
import _, { FormattedDuration } from 'intl'
|
import _, { FormattedDuration } from 'intl'
|
||||||
import ActionButton from 'action-button'
|
import ActionButton from 'action-button'
|
||||||
import decorate from 'apply-decorators'
|
import decorate from 'apply-decorators'
|
||||||
|
import defined, { get } from '@xen-orchestra/defined'
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Select from 'form/select'
|
import Select from 'form/select'
|
||||||
import Tooltip from 'tooltip'
|
import Tooltip from 'tooltip'
|
||||||
import { addSubscriptions, formatSize, formatSpeed } from 'utils'
|
import { addSubscriptions, formatSize, formatSpeed } from 'utils'
|
||||||
import { countBy, filter, get, keyBy, map } from 'lodash'
|
import { countBy, cloneDeep, filter, keyBy, map } from 'lodash'
|
||||||
import { FormattedDate } from 'react-intl'
|
import { FormattedDate } from 'react-intl'
|
||||||
import { injectState, provideState } from 'reaclette'
|
import { injectState, provideState } from 'reaclette'
|
||||||
import { runBackupNgJob, subscribeBackupNgLogs, subscribeRemotes } from 'xo'
|
import { runBackupNgJob, subscribeBackupNgLogs, subscribeRemotes } from 'xo'
|
||||||
import { Vm, Sr, Remote } from 'render-xo-item'
|
import { Vm, Sr, Remote, Pool } from 'render-xo-item'
|
||||||
|
|
||||||
|
const hasTaskFailed = ({ status }) =>
|
||||||
|
status !== 'success' && status !== 'pending'
|
||||||
|
|
||||||
const TASK_STATUS = {
|
const TASK_STATUS = {
|
||||||
failure: {
|
failure: {
|
||||||
@ -44,9 +48,7 @@ const TaskStateInfos = ({ status }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TaskDate = ({ label, value }) =>
|
const TaskDate = ({ value }) => (
|
||||||
_.keyValue(
|
|
||||||
_(label),
|
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
value={new Date(value)}
|
value={new Date(value)}
|
||||||
month='short'
|
month='short'
|
||||||
@ -56,7 +58,65 @@ const TaskDate = ({ label, value }) =>
|
|||||||
minute='2-digit'
|
minute='2-digit'
|
||||||
second='2-digit'
|
second='2-digit'
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const TaskStart = ({ task }) => (
|
||||||
|
<div>{_.keyValue(_('taskStart'), <TaskDate value={task.start} />)}</div>
|
||||||
|
)
|
||||||
|
const TaskEnd = ({ task }) =>
|
||||||
|
task.end !== undefined ? (
|
||||||
|
<div>{_.keyValue(_('taskEnd'), <TaskDate value={task.end} />)}</div>
|
||||||
|
) : null
|
||||||
|
const TaskDuration = ({ task }) =>
|
||||||
|
task.end !== undefined ? (
|
||||||
|
<div>
|
||||||
|
{_.keyValue(
|
||||||
|
_('taskDuration'),
|
||||||
|
<FormattedDuration duration={task.end - task.start} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain'
|
||||||
|
const UNHEALTHY_VDI_CHAIN_LINK =
|
||||||
|
'https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection'
|
||||||
|
|
||||||
|
const TaskError = ({ task }) => {
|
||||||
|
let message
|
||||||
|
if (
|
||||||
|
!hasTaskFailed(task) ||
|
||||||
|
(message = defined(() => task.result.message, () => task.result.code)) ===
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message === UNHEALTHY_VDI_CHAIN_ERROR) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip content={_('clickForMoreInformation')}>
|
||||||
|
<a
|
||||||
|
className='text-info'
|
||||||
|
href={UNHEALTHY_VDI_CHAIN_LINK}
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<Icon icon='info' /> {_('unhealthyVdiChainError')}
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [label, className] =
|
||||||
|
task.status === 'skipped'
|
||||||
|
? [_('taskReason'), 'text-info']
|
||||||
|
: [_('taskError'), 'text-danger']
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>{_.keyValue(label, <span className={className}>{message}</span>)}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Warnings = ({ warnings }) =>
|
const Warnings = ({ warnings }) =>
|
||||||
warnings !== undefined ? (
|
warnings !== undefined ? (
|
||||||
@ -72,9 +132,168 @@ const Warnings = ({ warnings }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
const UNHEALTHY_VDI_CHAIN_ERROR = 'unhealthy VDI chain'
|
const VmTask = ({ children, restartVmJob, task }) => (
|
||||||
const UNHEALTHY_VDI_CHAIN_LINK =
|
<div>
|
||||||
'https://xen-orchestra.com/docs/backup_troubleshooting.html#vdi-chain-protection'
|
<Vm id={task.data.id} link newTab /> <TaskStateInfos status={task.status} />{' '}
|
||||||
|
{restartVmJob !== undefined && hasTaskFailed(task) && (
|
||||||
|
<ActionButton
|
||||||
|
handler={restartVmJob}
|
||||||
|
icon='run'
|
||||||
|
size='small'
|
||||||
|
tooltip={_('backupRestartVm')}
|
||||||
|
data-vm={task.data.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
{children}
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
{task.transfer !== undefined && (
|
||||||
|
<div>
|
||||||
|
{_.keyValue(
|
||||||
|
_('taskTransferredDataSize'),
|
||||||
|
formatSize(task.transfer.size)
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
{_.keyValue(
|
||||||
|
_('taskTransferredDataSpeed'),
|
||||||
|
formatSpeed(task.transfer.size, task.transfer.duration)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{task.merge !== undefined && (
|
||||||
|
<div>
|
||||||
|
{_.keyValue(_('taskMergedDataSize'), formatSize(task.merge.size))}
|
||||||
|
<br />
|
||||||
|
{_.keyValue(
|
||||||
|
_('taskMergedDataSpeed'),
|
||||||
|
formatSpeed(task.merge.size, task.merge.duration)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{task.isFull !== undefined &&
|
||||||
|
_.keyValue(_('exportType'), task.isFull ? 'full' : 'delta')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const PoolTask = ({ children, task }) => (
|
||||||
|
<div>
|
||||||
|
<Pool id={task.data.id} link newTab />{' '}
|
||||||
|
<TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
{children}
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const XoTask = ({ children, task }) => (
|
||||||
|
<div>
|
||||||
|
<Icon icon='menu-xoa' /> XO <TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
{children}
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SnapshotTask = ({ task }) => (
|
||||||
|
<div>
|
||||||
|
<Icon icon='task' /> {_('snapshotVmLabel')}{' '}
|
||||||
|
<TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const RemoteTask = ({ children, task }) => (
|
||||||
|
<div>
|
||||||
|
<Remote id={task.data.id} link newTab />{' '}
|
||||||
|
<TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
{children}
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SrTask = ({ children, task }) => (
|
||||||
|
<div>
|
||||||
|
<Sr id={task.data.id} link newTab /> <TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
{children}
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const TransferMergeTask = ({ task }) => {
|
||||||
|
const size = get(() => task.result.size)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Icon icon='task' /> {task.message}{' '}
|
||||||
|
<TaskStateInfos status={task.status} />
|
||||||
|
<Warnings warnings={task.warnings} />
|
||||||
|
<TaskStart task={task} />
|
||||||
|
<TaskEnd task={task} />
|
||||||
|
<TaskDuration task={task} />
|
||||||
|
<TaskError task={task} />
|
||||||
|
{size > 0 && (
|
||||||
|
<div>
|
||||||
|
{_.keyValue(_('operationSize'), formatSize(size))}
|
||||||
|
<br />
|
||||||
|
{_.keyValue(
|
||||||
|
_('operationSpeed'),
|
||||||
|
formatSpeed(size, task.end - task.start)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMPONENT_BY_TYPE = {
|
||||||
|
vm: VmTask,
|
||||||
|
remote: RemoteTask,
|
||||||
|
sr: SrTask,
|
||||||
|
pool: PoolTask,
|
||||||
|
xo: XoTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMPONENT_BY_MESSAGE = {
|
||||||
|
snapshot: SnapshotTask,
|
||||||
|
merge: TransferMergeTask,
|
||||||
|
transfer: TransferMergeTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskLi = ({ className, task, ...props }) => {
|
||||||
|
let Component
|
||||||
|
if (
|
||||||
|
(Component = defined(
|
||||||
|
() => COMPONENT_BY_TYPE[task.data.type.toLowerCase()],
|
||||||
|
COMPONENT_BY_MESSAGE[task.message]
|
||||||
|
)) === undefined
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<li className={className}>
|
||||||
|
<Component task={task} {...props} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default decorate([
|
export default decorate([
|
||||||
addSubscriptions(({ id }) => ({
|
addSubscriptions(({ id }) => ({
|
||||||
@ -107,10 +326,39 @@ export default decorate([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredTaskLogs: (
|
log: (_, { log }) => {
|
||||||
{ defaultFilter, filter: value = defaultFilter },
|
if (log === undefined) {
|
||||||
{ log = {} }
|
return {}
|
||||||
) =>
|
}
|
||||||
|
|
||||||
|
if (log.tasks === undefined) {
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
let newLog
|
||||||
|
log.tasks.forEach((task, key) => {
|
||||||
|
if (task.tasks === undefined || get(() => task.data.type) !== 'VM') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const subTaskWithIsFull = task.tasks.find(
|
||||||
|
({ data = {} }) => data.isFull !== undefined
|
||||||
|
)
|
||||||
|
if (subTaskWithIsFull !== undefined) {
|
||||||
|
if (newLog === undefined) {
|
||||||
|
newLog = cloneDeep(log)
|
||||||
|
}
|
||||||
|
newLog.tasks[key].isFull = subTaskWithIsFull.data.isFull
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return defined(newLog, log)
|
||||||
|
},
|
||||||
|
filteredTaskLogs: ({
|
||||||
|
defaultFilter,
|
||||||
|
filter: value = defaultFilter,
|
||||||
|
log,
|
||||||
|
}) =>
|
||||||
value === 'all'
|
value === 'all'
|
||||||
? log.tasks
|
? log.tasks
|
||||||
: filter(log.tasks, ({ status }) => status === value),
|
: filter(log.tasks, ({ status }) => status === value),
|
||||||
@ -119,8 +367,8 @@ export default decorate([
|
|||||||
{_(label)} ({countByStatus[value] || 0})
|
{_(label)} ({countByStatus[value] || 0})
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
countByStatus: (_, { log = {} }) => ({
|
countByStatus: ({ log }) => ({
|
||||||
all: get(log.tasks, 'length'),
|
all: get(() => log.tasks.length),
|
||||||
...countBy(log.tasks, 'status'),
|
...countBy(log.tasks, 'status'),
|
||||||
}),
|
}),
|
||||||
options: ({ countByStatus }) => [
|
options: ({ countByStatus }) => [
|
||||||
@ -169,13 +417,13 @@ export default decorate([
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
injectState,
|
injectState,
|
||||||
({ log = {}, remotes, state, effects }) => {
|
({ remotes, state, effects }) => {
|
||||||
const { status, result, scheduleId } = log
|
const { scheduleId, warnings, tasks = [] } = state.log
|
||||||
return (status === 'failure' || status === 'skipped') &&
|
return tasks.length === 0 ? (
|
||||||
result !== undefined ? (
|
<div>
|
||||||
<span className={status === 'skipped' ? 'text-info' : 'text-danger'}>
|
<Warnings warnings={warnings} />
|
||||||
<Icon icon='alarm' /> {result.message}
|
<TaskError task={state.log} />
|
||||||
</span>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
@ -188,238 +436,29 @@ export default decorate([
|
|||||||
value={state.filter || state.defaultFilter}
|
value={state.filter || state.defaultFilter}
|
||||||
valueKey='value'
|
valueKey='value'
|
||||||
/>
|
/>
|
||||||
<Warnings warnings={log.warnings} />
|
<Warnings warnings={warnings} />
|
||||||
<br />
|
<br />
|
||||||
<ul className='list-group'>
|
<ul className='list-group'>
|
||||||
{map(state.filteredTaskLogs, taskLog => {
|
{map(state.filteredTaskLogs, taskLog => {
|
||||||
let globalIsFull
|
|
||||||
return (
|
return (
|
||||||
<li key={taskLog.data.id} className='list-group-item'>
|
<TaskLi
|
||||||
<Vm id={taskLog.data.id} link newTab /> (
|
className='list-group-item'
|
||||||
{taskLog.data.id.slice(4, 8)}){' '}
|
key={taskLog.id}
|
||||||
<TaskStateInfos status={taskLog.status} />{' '}
|
restartVmJob={scheduleId && effects.restartVmJob}
|
||||||
{scheduleId !== undefined &&
|
task={taskLog}
|
||||||
taskLog.status !== 'success' &&
|
|
||||||
taskLog.status !== 'pending' && (
|
|
||||||
<ActionButton
|
|
||||||
handler={effects.restartVmJob}
|
|
||||||
icon='run'
|
|
||||||
size='small'
|
|
||||||
tooltip={_('backupRestartVm')}
|
|
||||||
data-vm={taskLog.data.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Warnings warnings={taskLog.warnings} />
|
|
||||||
<ul>
|
|
||||||
{map(taskLog.tasks, subTaskLog => {
|
|
||||||
if (
|
|
||||||
subTaskLog.message !== 'export' &&
|
|
||||||
subTaskLog.message !== 'snapshot'
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFull = get(subTaskLog.data, 'isFull')
|
|
||||||
if (isFull !== undefined && globalIsFull === undefined) {
|
|
||||||
globalIsFull = isFull
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<li key={subTaskLog.id}>
|
|
||||||
{subTaskLog.message === 'snapshot' ? (
|
|
||||||
<span>
|
|
||||||
<Icon icon='task' /> {_('snapshotVmLabel')}
|
|
||||||
</span>
|
|
||||||
) : subTaskLog.data.type === 'remote' ? (
|
|
||||||
<span>
|
|
||||||
<Remote id={subTaskLog.data.id} link newTab /> (
|
|
||||||
{subTaskLog.data.id.slice(4, 8)})
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
<Sr id={subTaskLog.data.id} link newTab /> (
|
|
||||||
{subTaskLog.data.id.slice(4, 8)})
|
|
||||||
</span>
|
|
||||||
)}{' '}
|
|
||||||
<TaskStateInfos status={subTaskLog.status} />
|
|
||||||
<Warnings warnings={subTaskLog.warnings} />
|
|
||||||
<ul>
|
|
||||||
{map(subTaskLog.tasks, operationLog => {
|
|
||||||
if (
|
|
||||||
operationLog.message !== 'merge' &&
|
|
||||||
operationLog.message !== 'transfer'
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={operationLog.id}>
|
|
||||||
<span>
|
|
||||||
<Icon icon='task' /> {operationLog.message}
|
|
||||||
</span>{' '}
|
|
||||||
<TaskStateInfos status={operationLog.status} />
|
|
||||||
<Warnings warnings={operationLog.warnings} />
|
|
||||||
<br />
|
|
||||||
<TaskDate
|
|
||||||
label='taskStart'
|
|
||||||
value={operationLog.start}
|
|
||||||
/>
|
|
||||||
{operationLog.end !== undefined && (
|
|
||||||
<div>
|
|
||||||
<TaskDate
|
|
||||||
label='taskEnd'
|
|
||||||
value={operationLog.end}
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskDuration'),
|
|
||||||
<FormattedDuration
|
|
||||||
duration={
|
|
||||||
operationLog.end - operationLog.start
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{operationLog.status === 'failure'
|
|
||||||
? _.keyValue(
|
|
||||||
_('taskError'),
|
|
||||||
<span className='text-danger'>
|
|
||||||
{operationLog.result.message}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: operationLog.result.size > 0 && (
|
|
||||||
<div>
|
|
||||||
{_.keyValue(
|
|
||||||
_('operationSize'),
|
|
||||||
formatSize(
|
|
||||||
operationLog.result.size
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{_.keyValue(
|
|
||||||
_('operationSpeed'),
|
|
||||||
formatSpeed(
|
|
||||||
operationLog.result.size,
|
|
||||||
operationLog.end -
|
|
||||||
operationLog.start
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
<TaskDate label='taskStart' value={subTaskLog.start} />
|
|
||||||
{subTaskLog.end !== undefined && (
|
|
||||||
<div>
|
|
||||||
<TaskDate label='taskEnd' value={subTaskLog.end} />
|
|
||||||
<br />
|
|
||||||
{subTaskLog.message !== 'snapshot' &&
|
|
||||||
_.keyValue(
|
|
||||||
_('taskDuration'),
|
|
||||||
<FormattedDuration
|
|
||||||
duration={subTaskLog.end - subTaskLog.start}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{subTaskLog.status === 'failure' &&
|
|
||||||
subTaskLog.result !== undefined &&
|
|
||||||
_.keyValue(
|
|
||||||
_('taskError'),
|
|
||||||
<span className='text-danger'>
|
|
||||||
{subTaskLog.result.message}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
<TaskDate label='taskStart' value={taskLog.start} />
|
|
||||||
<br />
|
|
||||||
{taskLog.end !== undefined && (
|
|
||||||
<div>
|
|
||||||
<TaskDate label='taskEnd' value={taskLog.end} />
|
|
||||||
<br />
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskDuration'),
|
|
||||||
<FormattedDuration
|
|
||||||
duration={taskLog.end - taskLog.start}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{taskLog.result !== undefined ? (
|
|
||||||
taskLog.result.message === UNHEALTHY_VDI_CHAIN_ERROR ? (
|
|
||||||
<Tooltip content={_('clickForMoreInformation')}>
|
|
||||||
<a
|
|
||||||
className='text-info'
|
|
||||||
href={UNHEALTHY_VDI_CHAIN_LINK}
|
|
||||||
rel='noopener noreferrer'
|
|
||||||
target='_blank'
|
|
||||||
>
|
>
|
||||||
<Icon icon='info' /> {_('unhealthyVdiChainError')}
|
<ul>
|
||||||
</a>
|
{map(taskLog.tasks, subTaskLog => (
|
||||||
</Tooltip>
|
<TaskLi key={subTaskLog.id} task={subTaskLog}>
|
||||||
) : (
|
<ul>
|
||||||
_.keyValue(
|
{map(subTaskLog.tasks, subSubTaskLog => (
|
||||||
taskLog.status === 'skipped'
|
<TaskLi task={subSubTaskLog} key={subSubTaskLog.id} />
|
||||||
? _('taskReason')
|
))}
|
||||||
: _('taskError'),
|
</ul>
|
||||||
<span
|
</TaskLi>
|
||||||
className={
|
))}
|
||||||
taskLog.status === 'skipped'
|
</ul>
|
||||||
? 'text-info'
|
</TaskLi>
|
||||||
: 'text-danger'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{taskLog.result.message}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{taskLog.transfer !== undefined && (
|
|
||||||
<div>
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskTransferredDataSize'),
|
|
||||||
formatSize(taskLog.transfer.size)
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskTransferredDataSpeed'),
|
|
||||||
formatSpeed(
|
|
||||||
taskLog.transfer.size,
|
|
||||||
taskLog.transfer.duration
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{taskLog.merge !== undefined && (
|
|
||||||
<div>
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskMergedDataSize'),
|
|
||||||
formatSize(taskLog.merge.size)
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskMergedDataSpeed'),
|
|
||||||
formatSpeed(
|
|
||||||
taskLog.merge.size,
|
|
||||||
taskLog.merge.duration
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{globalIsFull !== undefined &&
|
|
||||||
_.keyValue(_('exportType'), globalIsFull ? 'full' : 'delta')}
|
|
||||||
</li>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user