feat(xo-web/backup-ng/logs): copy full log and report a failed job (#3110)
Fixes #3100
This commit is contained in:
parent
358e1441cc
commit
40568cd61f
@ -21,6 +21,7 @@
|
||||
- Add legacy backups snapshots to backup/health [#3082](https://github.com/vatesfr/xen-orchestra/issues/3082) (PR [#3111](https://github.com/vatesfr/xen-orchestra/pull/3111))
|
||||
- [Backup NG logs] Add the job's name to the modal's title [#2711](https://github.com/vatesfr/xen-orchestra/issues/2711) (PR [#3115](https://github.com/vatesfr/xen-orchestra/pull/3115))
|
||||
- Adding a XCP-ng host to a XS pool now fails fast [#3061](https://github.com/vatesfr/xen-orchestra/issues/3061) (PR [#3118](https://github.com/vatesfr/xen-orchestra/pull/3118))
|
||||
- [Backup NG logs] Ability to report a failed job and copy its log to the clipboard [#3100](https://github.com/vatesfr/xen-orchestra/issues/3100) (PR [#3110](https://github.com/vatesfr/xen-orchestra/pull/3110))
|
||||
|
||||
### Bugs
|
||||
|
||||
|
51
packages/xo-web/src/common/report-bug-button.js
Normal file
51
packages/xo-web/src/common/report-bug-button.js
Normal file
@ -0,0 +1,51 @@
|
||||
import _ from 'intl'
|
||||
import React from 'react'
|
||||
|
||||
import ActionButton from './action-button'
|
||||
import ActionRowButton from './action-row-button'
|
||||
import propTypes from './prop-types-decorator'
|
||||
|
||||
export const CAN_REPORT_BUG = process.env.XOA_PLAN > 1
|
||||
|
||||
const reportBug = ({ formatMessage, message, title }) => {
|
||||
const encodedTitle = encodeURIComponent(title)
|
||||
const encodedMessage = encodeURIComponent(
|
||||
formatMessage !== undefined ? formatMessage(message) : message
|
||||
)
|
||||
|
||||
window.open(
|
||||
process.env.XOA_PLAN < 5
|
||||
? `https://xen-orchestra.com/#!/member/support?title=${encodedTitle}&message=${encodedMessage}`
|
||||
: `https://github.com/vatesfr/xen-orchestra/issues/new?title=${encodedTitle}&body=${encodedMessage}`
|
||||
)
|
||||
}
|
||||
|
||||
const ReportBugButton = ({
|
||||
formatMessage,
|
||||
message,
|
||||
rowButton,
|
||||
title,
|
||||
...props
|
||||
}) => {
|
||||
const Button = rowButton ? ActionRowButton : ActionButton
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
data-formatMessage={formatMessage}
|
||||
data-message={message}
|
||||
data-title={title}
|
||||
handler={reportBug}
|
||||
icon='bug'
|
||||
tooltip={_('reportBug')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
propTypes(ReportBugButton)({
|
||||
formatMessage: propTypes.func,
|
||||
message: propTypes.string.isRequired,
|
||||
rowButton: propTypes.bool,
|
||||
title: propTypes.string.isRequired,
|
||||
})
|
||||
|
||||
export default ReportBugButton
|
@ -1,9 +1,14 @@
|
||||
import _, { FormattedDuration } from 'intl'
|
||||
import addSubscriptions from 'add-subscriptions'
|
||||
import Button from 'button'
|
||||
import ButtonGroup from 'button-group'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import Icon from 'icon'
|
||||
import NoObjects from 'no-objects'
|
||||
import React from 'react'
|
||||
import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button'
|
||||
import SortedTable from 'sorted-table'
|
||||
import Tooltip from 'tooltip'
|
||||
import { alert } from 'modal'
|
||||
import { Card, CardHeader, CardBlock } from 'card'
|
||||
import { keyBy } from 'lodash'
|
||||
@ -102,16 +107,37 @@ const LOG_COLUMNS = [
|
||||
},
|
||||
]
|
||||
|
||||
const showTasks = (log, { jobs }) =>
|
||||
const showTasks = (log, { jobs }) => {
|
||||
const formattedLog = JSON.stringify(log, null, 2)
|
||||
alert(
|
||||
<span>
|
||||
{get(() => jobs[log.jobId].name) || 'Job'} ({log.jobId.slice(4, 8)}){' '}
|
||||
<span style={{ fontSize: '0.5em' }} className='text-muted'>
|
||||
{log.id}
|
||||
</span>
|
||||
</span>{' '}
|
||||
{log.status !== 'success' &&
|
||||
log.status !== 'pending' && (
|
||||
<ButtonGroup>
|
||||
<Tooltip content={_('copyToClipboard')}>
|
||||
<CopyToClipboard text={formattedLog}>
|
||||
<Button size='small'>
|
||||
<Icon icon='clipboard' />
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Tooltip>
|
||||
{CAN_REPORT_BUG && (
|
||||
<ReportBugButton
|
||||
message={`\`\`\`json\n${formattedLog}\n\`\`\``}
|
||||
size='small'
|
||||
title='Backup job failed'
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</span>,
|
||||
<LogAlertBody id={log.id} />
|
||||
)
|
||||
}
|
||||
|
||||
const LOG_INDIVIDUAL_ACTIONS = [
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
import _, { FormattedDuration } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Copiable from 'copiable'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
|
||||
@ -148,9 +147,7 @@ export default [
|
||||
return (status === 'failure' || status === 'skipped') &&
|
||||
result !== undefined ? (
|
||||
<span className={status === 'skipped' ? 'text-info' : 'text-danger'}>
|
||||
<Copiable tagName='p' data={JSON.stringify(result, null, 2)}>
|
||||
<Icon icon='alarm' /> {result.message}
|
||||
</Copiable>
|
||||
<Icon icon='alarm' /> {result.message}
|
||||
</span>
|
||||
) : (
|
||||
<div>
|
||||
@ -258,41 +255,32 @@ export default [
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
{operationLog.status === 'failure' ? (
|
||||
<Copiable
|
||||
tagName='p'
|
||||
data={JSON.stringify(
|
||||
operationLog.result,
|
||||
null,
|
||||
2
|
||||
)}
|
||||
>
|
||||
{_.keyValue(
|
||||
{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>
|
||||
)}
|
||||
</Copiable>
|
||||
) : (
|
||||
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>
|
||||
@ -313,22 +301,12 @@ export default [
|
||||
)}
|
||||
<br />
|
||||
{subTaskLog.status === 'failure' &&
|
||||
subTaskLog.result !== undefined && (
|
||||
<Copiable
|
||||
tagName='p'
|
||||
data={JSON.stringify(
|
||||
subTaskLog.result,
|
||||
null,
|
||||
2
|
||||
)}
|
||||
>
|
||||
{_.keyValue(
|
||||
_('taskError'),
|
||||
<span className='text-danger'>
|
||||
{subTaskLog.result.message}
|
||||
</span>
|
||||
)}
|
||||
</Copiable>
|
||||
subTaskLog.result !== undefined &&
|
||||
_.keyValue(
|
||||
_('taskError'),
|
||||
<span className='text-danger'>
|
||||
{subTaskLog.result.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -362,25 +340,20 @@ export default [
|
||||
</a>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Copiable
|
||||
tagName='p'
|
||||
data={JSON.stringify(taskLog.result, null, 2)}
|
||||
>
|
||||
{_.keyValue(
|
||||
taskLog.status === 'skipped'
|
||||
? _('taskReason')
|
||||
: _('taskError'),
|
||||
<span
|
||||
className={
|
||||
taskLog.status === 'skipped'
|
||||
? 'text-info'
|
||||
: 'text-danger'
|
||||
}
|
||||
>
|
||||
{taskLog.result.message}
|
||||
</span>
|
||||
)}
|
||||
</Copiable>
|
||||
_.keyValue(
|
||||
taskLog.status === 'skipped'
|
||||
? _('taskReason')
|
||||
: _('taskError'),
|
||||
<span
|
||||
className={
|
||||
taskLog.status === 'skipped'
|
||||
? 'text-info'
|
||||
: 'text-danger'
|
||||
}
|
||||
>
|
||||
{taskLog.result.message}
|
||||
</span>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<div>
|
||||
|
@ -8,6 +8,7 @@ import BaseComponent from 'base-component'
|
||||
import ButtonGroup from 'button-group'
|
||||
import Copiable from 'copiable'
|
||||
import NoObjects from 'no-objects'
|
||||
import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button'
|
||||
import SortedTable from 'sorted-table'
|
||||
import styles from './index.css'
|
||||
import TabButton from 'tab-button'
|
||||
@ -16,27 +17,12 @@ import { alert, confirm } from 'modal'
|
||||
import { createSelector } from 'selectors'
|
||||
import { subscribeApiLogs, subscribeUsers, deleteApiLog } from 'xo'
|
||||
|
||||
const CAN_REPORT_BUG = process.env.XOA_PLAN > 1
|
||||
|
||||
const reportBug = log => {
|
||||
const title = encodeURIComponent(`Error on ${log.data.method}`)
|
||||
const message = encodeURIComponent(
|
||||
`\`\`\`\n${log.data.method}\n${JSON.stringify(
|
||||
log.data.params,
|
||||
null,
|
||||
2
|
||||
)}\n${JSON.stringify(log.data.error, null, 2).replace(
|
||||
/\\n/g,
|
||||
'\n'
|
||||
)}\n\`\`\``
|
||||
)
|
||||
|
||||
window.open(
|
||||
process.env.XOA_PLAN < 5
|
||||
? `https://xen-orchestra.com/#!/member/support?title=${title}&message=${message}`
|
||||
: `https://github.com/vatesfr/xen-orchestra/issues/new?title=${title}&body=${message}`
|
||||
)
|
||||
}
|
||||
const formatMessage = data =>
|
||||
`\`\`\`\n${data.method}\n${JSON.stringify(
|
||||
data.params,
|
||||
null,
|
||||
2
|
||||
)}\n${JSON.stringify(data.error, null, 2).replace(/\\n/g, '\n')}\n\`\`\``
|
||||
|
||||
const COLUMNS = [
|
||||
{
|
||||
@ -102,10 +88,11 @@ const COLUMNS = [
|
||||
tooltip={_('logDelete')}
|
||||
/>
|
||||
{CAN_REPORT_BUG && (
|
||||
<ActionRowButton
|
||||
handler={() => reportBug(log)}
|
||||
icon='bug'
|
||||
tooltip={_('reportBug')}
|
||||
<ReportBugButton
|
||||
message={log.data}
|
||||
formatMessage={formatMessage}
|
||||
rowButton
|
||||
title={`Error on ${log.data.method}`}
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user