9494 :list =" datalistId"
9595 :type =" inputType"
9696 :step =" step"
97- :placeholder =" placeholder "
97+ :placeholder =" effectivePlaceholder "
9898 :required =" required"
9999 :pattern =" patternRegex"
100100 :title =" patternTitle"
@@ -191,13 +191,40 @@ export default {
191191 },
192192 emits: [" update:modelValue" ],
193193 data : () => {
194- return { selectMode: false , unitOverride: null };
194+ return { selectMode: false , unitOverride: null , cronMode: false };
195+ },
196+ created () {
197+ // start in cron mode if the existing value isn't a parseable duration
198+ if (
199+ this .cronCapable &&
200+ typeof this .modelValue === " string" &&
201+ this .modelValue !== " " &&
202+ parseGoDuration (this .modelValue ) === null
203+ ) {
204+ this .cronMode = true ;
205+ }
195206 },
196207 computed: {
208+ cronCapable () {
209+ return this .property === " interval" && this .type === " Duration" ;
210+ },
211+ cronActive () {
212+ return this .cronCapable && this .cronMode ;
213+ },
214+ effectivePlaceholder () {
215+ if (this .cronActive ) return " 15 0 * * *" ;
216+ return this .placeholder ;
217+ },
218+ // loose guardrail matching cron forms while rejecting a plain duration like "1h"; backend is authoritative
219+ cronPattern () {
220+ return " @(annually|yearly|monthly|weekly|daily|midnight|hourly|reboot)|@every\\ s+\\ S+|(\\ S+\\ s+){4,5}\\ S+" ;
221+ },
197222 patternRegex () {
223+ if (this .cronActive ) return this .cronPattern ;
198224 return this .pattern .Regex || null ;
199225 },
200226 patternTitle () {
227+ if (this .cronActive ) return this .$t (" config.form.cronInvalid" );
201228 const examples = this .pattern .Examples || [];
202229 if (! examples .length ) return null ;
203230 return examples .join (" , " );
@@ -229,6 +256,9 @@ export default {
229256 if (this .masked ) {
230257 return " password" ;
231258 }
259+ if (this .cronActive ) {
260+ return " text" ;
261+ }
232262 if ([" Int" , " Float" , " Duration" , " PricePerKWh" ].includes (this .type )) {
233263 return " number" ;
234264 }
@@ -254,6 +284,9 @@ export default {
254284 return result;
255285 },
256286 endAlign () {
287+ if (this .cronActive ) {
288+ return false ;
289+ }
257290 return [" Int" , " Float" , " Duration" , " PricePerKWh" ].includes (this .type );
258291 },
259292 step () {
@@ -263,6 +296,9 @@ export default {
263296 return null ;
264297 },
265298 unitValue () {
299+ if (this .cronActive ) {
300+ return this .$t (" config.form.cron" );
301+ }
266302 if (this .type === " Duration" ) {
267303 return this .fmtDurationUnit (this .value , this .durationUnit );
268304 }
@@ -324,16 +360,27 @@ export default {
324360 return displayFactors[this .durationUnit ] ?? 1 ;
325361 },
326362 durationUnit () {
363+ if (this .cronActive ) {
364+ return " cron" ;
365+ }
327366 return this .unitOverride ?? goDurationUnit (this .modelValue ) ?? this .unit ?? " second" ;
328367 },
329368 unitSelectable () {
330369 return this .type === " Duration" && ! this .legacyDuration && ! this .disabled ;
331370 },
332371 unitOptions () {
333- return durationUnits .map ((value ) => ({
372+ const options = durationUnits .map ((value ) => ({
334373 value,
335374 name: this .fmtDurationUnit (2 , value),
336375 }));
376+ // interval fields can also be driven by a cron expression
377+ if (this .cronCapable ) {
378+ options .push ({
379+ value: " cron" ,
380+ name: this .$t (" config.form.cron" ),
381+ });
382+ }
383+ return options;
337384 },
338385 selectOptions () {
339386 if (this .chargeModes ) {
@@ -363,6 +410,10 @@ export default {
363410 },
364411 value: {
365412 get () {
413+ if (this .cronActive ) {
414+ return this .modelValue ?? " " ;
415+ }
416+
366417 if (this .select && this .modelValue == null ) {
367418 return " " ;
368419 }
@@ -399,6 +450,11 @@ export default {
399450 return this .modelValue ;
400451 },
401452 set (value ) {
453+ if (this .cronActive ) {
454+ this .$emit (" update:modelValue" , value);
455+ return ;
456+ }
457+
402458 let newValue = value;
403459
404460 if (this .scale ) {
@@ -431,12 +487,22 @@ export default {
431487 return val;
432488 },
433489 onUnitChange (e ) {
434- // read display value before override changes the getter's unit
435- const num = this .value ;
436- this .unitOverride = e .target .value ;
437- if (typeof num === " number" ) {
438- this .$emit (" update:modelValue" , toGoDuration (num, this .unitOverride ));
490+ const unit = e .target .value ;
491+
492+ if (unit === " cron" ) {
493+ this .cronMode = true ;
494+ this .$emit (" update:modelValue" , " " );
495+ return ;
496+ }
497+
498+ // a cron expression can't convert to a duration, so clear it
499+ if (this .cronActive ) {
500+ this .cronMode = false ;
501+ this .unitOverride = unit;
502+ this .$emit (" update:modelValue" , " " );
503+ return ;
439504 }
505+ this .unitOverride = unit;
440506 },
441507 onFieldChange (e ) {
442508 // unparsable input (e.g. locale decimal separator mismatch)
0 commit comments