feat(backup): report recipients configurable in backup settings (#4646)
Fixes #4581
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user