feat(xo-web/jobs/new): use SortedTable (#2670)

See #2416
This commit is contained in:
Rajaa.BARHTAOUI 2018-04-06 16:45:46 +02:00 committed by Pierre Donias
parent 3cef668a75
commit 172ce2c7a1
3 changed files with 104 additions and 80 deletions

View File

@ -256,7 +256,6 @@ const messages = {
jobMergedDataSpeed: 'Merge speed:',
allJobCalls: 'All',
job: 'Job',
jobEdit: 'Edit job',
jobModalTitle: 'Job {job}',
jobId: 'ID',
jobType: 'Type',
@ -278,6 +277,8 @@ const messages = {
runJob: 'Run job',
runJobConfirm: 'Are you sure you want to run {backupType} {id} ({tag})?',
runJobVerbose: 'One shot running started. See overview for logs.',
jobEdit: 'Edit job',
jobDelete: 'Delete',
jobFinished: 'Finished',
jobInterrupted: 'Interrupted',
jobStarted: 'Started',
@ -287,6 +288,7 @@ const messages = {
deleteBackupSchedule: 'Remove backup job',
deleteBackupScheduleQuestion:
'Are you sure you want to delete this backup job?',
deleteSelectedJobs: 'Delete selected jobs',
scheduleEnableAfterCreation: 'Enable immediately after creation',
scheduleEditMessage:
'You are editing Schedule {name} ({id}). Saving will override previous schedule state.',
@ -1324,6 +1326,9 @@ const messages = {
chooseSrForEachVdisModalVdiLabel: 'VDI',
chooseSrForEachVdisModalSrLabel: 'SR*',
chooseSrForEachVdisModalOptionalEntry: '* optional',
deleteJobsModalTitle: 'Delete job{nJobs, plural, one {} other {s}}',
deleteJobsModalMessage:
'Are you sure you want to delete {nJobs, number} job{nJobs, plural, one {} other {s}}?',
deleteVbdsModalTitle: 'Delete VBD{nVbds, plural, one {} other {s}}',
deleteVbdsModalMessage:
'Are you sure you want to delete {nVbds, number} VBD{nVbds, plural, one {} other {s}}?',

View File

@ -1617,14 +1617,26 @@ export const createJob = job =>
export const deleteJob = job =>
_call('job.delete', { id: resolveId(job) })::tap(subscribeJobs.forceRefresh)
export const deleteJobs = jobs =>
confirm({
title: _('deleteJobsModalTitle', { nJobs: jobs.length }),
body: _('deleteJobsModalMessage', { nJobs: jobs.length }),
}).then(
() =>
Promise.all(
map(jobs, job => _call('job.delete', { id: resolveId(job) }))
)::tap(subscribeJobs.forceRefresh),
noop
)
export const editJob = job =>
_call('job.set', { job })::tap(subscribeJobs.forceRefresh)
export const getJob = id => _call('job.get', { id })
export const runJob = id => {
export const runJob = job => {
info(_('runJob'), _('runJobVerbose'))
return _call('job.runSequence', { idSequence: [id] })
return _call('job.runSequence', { idSequence: [resolveId(job)] })
}
// Backup/Schedule ---------------------------------------------------------

View File

@ -1,21 +1,13 @@
import _, { messages } from 'intl'
import ActionButton from 'action-button'
import ActionRowButton from 'action-row-button'
import Button from 'button'
import Component from 'base-component'
import defined from 'xo-defined'
import delay from 'lodash/delay'
import find from 'lodash/find'
import forEach from 'lodash/forEach'
import GenericInput from 'json-schema-input'
import Icon from 'icon'
import includes from 'lodash/includes'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import mapValues from 'lodash/mapValues'
import React from 'react'
import Select from 'form/select'
import size from 'lodash/size'
import SortedTable from 'sorted-table'
import Tooltip from 'tooltip'
import Upgrade from 'xoa-upgrade'
import { addSubscriptions } from 'utils'
@ -24,11 +16,20 @@ import { error } from 'notification'
import { generateUiSchema } from 'xo-json-schema-input'
import { injectIntl } from 'react-intl'
import { SelectSubject } from 'select-objects'
import {
delay,
find,
forEach,
includes,
isEmpty,
mapValues,
size,
} from 'lodash'
import {
apiMethods,
createJob,
deleteJob,
deleteJobs,
editJob,
runJob,
subscribeCurrentUser,
@ -38,6 +39,43 @@ import {
const JOB_KEY = 'genericTask'
const COLUMNS = [
{
itemRenderer: (job, { isJobUserMissing }) => {
const { id } = job
return (
<div>
{job.name} <span className='text-muted'>({id.slice(4, 8)})</span>
{isJobUserMissing[id] && (
<Tooltip content={_('jobUserNotFound')}>
<Icon className='ml-1' icon='error' />
</Tooltip>
)}
</div>
)
},
name: _('jobName'),
sortCriteria: 'name',
},
{
itemRenderer: job => job.method,
name: _('jobAction'),
sortCriteria: 'method',
},
]
const ACTIONS = [
{
handler: deleteJobs,
individualHandler: deleteJob,
individualLabel: _('jobDelete'),
icon: 'delete',
label: _('deleteSelectedJobs'),
level: 'danger',
},
]
const getType = function (param) {
if (!param) {
return
@ -297,16 +335,18 @@ export default class Jobs extends Component {
.catch(err => error('Create Job', err.message || String(err)))
}
_edit = id => {
const { jobs, actions } = this.state
const job = find(jobs, job => job.id === id)
if (!job) {
_edit = job => {
if (typeof job === 'string') {
job = find(this.state.jobs, { id: job })
}
if (job === undefined) {
error('Job edition', 'This job was not found, or may not longer exists.')
return
}
const { name, method } = this.refs
const action = find(actions, action => action.method === job.method)
const action = find(this.state.actions, { method: job.method })
name.value = job.name
method.value = action
this.setState(
@ -367,7 +407,7 @@ export default class Jobs extends Component {
(jobs, users) => {
const isJobUserMissing = {}
forEach(jobs, job => {
isJobUserMissing[job.id] = !!find(users, user => user.id === job.userId)
isJobUserMissing[job.id] = !find(users, user => user.id === job.userId)
})
return isJobUserMissing
@ -377,17 +417,31 @@ export default class Jobs extends Component {
_subjectPredicate = ({ type, permission }) =>
type === 'user' && permission === 'admin'
_individualActions = [
{
disabled: (job, { isJobUserMissing }) => isJobUserMissing[job.id],
handler: runJob,
icon: 'run-schedule',
label: _('runJob'),
level: 'warning',
},
{
handler: this._edit,
icon: 'edit',
label: _('jobEdit'),
level: 'primary',
},
]
render () {
const { props, state } = this
const { action, actions, job, jobs } = state
const { formatMessage } = this.props.intl
const isJobUserMissing = this._getIsJobUserMissing()
return (
<div>
<h1>{_('jobsPage')}</h1>
<form id='newJobForm'>
<form id='newJobForm' className='mb-2'>
<SelectSubject
onChange={this.linkState('owner', 'id')}
placeholder={_('jobOwnerPlaceholder')}
@ -455,64 +509,17 @@ export default class Jobs extends Component {
</fieldset>
)}
</form>
<table className='table'>
<thead>
<tr>
<th>{_('jobName')}</th>
<th>{_('jobAction')}</th>
<th />
<th />
</tr>
</thead>
<tbody>
{isEmpty(jobs) && (
<tr>
<td>
<em>{_('noJobs')}</em>
</td>
</tr>
)}
{map(jobs, job => (
<tr key={job.id}>
<td>
<span>
{job.name}{' '}
<span className='text-muted'>({job.id.slice(4, 8)})</span>
</span>
</td>
<td>{job.method}</td>
<td>
<ActionRowButton
disabled={!isJobUserMissing[job.id]}
icon='run-schedule'
btnStyle='warning'
handler={runJob}
handlerParam={job.id}
/>
{!isJobUserMissing[job.id] && (
<Tooltip content={_('jobUserNotFound')}>
<Icon className='ml-1' icon='error' />
</Tooltip>
)}
</td>
<td>
<ActionRowButton
icon='edit'
btnStyle='primary'
handler={this._edit}
handlerParam={job.id}
/>{' '}
<ActionRowButton
icon='delete'
btnStyle='danger'
handler={deleteJob}
handlerParam={job.id}
/>
</td>
</tr>
))}
</tbody>
</table>
{jobs !== undefined && (
<SortedTable
actions={ACTIONS}
collection={jobs}
columns={COLUMNS}
data-isJobUserMissing={this._getIsJobUserMissing()}
individualActions={this._individualActions}
shortcutsTarget='body'
stateUrlParam='s'
/>
)}
</div>
)
}