feat(backup): report recipients configurable in backup settings (#4646)

Fixes #4581
This commit is contained in:
badrAZ
2019-11-25 14:49:17 +01:00
committed by Pierre Donias
parent cca945e05b
commit 19e10bbb53
7 changed files with 122 additions and 6 deletions

View File

@@ -7,6 +7,8 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [Backup NG] Make report recipients configurable in the backup settings [#4581](https://github.com/vatesfr/xen-orchestra/issues/4581) (PR [#4646](https://github.com/vatesfr/xen-orchestra/pull/4646))
### Bug fixes
> Users must be able to say: “I had this issue, happy to know it's fixed”
@@ -18,6 +20,7 @@
>
> Rule of thumb: add packages on top.
- xo-server-backup-reports v0.16.4
- @xen-orchestra/fs v0.10.2
- xo-server v5.53.0
- xo-web v5.53.0

View File

@@ -36,6 +36,7 @@
"node": ">=6"
},
"dependencies": {
"@xen-orchestra/defined": "^0.0.0",
"@xen-orchestra/log": "^0.2.0",
"human-format": "^0.10.0",
"lodash": "^4.13.1",

View File

@@ -2,6 +2,7 @@ import createLogger from '@xen-orchestra/log'
import humanFormat from 'human-format'
import moment from 'moment-timezone'
import { forEach, groupBy, startCase } from 'lodash'
import { get } from '@xen-orchestra/defined'
import pkg from '../package'
const logger = createLogger('xo:xo-server-backup-reports')
@@ -365,9 +366,10 @@ class BackupReportsXoPlugin {
})
}
async _ngVmHandler(log, { name: jobName }, schedule, force) {
async _ngVmHandler(log, { name: jobName, settings }, schedule, force) {
const xo = this._xo
const mailReceivers = get(() => settings[''].reportRecipients)
const { reportWhen, mode } = log.data || {}
const formatDate = createDateFormatter(schedule?.timezone)
@@ -390,6 +392,7 @@ class BackupReportsXoPlugin {
subject: `[Xen Orchestra] ${
log.status
} Backup report for ${jobName} ${STATUS_ICON[log.status]}`,
mailReceivers,
markdown: toMarkdown(markdown),
success: false,
nagiosMarkdown: `[Xen Orchestra] [${log.status}] Backup report for ${jobName} - Error : ${log.result.message}`,
@@ -643,6 +646,7 @@ class BackupReportsXoPlugin {
markdown.push('---', '', `*${pkg.name} v${pkg.version}*`)
return this._sendReport({
mailReceivers,
markdown: toMarkdown(markdown),
subject: `[Xen Orchestra] ${log.status} Backup report for ${jobName} ${
STATUS_ICON[log.status]
@@ -657,12 +661,18 @@ class BackupReportsXoPlugin {
})
}
_sendReport({ markdown, subject, success, nagiosMarkdown }) {
_sendReport({
mailReceivers = this._mailsReceivers,
markdown,
nagiosMarkdown,
subject,
success,
}) {
const xo = this._xo
return Promise.all([
xo.sendEmail !== undefined &&
xo.sendEmail({
to: this._mailsReceivers,
to: mailReceivers,
subject,
markdown,
}),

View File

@@ -78,6 +78,7 @@ type Settings = {|
exportRetention?: number,
offlineBackup?: boolean,
offlineSnapshot?: boolean,
reportRecipients?: Array<string>,
reportWhen?: ReportWhen,
snapshotRetention?: number,
timeout?: number,
@@ -151,6 +152,7 @@ const defaultSettings: Settings = {
fullInterval: 0,
offlineBackup: false,
offlineSnapshot: false,
reportRecipients: undefined,
reportWhen: 'failure',
snapshotRetention: 0,
timeout: 0,

View File

@@ -443,6 +443,7 @@ const messages = {
reportWhenAlways: 'Always',
reportWhenFailure: 'Failure',
reportWhenNever: 'Never',
reportRecipients: 'Report recipients',
reportWhen: 'Report when',
concurrency: 'Concurrency',
newBackupSelection: 'Select your backup type:',

View File

@@ -17,7 +17,7 @@ import { Container, Col, Row } from 'grid'
import { createGetObjectsOfType } from 'selectors'
import { flatten, includes, isEmpty, map, mapValues, omit, some } from 'lodash'
import { form } from 'modal'
import { generateId } from 'reaclette-utils'
import { generateId, linkState } from 'reaclette-utils'
import { injectIntl } from 'react-intl'
import { injectState, provideState } from 'reaclette'
import { Map } from 'immutable'
@@ -70,6 +70,73 @@ const DEFAULT_SCHEDULE = {
timezone: moment.tz.guess(),
}
const ReportRecipients = decorate([
provideState({
initialState: () => ({
recipient: '',
}),
effects: {
linkState,
add() {
this.props.add(this.state.recipient)
this.resetState()
},
onKeyDown({ add }, event) {
if (event.key === 'Enter') {
event.preventDefault()
add()
}
},
},
computed: {
inputId: generateId,
disabledAddButton: ({ recipient }) => recipient === '',
},
}),
injectState,
({ effects, recipients, remove, state }) => (
<div>
<FormGroup>
<label htmlFor={state.inputId}>
<strong>{_('reportRecipients')}</strong>
</label>
<div className='input-group'>
<Input
id={state.inputId}
name='recipient'
onChange={effects.linkState}
onKeyDown={effects.onKeyDown}
value={state.recipient}
/>
<span className='input-group-btn'>
<ActionButton
btnStyle='primary'
disabled={state.disabledAddButton}
handler={effects.add}
icon='add'
/>
</span>
</div>
</FormGroup>
<Ul className='mb-1'>
{map(recipients, (recipient, key) => (
<Li key={recipient}>
{recipient}{' '}
<ActionButton
btnStyle='danger'
className='pull-right'
handler={remove}
handlerParam={key}
icon='delete'
size='small'
/>
</Li>
))}
</Ul>
</div>
),
])
const SR_BACKEND_FAILURE_LINK =
'https://xen-orchestra.com/docs/backup_troubleshooting.html#srbackendfailure44-insufficient-space'
@@ -523,6 +590,27 @@ export default decorate([
[name]: value,
})),
}),
addReportRecipient({ setGlobalSettings }, value) {
const { propSettings, settings = propSettings } = this.state
const reportRecipients = defined(
settings.getIn(['', 'reportRecipients']),
[]
)
if (!reportRecipients.includes(value)) {
setGlobalSettings({
name: 'reportRecipients',
value: (reportRecipients.push(value), reportRecipients),
})
}
},
deleteReportRecipient({ setGlobalSettings }, key) {
const { propSettings, settings = propSettings } = this.state
const reportRecipients = settings.getIn(['', 'reportRecipients'])
setGlobalSettings({
name: 'reportRecipients',
value: (reportRecipients.splice(key, 1), reportRecipients),
})
},
setReportWhen: ({ setGlobalSettings }, { value }) => () => {
setGlobalSettings({
name: 'reportWhen',
@@ -668,7 +756,9 @@ export default decorate([
!isEmpty(
getSettingsWithNonDefaultValue(state.isFull ? 'full' : 'delta', {
compression: get(() => props.job.compression),
...get(() => omit(props.job.settings[''], 'reportWhen')),
...get(() =>
omit(props.job.settings[''], ['reportRecipients', 'reportWhen'])
),
})
)
),
@@ -684,6 +774,7 @@ export default decorate([
fullInterval,
offlineBackup,
offlineSnapshot,
reportRecipients,
reportWhen = 'failure',
timeout,
} = settings.get('') || {}
@@ -933,6 +1024,11 @@ export default decorate([
// https://github.com/vatesfr/xen-orchestra/commit/753ee994f2948bbaca9d3161eaab82329a682773#diff-9c044ab8a42ed6576ea927a64c1ec3ebR105
value={reportWhen === 'Never' ? 'never' : reportWhen}
/>
<ReportRecipients
add={effects.addReportRecipient}
recipients={reportRecipients}
remove={effects.deleteReportRecipient}
/>
{state.displayAdvancedSettings && (
<div>
<FormGroup>

View File

@@ -1,3 +1,4 @@
import classNames from 'classnames'
import Icon from 'icon'
import PropTypes from 'prop-types'
import React from 'react'
@@ -5,7 +6,9 @@ import { resolveId, resolveIds } from 'utils'
export const FormGroup = props => <div {...props} className='form-group' />
export const Input = props => <input {...props} className='form-control' />
export const Ul = props => <ul {...props} className='list-group' />
export const Ul = props => (
<ul {...props} className={classNames(props.className, 'list-group')} />
)
export const Li = props => <li {...props} className='list-group-item' />
export const destructPattern = pattern =>