diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index c86ffa64b..41ba60206 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -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)) - [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)) +- [Metadata backup] Logs [#4005](https://github.com/vatesfr/xen-orchestra/issues/4005) (PR [#4014](https://github.com/vatesfr/xen-orchestra/pull/4014)) ### Bug fixes diff --git a/packages/xo-web/src/xo-app/logs/backup-ng/index.js b/packages/xo-web/src/xo-app/logs/backup-ng/index.js index 848062099..ef3bb7a14 100644 --- a/packages/xo-web/src/xo-app/logs/backup-ng/index.js +++ b/packages/xo-web/src/xo-app/logs/backup-ng/index.js @@ -38,7 +38,6 @@ export const LogStatus = ({ log, tooltip = _('logDisplayDetails') }) => { return ( + status !== 'success' && status !== 'pending' const TASK_STATUS = { failure: { @@ -44,19 +48,75 @@ const TaskStateInfos = ({ status }) => { ) } -const TaskDate = ({ label, value }) => - _.keyValue( - _(label), - +const TaskDate = ({ value }) => ( + +) + +const TaskStart = ({ task }) => ( +
{_.keyValue(_('taskStart'), )}
+) +const TaskEnd = ({ task }) => + task.end !== undefined ? ( +
{_.keyValue(_('taskEnd'), )}
+ ) : null +const TaskDuration = ({ task }) => + task.end !== undefined ? ( +
+ {_.keyValue( + _('taskDuration'), + + )} +
+ ) : 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 ( +
+ + + {_('unhealthyVdiChainError')} + + +
+ ) + } + + const [label, className] = + task.status === 'skipped' + ? [_('taskReason'), 'text-info'] + : [_('taskError'), 'text-danger'] + + return ( +
{_.keyValue(label, {message})}
) +} const Warnings = ({ warnings }) => warnings !== undefined ? ( @@ -72,9 +132,168 @@ const Warnings = ({ warnings }) => ) : 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 VmTask = ({ children, restartVmJob, task }) => ( +
+ {' '} + {restartVmJob !== undefined && hasTaskFailed(task) && ( + + )} + + {children} + + + + + {task.transfer !== undefined && ( +
+ {_.keyValue( + _('taskTransferredDataSize'), + formatSize(task.transfer.size) + )} +
+ {_.keyValue( + _('taskTransferredDataSpeed'), + formatSpeed(task.transfer.size, task.transfer.duration) + )} +
+ )} + {task.merge !== undefined && ( +
+ {_.keyValue(_('taskMergedDataSize'), formatSize(task.merge.size))} +
+ {_.keyValue( + _('taskMergedDataSpeed'), + formatSpeed(task.merge.size, task.merge.duration) + )} +
+ )} + {task.isFull !== undefined && + _.keyValue(_('exportType'), task.isFull ? 'full' : 'delta')} +
+) + +const PoolTask = ({ children, task }) => ( +
+ {' '} + + + {children} + + + + +
+) + +const XoTask = ({ children, task }) => ( +
+ XO + + {children} + + + + +
+) + +const SnapshotTask = ({ task }) => ( +
+ {_('snapshotVmLabel')}{' '} + + + + + +
+) + +const RemoteTask = ({ children, task }) => ( +
+ {' '} + + + {children} + + + + +
+) + +const SrTask = ({ children, task }) => ( +
+ + + {children} + + + + +
+) + +const TransferMergeTask = ({ task }) => { + const size = get(() => task.result.size) + return ( +
+ {task.message}{' '} + + + + + + + {size > 0 && ( +
+ {_.keyValue(_('operationSize'), formatSize(size))} +
+ {_.keyValue( + _('operationSpeed'), + formatSpeed(size, task.end - task.start) + )} +
+ )} +
+ ) +} + +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 ( +
  • + +
  • + ) +} export default decorate([ addSubscriptions(({ id }) => ({ @@ -107,10 +326,39 @@ export default decorate([ }, }, computed: { - filteredTaskLogs: ( - { defaultFilter, filter: value = defaultFilter }, - { log = {} } - ) => + log: (_, { log }) => { + if (log === undefined) { + 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' ? log.tasks : filter(log.tasks, ({ status }) => status === value), @@ -119,8 +367,8 @@ export default decorate([ {_(label)} ({countByStatus[value] || 0}) ), - countByStatus: (_, { log = {} }) => ({ - all: get(log.tasks, 'length'), + countByStatus: ({ log }) => ({ + all: get(() => log.tasks.length), ...countBy(log.tasks, 'status'), }), options: ({ countByStatus }) => [ @@ -169,13 +417,13 @@ export default decorate([ }, }), injectState, - ({ log = {}, remotes, state, effects }) => { - const { status, result, scheduleId } = log - return (status === 'failure' || status === 'skipped') && - result !== undefined ? ( - - {result.message} - + ({ remotes, state, effects }) => { + const { scheduleId, warnings, tasks = [] } = state.log + return tasks.length === 0 ? ( +
    + + +
    ) : (