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))
|
- 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))
|
- [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))
|
- 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
|
### 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 _, { FormattedDuration } from 'intl'
|
||||||
import addSubscriptions from 'add-subscriptions'
|
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 Icon from 'icon'
|
||||||
import NoObjects from 'no-objects'
|
import NoObjects from 'no-objects'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button'
|
||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
|
import Tooltip from 'tooltip'
|
||||||
import { alert } from 'modal'
|
import { alert } from 'modal'
|
||||||
import { Card, CardHeader, CardBlock } from 'card'
|
import { Card, CardHeader, CardBlock } from 'card'
|
||||||
import { keyBy } from 'lodash'
|
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(
|
alert(
|
||||||
<span>
|
<span>
|
||||||
{get(() => jobs[log.jobId].name) || 'Job'} ({log.jobId.slice(4, 8)}){' '}
|
{get(() => jobs[log.jobId].name) || 'Job'} ({log.jobId.slice(4, 8)}){' '}
|
||||||
<span style={{ fontSize: '0.5em' }} className='text-muted'>
|
<span style={{ fontSize: '0.5em' }} className='text-muted'>
|
||||||
{log.id}
|
{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>,
|
</span>,
|
||||||
<LogAlertBody id={log.id} />
|
<LogAlertBody id={log.id} />
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const LOG_INDIVIDUAL_ACTIONS = [
|
const LOG_INDIVIDUAL_ACTIONS = [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import _, { FormattedDuration } from 'intl'
|
import _, { FormattedDuration } from 'intl'
|
||||||
import ActionButton from 'action-button'
|
import ActionButton from 'action-button'
|
||||||
import Copiable from 'copiable'
|
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
|
import renderXoItem, { renderXoItemFromId } from 'render-xo-item'
|
||||||
@ -148,9 +147,7 @@ export default [
|
|||||||
return (status === 'failure' || status === 'skipped') &&
|
return (status === 'failure' || status === 'skipped') &&
|
||||||
result !== undefined ? (
|
result !== undefined ? (
|
||||||
<span className={status === 'skipped' ? 'text-info' : 'text-danger'}>
|
<span className={status === 'skipped' ? 'text-info' : 'text-danger'}>
|
||||||
<Copiable tagName='p' data={JSON.stringify(result, null, 2)}>
|
<Icon icon='alarm' /> {result.message}
|
||||||
<Icon icon='alarm' /> {result.message}
|
|
||||||
</Copiable>
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
@ -258,41 +255,32 @@ export default [
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
{operationLog.status === 'failure' ? (
|
{operationLog.status === 'failure'
|
||||||
<Copiable
|
? _.keyValue(
|
||||||
tagName='p'
|
|
||||||
data={JSON.stringify(
|
|
||||||
operationLog.result,
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskError'),
|
_('taskError'),
|
||||||
<span className='text-danger'>
|
<span className='text-danger'>
|
||||||
{operationLog.result.message}
|
{operationLog.result.message}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
@ -313,22 +301,12 @@ export default [
|
|||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
{subTaskLog.status === 'failure' &&
|
{subTaskLog.status === 'failure' &&
|
||||||
subTaskLog.result !== undefined && (
|
subTaskLog.result !== undefined &&
|
||||||
<Copiable
|
_.keyValue(
|
||||||
tagName='p'
|
_('taskError'),
|
||||||
data={JSON.stringify(
|
<span className='text-danger'>
|
||||||
subTaskLog.result,
|
{subTaskLog.result.message}
|
||||||
null,
|
</span>
|
||||||
2
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{_.keyValue(
|
|
||||||
_('taskError'),
|
|
||||||
<span className='text-danger'>
|
|
||||||
{subTaskLog.result.message}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Copiable>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -362,25 +340,20 @@ export default [
|
|||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Copiable
|
_.keyValue(
|
||||||
tagName='p'
|
taskLog.status === 'skipped'
|
||||||
data={JSON.stringify(taskLog.result, null, 2)}
|
? _('taskReason')
|
||||||
>
|
: _('taskError'),
|
||||||
{_.keyValue(
|
<span
|
||||||
taskLog.status === 'skipped'
|
className={
|
||||||
? _('taskReason')
|
taskLog.status === 'skipped'
|
||||||
: _('taskError'),
|
? 'text-info'
|
||||||
<span
|
: 'text-danger'
|
||||||
className={
|
}
|
||||||
taskLog.status === 'skipped'
|
>
|
||||||
? 'text-info'
|
{taskLog.result.message}
|
||||||
: 'text-danger'
|
</span>
|
||||||
}
|
)
|
||||||
>
|
|
||||||
{taskLog.result.message}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Copiable>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
|
@ -8,6 +8,7 @@ import BaseComponent from 'base-component'
|
|||||||
import ButtonGroup from 'button-group'
|
import ButtonGroup from 'button-group'
|
||||||
import Copiable from 'copiable'
|
import Copiable from 'copiable'
|
||||||
import NoObjects from 'no-objects'
|
import NoObjects from 'no-objects'
|
||||||
|
import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button'
|
||||||
import SortedTable from 'sorted-table'
|
import SortedTable from 'sorted-table'
|
||||||
import styles from './index.css'
|
import styles from './index.css'
|
||||||
import TabButton from 'tab-button'
|
import TabButton from 'tab-button'
|
||||||
@ -16,27 +17,12 @@ import { alert, confirm } from 'modal'
|
|||||||
import { createSelector } from 'selectors'
|
import { createSelector } from 'selectors'
|
||||||
import { subscribeApiLogs, subscribeUsers, deleteApiLog } from 'xo'
|
import { subscribeApiLogs, subscribeUsers, deleteApiLog } from 'xo'
|
||||||
|
|
||||||
const CAN_REPORT_BUG = process.env.XOA_PLAN > 1
|
const formatMessage = data =>
|
||||||
|
`\`\`\`\n${data.method}\n${JSON.stringify(
|
||||||
const reportBug = log => {
|
data.params,
|
||||||
const title = encodeURIComponent(`Error on ${log.data.method}`)
|
null,
|
||||||
const message = encodeURIComponent(
|
2
|
||||||
`\`\`\`\n${log.data.method}\n${JSON.stringify(
|
)}\n${JSON.stringify(data.error, null, 2).replace(/\\n/g, '\n')}\n\`\`\``
|
||||||
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 COLUMNS = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
@ -102,10 +88,11 @@ const COLUMNS = [
|
|||||||
tooltip={_('logDelete')}
|
tooltip={_('logDelete')}
|
||||||
/>
|
/>
|
||||||
{CAN_REPORT_BUG && (
|
{CAN_REPORT_BUG && (
|
||||||
<ActionRowButton
|
<ReportBugButton
|
||||||
handler={() => reportBug(log)}
|
message={log.data}
|
||||||
icon='bug'
|
formatMessage={formatMessage}
|
||||||
tooltip={_('reportBug')}
|
rowButton
|
||||||
|
title={`Error on ${log.data.method}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user