@ -1,6 +1,7 @@
import { PlayIcon , StopIcon , TrashIcon } from '@heroicons/react/outline' ;
import { PencilIcon } from '@heroicons/react/solid' ;
import axios from 'axios' ;
import React from 'react' ;
import React , { useState } from 'react' ;
import {
defineMessages ,
FormattedRelativeTime ,
@ -10,14 +11,17 @@ import {
import { useToasts } from 'react-toast-notifications' ;
import useSWR from 'swr' ;
import { CacheItem } from '../../../../server/interfaces/api/settingsInterfaces' ;
import { JobId } from '../../../../server/lib/settings' ;
import Spinner from '../../../assets/spinner.svg' ;
import globalMessages from '../../../i18n/globalMessages' ;
import { formatBytes } from '../../../utils/numberHelpers' ;
import Badge from '../../Common/Badge' ;
import Button from '../../Common/Button' ;
import LoadingSpinner from '../../Common/LoadingSpinner' ;
import Modal from '../../Common/Modal' ;
import PageTitle from '../../Common/PageTitle' ;
import Table from '../../Common/Table' ;
import Transition from '../../Transition' ;
const messages : { [ messageName : string ] : MessageDescriptor } = defineMessages ( {
jobsandcache : 'Jobs & Cache' ,
@ -51,12 +55,21 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'sonarr-scan' : 'Sonarr Scan' ,
'download-sync' : 'Download Sync' ,
'download-sync-reset' : 'Download Sync Reset' ,
editJobSchedule : 'Modify Job' ,
jobScheduleEditSaved : 'Job edited successfully!' ,
jobScheduleEditFailed : 'Something went wrong while saving the job.' ,
editJobSchedulePrompt : 'Frequency' ,
editJobScheduleSelectorHours :
'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}' ,
editJobScheduleSelectorMinutes :
'Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}' ,
} ) ;
interface Job {
id : string ;
id : JobId ;
name : string ;
type : 'process' | 'command' ;
interval : 'short' | 'long' | 'fixed' ;
nextExecutionTime : string ;
running : boolean ;
}
@ -74,6 +87,16 @@ const SettingsJobs: React.FC = () => {
}
) ;
const [ jobEditModal , setJobEditModal ] = useState < {
isOpen : boolean ;
job? : Job ;
} > ( {
isOpen : false ,
} ) ;
const [ isSaving , setIsSaving ] = useState ( false ) ;
const [ jobScheduleMinutes , setJobScheduleMinutes ] = useState ( 5 ) ;
const [ jobScheduleHours , setJobScheduleHours ] = useState ( 1 ) ;
if ( ! data && ! error ) {
return < LoadingSpinner / > ;
}
@ -118,6 +141,42 @@ const SettingsJobs: React.FC = () => {
cacheRevalidate ( ) ;
} ;
const scheduleJob = async ( ) = > {
const jobScheduleCron = [ '0' , '0' , '*' , '*' , '*' , '*' ] ;
try {
if ( jobEditModal . job ? . interval === 'short' ) {
jobScheduleCron [ 1 ] = ` */ ${ jobScheduleMinutes } ` ;
} else if ( jobEditModal . job ? . interval === 'long' ) {
jobScheduleCron [ 2 ] = ` */ ${ jobScheduleHours } ` ;
} else {
// jobs with interval: fixed should not be editable
throw new Error ( ) ;
}
setIsSaving ( true ) ;
await axios . post (
` /api/v1/settings/jobs/ ${ jobEditModal . job ? . id } /schedule ` ,
{
schedule : jobScheduleCron.join ( ' ' ) ,
}
) ;
addToast ( intl . formatMessage ( messages . jobScheduleEditSaved ) , {
appearance : 'success' ,
autoDismiss : true ,
} ) ;
setJobEditModal ( { isOpen : false } ) ;
revalidate ( ) ;
} catch ( e ) {
addToast ( intl . formatMessage ( messages . jobScheduleEditFailed ) , {
appearance : 'error' ,
autoDismiss : true ,
} ) ;
} finally {
setIsSaving ( false ) ;
}
} ;
return (
< >
< PageTitle
@ -126,6 +185,82 @@ const SettingsJobs: React.FC = () => {
intl . formatMessage ( globalMessages . settings ) ,
] }
/ >
< Transition
enter = "opacity-0 transition duration-300"
enterFrom = "opacity-0"
enterTo = "opacity-100"
leave = "opacity-100 transition duration-300"
leaveFrom = "opacity-100"
leaveTo = "opacity-0"
show = { jobEditModal . isOpen }
>
< Modal
title = { intl . formatMessage ( messages . editJobSchedule ) }
okText = {
isSaving
? intl . formatMessage ( globalMessages . saving )
: intl . formatMessage ( globalMessages . save )
}
iconSvg = { < PencilIcon / > }
onCancel = { ( ) = > setJobEditModal ( { isOpen : false } ) }
okDisabled = { isSaving }
onOk = { ( ) = > scheduleJob ( ) }
>
< div className = "section" >
< form >
< div className = "pb-6 form-row" >
< label htmlFor = "jobSchedule" className = "text-label" >
{ intl . formatMessage ( messages . editJobSchedulePrompt ) }
< / label >
< div className = "form-input" >
{ jobEditModal . job ? . interval === 'short' ? (
< select
name = "jobScheduleMinutes"
className = "inline"
value = { jobScheduleMinutes }
onChange = { ( e ) = >
setJobScheduleMinutes ( Number ( e . target . value ) )
}
>
{ [ 5 , 10 , 15 , 20 , 30 , 60 ] . map ( ( v ) = > (
< option value = { v } key = { ` jobScheduleMinutes- ${ v } ` } >
{ intl . formatMessage (
messages . editJobScheduleSelectorMinutes ,
{
jobScheduleMinutes : v ,
}
) }
< / option >
) ) }
< / select >
) : (
< select
name = "jobScheduleHours"
className = "inline"
value = { jobScheduleHours }
onChange = { ( e ) = >
setJobScheduleHours ( Number ( e . target . value ) )
}
>
{ [ 1 , 2 , 3 , 4 , 6 , 8 , 12 , 24 , 48 , 72 ] . map ( ( v ) = > (
< option value = { v } key = { ` jobScheduleHours- ${ v } ` } >
{ intl . formatMessage (
messages . editJobScheduleSelectorHours ,
{
jobScheduleHours : v ,
}
) }
< / option >
) ) }
< / select >
) }
< / div >
< / div >
< / form >
< / div >
< / Modal >
< / Transition >
< div className = "mb-6" >
< h3 className = "heading" > { intl . formatMessage ( messages . jobs ) } < / h3 >
< p className = "description" >
@ -179,6 +314,18 @@ const SettingsJobs: React.FC = () => {
< / div >
< / Table.TD >
< Table.TD alignText = "right" >
{ job . interval !== 'fixed' && (
< Button
className = "mr-2"
buttonType = "warning"
onClick = { ( ) = >
setJobEditModal ( { isOpen : true , job : job } )
}
>
< PencilIcon / >
{ intl . formatMessage ( globalMessages . edit ) }
< / Button >
) }
{ job . running ? (
< Button buttonType = "danger" onClick = { ( ) = > cancelJob ( job ) } >
< StopIcon / >