diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4244e64a1..affdb9eef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/packages/xo-web/src/common/report-bug-button.js b/packages/xo-web/src/common/report-bug-button.js
new file mode 100644
index 000000000..f46f75a8b
--- /dev/null
+++ b/packages/xo-web/src/common/report-bug-button.js
@@ -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 (
+
+ )
+}
+
+propTypes(ReportBugButton)({
+ formatMessage: propTypes.func,
+ message: propTypes.string.isRequired,
+ rowButton: propTypes.bool,
+ title: propTypes.string.isRequired,
+})
+
+export default ReportBugButton
diff --git a/packages/xo-web/src/xo-app/logs/backup-ng-logs.js b/packages/xo-web/src/xo-app/logs/backup-ng-logs.js
index 232688b89..bac3c73d6 100644
--- a/packages/xo-web/src/xo-app/logs/backup-ng-logs.js
+++ b/packages/xo-web/src/xo-app/logs/backup-ng-logs.js
@@ -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(
{get(() => jobs[log.jobId].name) || 'Job'} ({log.jobId.slice(4, 8)}){' '}
{log.id}
-
+ {' '}
+ {log.status !== 'success' &&
+ log.status !== 'pending' && (
+
+
+
+
+
+
+ {CAN_REPORT_BUG && (
+
+ )}
+
+ )}
,
)
+}
const LOG_INDIVIDUAL_ACTIONS = [
{
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 439cc3f49..216a890b3 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
@@ -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 ? (
-
- {result.message}
-
+ {result.message}
) : (
@@ -258,41 +255,32 @@ export default [
/>
)}
- {operationLog.status === 'failure' ? (
-
- {_.keyValue(
+ {operationLog.status === 'failure'
+ ? _.keyValue(
_('taskError'),
{operationLog.result.message}
+ )
+ : operationLog.result.size > 0 && (
+
+ {_.keyValue(
+ _('operationSize'),
+ formatSize(
+ operationLog.result.size
+ )
+ )}
+
+ {_.keyValue(
+ _('operationSpeed'),
+ formatSpeed(
+ operationLog.result.size,
+ operationLog.end -
+ operationLog.start
+ )
+ )}
+
)}
-
- ) : (
- operationLog.result.size > 0 && (
-
- {_.keyValue(
- _('operationSize'),
- formatSize(operationLog.result.size)
- )}
-
- {_.keyValue(
- _('operationSpeed'),
- formatSpeed(
- operationLog.result.size,
- operationLog.end -
- operationLog.start
- )
- )}
-
- )
- )}
)}
@@ -313,22 +301,12 @@ export default [
)}
{subTaskLog.status === 'failure' &&
- subTaskLog.result !== undefined && (
-
- {_.keyValue(
- _('taskError'),
-
- {subTaskLog.result.message}
-
- )}
-
+ subTaskLog.result !== undefined &&
+ _.keyValue(
+ _('taskError'),
+
+ {subTaskLog.result.message}
+
)}
)}
@@ -362,25 +340,20 @@ export default [
) : (
-
- {_.keyValue(
- taskLog.status === 'skipped'
- ? _('taskReason')
- : _('taskError'),
-
- {taskLog.result.message}
-
- )}
-
+ _.keyValue(
+ taskLog.status === 'skipped'
+ ? _('taskReason')
+ : _('taskError'),
+
+ {taskLog.result.message}
+
+ )
)
) : (
diff --git a/packages/xo-web/src/xo-app/settings/logs/index.js b/packages/xo-web/src/xo-app/settings/logs/index.js
index d4c4a31de..f154b754d 100644
--- a/packages/xo-web/src/xo-app/settings/logs/index.js
+++ b/packages/xo-web/src/xo-app/settings/logs/index.js
@@ -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 && (
-
reportBug(log)}
- icon='bug'
- tooltip={_('reportBug')}
+
)}