feat(xo-web): add button to download log (#3985)

Fixes #3957
This commit is contained in:
Enishowk 2019-02-27 10:02:30 +01:00 committed by Pierre Donias
parent 344e9e06d0
commit 76ae54ff05
6 changed files with 56 additions and 19 deletions

View File

@ -6,6 +6,7 @@
- [Home/VM] Sort VM by start time [#3955](https://github.com/vatesfr/xen-orchestra/issues/3955) (PR [#3970](https://github.com/vatesfr/xen-orchestra/pull/3970)) - [Home/VM] Sort VM by start time [#3955](https://github.com/vatesfr/xen-orchestra/issues/3955) (PR [#3970](https://github.com/vatesfr/xen-orchestra/pull/3970))
- [Editable fields] Unfocusing (clicking outside) submits the change instead of canceling (PR [#3980](https://github.com/vatesfr/xen-orchestra/pull/3980)) - [Editable fields] Unfocusing (clicking outside) submits the change instead of canceling (PR [#3980](https://github.com/vatesfr/xen-orchestra/pull/3980))
- [Network] Dedicated page for network creation [#3895](https://github.com/vatesfr/xen-orchestra/issues/3895) (PR [#3906](https://github.com/vatesfr/xen-orchestra/pull/3906)) - [Network] Dedicated page for network creation [#3895](https://github.com/vatesfr/xen-orchestra/issues/3895) (PR [#3906](https://github.com/vatesfr/xen-orchestra/pull/3906))
- [Logs] Add button to download the log [#3957](https://github.com/vatesfr/xen-orchestra/issues/3957) (PR [#3985](https://github.com/vatesfr/xen-orchestra/pull/3985))
### Bug fixes ### Bug fixes

View File

@ -1874,6 +1874,7 @@ const messages = {
logError: 'Error', logError: 'Error',
logTitle: 'Logs', logTitle: 'Logs',
logDisplayDetails: 'Display details', logDisplayDetails: 'Display details',
logDownload: 'Download log',
logTime: 'Date', logTime: 'Date',
logNoStackTrace: 'No stack trace', logNoStackTrace: 'No stack trace',
logNoParams: 'No params', logNoParams: 'No params',

View File

@ -601,3 +601,20 @@ export const getIscsiPaths = pbd => {
const pathsInfo = pbd.otherConfig[`mpath-${pbd.device_config.SCSIid}`] const pathsInfo = pbd.otherConfig[`mpath-${pbd.device_config.SCSIid}`]
return pathsInfo !== undefined ? JSON.parse(pathsInfo) : [] return pathsInfo !== undefined ? JSON.parse(pathsInfo) : []
} }
// ===================================================================
export const downloadLog = ({ log, date, type }) => {
const file = new window.Blob([log], {
type: 'text/plain',
})
const anchor = document.createElement('a')
anchor.href = window.URL.createObjectURL(file)
anchor.download = `${new Date(date)
.toISOString()
.replace(/:/g, '_')} - ${type}.log`
anchor.style.display = 'none'
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
}

View File

@ -108,6 +108,10 @@
@extend .fa; @extend .fa;
@extend .fa-clipboard; @extend .fa-clipboard;
} }
&-download {
@extend .fa;
@extend .fa-download;
}
&-shortcuts { &-shortcuts {
@extend .fa; @extend .fa;
@extend .fa-keyboard-o; @extend .fa-keyboard-o;
@ -489,7 +493,8 @@
} }
// SR // SR
&-sr, &-vdi { &-sr,
&-vdi {
&-reconnect-all { &-reconnect-all {
@extend .fa; @extend .fa;
@extend .fa-retweet; @extend .fa-retweet;
@ -563,7 +568,8 @@
} }
// Host and VM actions // Host and VM actions
&-host, &-vm { &-host,
&-vm {
&-start { &-start {
@extend .fa; @extend .fa;
@extend .fa-play; @extend .fa-play;
@ -611,12 +617,12 @@
&-filters { &-filters {
@extend .fa; @extend .fa;
@extend .fa-filter @extend .fa-filter;
} }
&-tags { &-tags {
@extend .fa; @extend .fa;
@extend .fa-tags @extend .fa-tags;
} }
&-remove-tag { &-remove-tag {
@ -656,11 +662,11 @@
} }
&-minus { &-minus {
@extend .fa; @extend .fa;
@extend .fa-minus @extend .fa-minus;
} }
&-plus { &-plus {
@extend .fa; @extend .fa;
@extend .fa-plus @extend .fa-plus;
} }
&-clear-search { &-clear-search {
@extend .fa; @extend .fa;
@ -871,7 +877,7 @@
&-new-vm { &-new-vm {
&-infos { &-infos {
@extend .fa; @extend .fa;
@extend .fa-info-circle @extend .fa-info-circle;
} }
&-perf { &-perf {
@extend .fa; @extend .fa;
@ -990,7 +996,7 @@
color: #ccc; color: #ccc;
} }
// About // About
&-bug { &-bug {
@extend .fa; @extend .fa;
@extend .fa-bug; @extend .fa-bug;

View File

@ -9,6 +9,7 @@ import Icon from 'icon'
import React from 'react' import React from 'react'
import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button' import ReportBugButton, { CAN_REPORT_BUG } from 'report-bug-button'
import Tooltip from 'tooltip' import Tooltip from 'tooltip'
import { downloadLog } from 'utils'
import { get } from '@xen-orchestra/defined' import { get } from '@xen-orchestra/defined'
import { injectState, provideState } from 'reaclette' import { injectState, provideState } from 'reaclette'
import { keyBy } from 'lodash' import { keyBy } from 'lodash'
@ -28,6 +29,8 @@ export default decorate([
})), })),
provideState({ provideState({
effects: { effects: {
_downloadLog: () => ({ formattedLog }, { log }) =>
downloadLog({ log: formattedLog, date: log.start, type: 'backup NG' }),
restartFailedVms: () => async ( restartFailedVms: () => async (
_, _,
{ log: { jobId: id, scheduleId: schedule, tasks, infos } } { log: { jobId: id, scheduleId: schedule, tasks, infos } }
@ -81,6 +84,11 @@ export default decorate([
</Button> </Button>
</CopyToClipboard> </CopyToClipboard>
</Tooltip> </Tooltip>
<Tooltip content={_('logDownload')}>
<Button size='small' onClick={effects._downloadLog}>
<Icon icon='download' />
</Button>
</Tooltip>
{CAN_REPORT_BUG && ( {CAN_REPORT_BUG && (
<ReportBugButton <ReportBugButton
message={`\`\`\`json\n${state.formattedLog}\n\`\`\``} message={`\`\`\`json\n${state.formattedLog}\n\`\`\``}

View File

@ -8,7 +8,7 @@ import Copiable from 'copiable'
import NoObjects from 'no-objects' import NoObjects from 'no-objects'
import SortedTable from 'sorted-table' import SortedTable from 'sorted-table'
import styles from './index.css' import styles from './index.css'
import { addSubscriptions } from 'utils' import { addSubscriptions, downloadLog } from 'utils'
import { alert } from 'modal' import { alert } from 'modal'
import { createSelector } from 'selectors' import { createSelector } from 'selectors'
import { CAN_REPORT_BUG, reportBug } from 'report-bug-button' import { CAN_REPORT_BUG, reportBug } from 'report-bug-button'
@ -26,6 +26,13 @@ const formatMessage = data =>
2 2
)}\n${JSON.stringify(data.error, null, 2).replace(/\\n/g, '\n')}\n\`\`\`` )}\n${JSON.stringify(data.error, null, 2).replace(/\\n/g, '\n')}\n\`\`\``
const formatLog = log =>
`${log.data.method}\n${JSON.stringify(
log.data.params,
null,
2
)}\n${JSON.stringify(log.data.error, null, 2).replace(/\\n/g, '\n')}`
const COLUMNS = [ const COLUMNS = [
{ {
name: _('logUser'), name: _('logUser'),
@ -87,19 +94,16 @@ const ACTIONS = [
const INDIVIDUAL_ACTIONS = [ const INDIVIDUAL_ACTIONS = [
{ {
handler: log => handler: log =>
alert( alert(_('logError'), <Copiable tagName='pre'>{formatLog(log)}</Copiable>),
_('logError'),
<Copiable tagName='pre'>
{`${log.data.method}\n${JSON.stringify(
log.data.params,
null,
2
)}\n${JSON.stringify(log.data.error, null, 2).replace(/\\n/g, '\n')}`}
</Copiable>
),
icon: 'preview', icon: 'preview',
label: _('logDisplayDetails'), label: _('logDisplayDetails'),
}, },
{
handler: log =>
downloadLog({ log: formatLog(log), date: log.time, type: 'XO' }),
icon: 'download',
label: _('logDownload'),
},
{ {
disabled: !CAN_REPORT_BUG, disabled: !CAN_REPORT_BUG,
handler: log => handler: log =>