@@ -5,6 +5,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
55import Modal from '@app/components/Common/Modal' ;
66import PageTitle from '@app/components/Common/PageTitle' ;
77import Table from '@app/components/Common/Table' ;
8+ import useLocale from '@app/hooks/useLocale' ;
89import globalMessages from '@app/i18n/globalMessages' ;
910import { formatBytes } from '@app/utils/numberHelpers' ;
1011import { Transition } from '@headlessui/react' ;
@@ -13,7 +14,8 @@ import { PencilIcon } from '@heroicons/react/solid';
1314import type { CacheItem } from '@server/interfaces/api/settingsInterfaces' ;
1415import type { JobId } from '@server/lib/settings' ;
1516import axios from 'axios' ;
16- import { Fragment , useState } from 'react' ;
17+ import cronstrue from 'cronstrue/i18n' ;
18+ import { Fragment , useReducer , useState } from 'react' ;
1719import type { MessageDescriptor } from 'react-intl' ;
1820import { defineMessages , FormattedRelativeTime , useIntl } from 'react-intl' ;
1921import { useToasts } from 'react-toast-notifications' ;
@@ -55,7 +57,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
5557 editJobSchedule : 'Modify Job' ,
5658 jobScheduleEditSaved : 'Job edited successfully!' ,
5759 jobScheduleEditFailed : 'Something went wrong while saving the job.' ,
58- editJobSchedulePrompt : 'Frequency' ,
60+ editJobScheduleCurrent : 'Current Frequency' ,
61+ editJobSchedulePrompt : 'New Frequency' ,
5962 editJobScheduleSelectorHours :
6063 'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}' ,
6164 editJobScheduleSelectorMinutes :
@@ -67,12 +70,56 @@ interface Job {
6770 name : string ;
6871 type : 'process' | 'command' ;
6972 interval : 'short' | 'long' | 'fixed' ;
73+ cronSchedule : string ;
7074 nextExecutionTime : string ;
7175 running : boolean ;
7276}
7377
78+ type JobModalState = {
79+ isOpen ?: boolean ;
80+ job ?: Job ;
81+ scheduleHours : number ;
82+ scheduleMinutes : number ;
83+ } ;
84+
85+ type JobModalAction =
86+ | { type : 'set' ; hours ?: number ; minutes ?: number }
87+ | {
88+ type : 'close' ;
89+ }
90+ | { type : 'open' ; job ?: Job } ;
91+
92+ const jobModalReducer = (
93+ state : JobModalState ,
94+ action : JobModalAction
95+ ) : JobModalState => {
96+ switch ( action . type ) {
97+ case 'close' :
98+ return {
99+ ...state ,
100+ isOpen : false ,
101+ } ;
102+
103+ case 'open' :
104+ return {
105+ isOpen : true ,
106+ job : action . job ,
107+ scheduleHours : 1 ,
108+ scheduleMinutes : 5 ,
109+ } ;
110+
111+ case 'set' :
112+ return {
113+ ...state ,
114+ scheduleHours : action . hours ?? state . scheduleHours ,
115+ scheduleMinutes : action . minutes ?? state . scheduleMinutes ,
116+ } ;
117+ }
118+ } ;
119+
74120const SettingsJobs = ( ) => {
75121 const intl = useIntl ( ) ;
122+ const { locale } = useLocale ( ) ;
76123 const { addToast } = useToasts ( ) ;
77124 const {
78125 data,
@@ -88,15 +135,12 @@ const SettingsJobs = () => {
88135 }
89136 ) ;
90137
91- const [ jobEditModal , setJobEditModal ] = useState < {
92- isOpen : boolean ;
93- job ?: Job ;
94- } > ( {
138+ const [ jobModalState , dispatch ] = useReducer ( jobModalReducer , {
95139 isOpen : false ,
140+ scheduleHours : 1 ,
141+ scheduleMinutes : 5 ,
96142 } ) ;
97143 const [ isSaving , setIsSaving ] = useState ( false ) ;
98- const [ jobScheduleMinutes , setJobScheduleMinutes ] = useState ( 5 ) ;
99- const [ jobScheduleHours , setJobScheduleHours ] = useState ( 1 ) ;
100144
101145 if ( ! data && ! error ) {
102146 return < LoadingSpinner /> ;
@@ -146,27 +190,29 @@ const SettingsJobs = () => {
146190 const jobScheduleCron = [ '0' , '0' , '*' , '*' , '*' , '*' ] ;
147191
148192 try {
149- if ( jobEditModal . job ?. interval === 'short' ) {
150- jobScheduleCron [ 1 ] = `*/${ jobScheduleMinutes } ` ;
151- } else if ( jobEditModal . job ?. interval === 'long' ) {
152- jobScheduleCron [ 2 ] = `*/${ jobScheduleHours } ` ;
193+ if ( jobModalState . job ?. interval === 'short' ) {
194+ jobScheduleCron [ 1 ] = `*/${ jobModalState . scheduleMinutes } ` ;
195+ } else if ( jobModalState . job ?. interval === 'long' ) {
196+ jobScheduleCron [ 2 ] = `*/${ jobModalState . scheduleHours } ` ;
153197 } else {
154198 // jobs with interval: fixed should not be editable
155199 throw new Error ( ) ;
156200 }
157201
158202 setIsSaving ( true ) ;
159203 await axios . post (
160- `/api/v1/settings/jobs/${ jobEditModal . job ? .id } /schedule` ,
204+ `/api/v1/settings/jobs/${ jobModalState . job . id } /schedule` ,
161205 {
162206 schedule : jobScheduleCron . join ( ' ' ) ,
163207 }
164208 ) ;
209+
165210 addToast ( intl . formatMessage ( messages . jobScheduleEditSaved ) , {
166211 appearance : 'success' ,
167212 autoDismiss : true ,
168213 } ) ;
169- setJobEditModal ( { isOpen : false } ) ;
214+
215+ dispatch ( { type : 'close' } ) ;
170216 revalidate ( ) ;
171217 } catch ( e ) {
172218 addToast ( intl . formatMessage ( messages . jobScheduleEditFailed ) , {
@@ -194,7 +240,7 @@ const SettingsJobs = () => {
194240 leave = "opacity-100 transition duration-300"
195241 leaveFrom = "opacity-100"
196242 leaveTo = "opacity-0"
197- show = { jobEditModal . isOpen }
243+ show = { jobModalState . isOpen }
198244 >
199245 < Modal
200246 title = { intl . formatMessage ( messages . editJobSchedule ) }
@@ -203,24 +249,43 @@ const SettingsJobs = () => {
203249 ? intl . formatMessage ( globalMessages . saving )
204250 : intl . formatMessage ( globalMessages . save )
205251 }
206- onCancel = { ( ) => setJobEditModal ( { isOpen : false } ) }
252+ onCancel = { ( ) => dispatch ( { type : 'close' } ) }
207253 okDisabled = { isSaving }
208254 onOk = { ( ) => scheduleJob ( ) }
209255 >
210256 < div className = "section" >
211- < form >
212- < div className = "form-row pb-6" >
257+ < form className = "mb-6" >
258+ < div className = "form-row" >
259+ < label className = "text-label" >
260+ { intl . formatMessage ( messages . editJobScheduleCurrent ) }
261+ </ label >
262+ < div className = "form-input-area mt-2 mb-1" >
263+ < div >
264+ { jobModalState . job &&
265+ cronstrue . toString ( jobModalState . job . cronSchedule , {
266+ locale,
267+ } ) }
268+ </ div >
269+ < div className = "text-sm text-gray-500" >
270+ { jobModalState . job ?. cronSchedule }
271+ </ div >
272+ </ div >
273+ </ div >
274+ < div className = "form-row" >
213275 < label htmlFor = "jobSchedule" className = "text-label" >
214276 { intl . formatMessage ( messages . editJobSchedulePrompt ) }
215277 </ label >
216278 < div className = "form-input-area" >
217- { jobEditModal . job ?. interval === 'short' ? (
279+ { jobModalState . job ?. interval === 'short' ? (
218280 < select
219281 name = "jobScheduleMinutes"
220282 className = "inline"
221- value = { jobScheduleMinutes }
283+ value = { jobModalState . scheduleMinutes }
222284 onChange = { ( e ) =>
223- setJobScheduleMinutes ( Number ( e . target . value ) )
285+ dispatch ( {
286+ type : 'set' ,
287+ minutes : Number ( e . target . value ) ,
288+ } )
224289 }
225290 >
226291 { [ 5 , 10 , 15 , 20 , 30 , 60 ] . map ( ( v ) => (
@@ -238,9 +303,12 @@ const SettingsJobs = () => {
238303 < select
239304 name = "jobScheduleHours"
240305 className = "inline"
241- value = { jobScheduleHours }
306+ value = { jobModalState . scheduleHours }
242307 onChange = { ( e ) =>
243- setJobScheduleHours ( Number ( e . target . value ) )
308+ dispatch ( {
309+ type : 'set' ,
310+ hours : Number ( e . target . value ) ,
311+ } )
244312 }
245313 >
246314 { [ 1 , 2 , 3 , 4 , 6 , 8 , 12 , 24 , 48 , 72 ] . map ( ( v ) => (
@@ -319,9 +387,7 @@ const SettingsJobs = () => {
319387 < Button
320388 className = "mr-2"
321389 buttonType = "warning"
322- onClick = { ( ) =>
323- setJobEditModal ( { isOpen : true , job : job } )
324- }
390+ onClick = { ( ) => dispatch ( { type : 'open' , job } ) }
325391 >
326392 < PencilIcon />
327393 < span > { intl . formatMessage ( globalMessages . edit ) } </ span >
0 commit comments