1
1
import os
2
2
import re
3
3
from loguru import logger
4
+ from wtforms .widgets .core import TimeInput
4
5
5
6
from changedetectionio .strtobool import strtobool
6
7
7
8
from wtforms import (
8
9
BooleanField ,
9
10
Form ,
11
+ Field ,
10
12
IntegerField ,
11
13
RadioField ,
12
14
SelectField ,
@@ -125,6 +127,87 @@ def _value(self):
125
127
126
128
return 'error'
127
129
130
+ class TimeDurationForm (Form ):
131
+ hours = SelectField (choices = [(f"{ i } " , f"{ i } " ) for i in range (0 , 25 )], default = "24" , validators = [validators .Optional ()])
132
+ minutes = SelectField (choices = [(f"{ i } " , f"{ i } " ) for i in range (0 , 60 )], default = "00" , validators = [validators .Optional ()])
133
+
134
+ class TimeStringField (Field ):
135
+ """
136
+ A WTForms field for time inputs (HH:MM) that stores the value as a string.
137
+ """
138
+ widget = TimeInput () # Use the built-in time input widget
139
+
140
+ def _value (self ):
141
+ """
142
+ Returns the value for rendering in the form.
143
+ """
144
+ return self .data if self .data is not None else ""
145
+
146
+ def process_formdata (self , valuelist ):
147
+ """
148
+ Processes the raw input from the form and stores it as a string.
149
+ """
150
+ if valuelist :
151
+ time_str = valuelist [0 ]
152
+ # Simple validation for HH:MM format
153
+ if not time_str or len (time_str .split (":" )) != 2 :
154
+ raise ValidationError ("Invalid time format. Use HH:MM." )
155
+ self .data = time_str
156
+
157
+
158
+ class validateTimeZoneName (object ):
159
+ """
160
+ Flask wtform validators wont work with basic auth
161
+ """
162
+
163
+ def __init__ (self , message = None ):
164
+ self .message = message
165
+
166
+ def __call__ (self , form , field ):
167
+ from zoneinfo import available_timezones
168
+ python_timezones = available_timezones ()
169
+ if field .data and field .data not in python_timezones :
170
+ raise ValidationError ("Not a valid timezone name" )
171
+
172
+ class ScheduleLimitDaySubForm (Form ):
173
+ enabled = BooleanField ("not set" , default = True )
174
+ start_time = TimeStringField ("Start At" , default = "00:00" , render_kw = {"placeholder" : "HH:MM" }, validators = [validators .Optional ()])
175
+ duration = FormField (TimeDurationForm , label = "Run duration" )
176
+
177
+ class ScheduleLimitForm (Form ):
178
+ enabled = BooleanField ("Use time scheduler" , default = False )
179
+ # Because the label for=""" doesnt line up/work with the actual checkbox
180
+ monday = FormField (ScheduleLimitDaySubForm , label = "" )
181
+ tuesday = FormField (ScheduleLimitDaySubForm , label = "" )
182
+ wednesday = FormField (ScheduleLimitDaySubForm , label = "" )
183
+ thursday = FormField (ScheduleLimitDaySubForm , label = "" )
184
+ friday = FormField (ScheduleLimitDaySubForm , label = "" )
185
+ saturday = FormField (ScheduleLimitDaySubForm , label = "" )
186
+ sunday = FormField (ScheduleLimitDaySubForm , label = "" )
187
+
188
+ timezone = StringField ("Optional timezone to run in" ,
189
+ render_kw = {"list" : "timezones" },
190
+ validators = [validateTimeZoneName ()]
191
+ )
192
+ def __init__ (
193
+ self ,
194
+ formdata = None ,
195
+ obj = None ,
196
+ prefix = "" ,
197
+ data = None ,
198
+ meta = None ,
199
+ ** kwargs ,
200
+ ):
201
+ super ().__init__ (formdata , obj , prefix , data , meta , ** kwargs )
202
+ self .monday .form .enabled .label .text = "Monday"
203
+ self .tuesday .form .enabled .label .text = "Tuesday"
204
+ self .wednesday .form .enabled .label .text = "Wednesday"
205
+ self .thursday .form .enabled .label .text = "Thursday"
206
+ self .friday .form .enabled .label .text = "Friday"
207
+ self .saturday .form .enabled .label .text = "Saturday"
208
+ self .sunday .form .enabled .label .text = "Sunday"
209
+
210
+
128
211
class TimeBetweenCheckForm (Form ):
129
212
weeks = IntegerField ('Weeks' , validators = [validators .Optional (), validators .NumberRange (min = 0 , message = "Should contain zero or more seconds" )])
130
213
days = IntegerField ('Days' , validators = [validators .Optional (), validators .NumberRange (min = 0 , message = "Should contain zero or more seconds" )])
@@ -279,6 +362,7 @@ def __call__(self, form, field):
279
362
# This should raise a ValidationError() or not
280
363
validate_url (field .data )
281
364
365
+
282
366
def validate_url (test_url ):
283
367
# If hosts that only contain alphanumerics are allowed ("localhost" for example)
284
368
try :
@@ -438,6 +522,7 @@ def __init__(self, formdata=None, obj=None, prefix="", data=None, meta=None, **k
438
522
notification_title = StringField ('Notification Title' , default = 'ChangeDetection.io Notification - {{ watch_url }}' , validators = [validators .Optional (), ValidateJinja2Template ()])
439
523
notification_urls = StringListField ('Notification URL List' , validators = [validators .Optional (), ValidateAppRiseServers (), ValidateJinja2Template ()])
440
524
processor = RadioField ( label = u"Processor - What do you want to achieve?" , choices = processors .available_processors (), default = "text_json_diff" )
525
+ timezone = StringField ("Timezone for watch schedule" , render_kw = {"list" : "timezones" }, validators = [validateTimeZoneName ()])
441
526
webdriver_delay = IntegerField ('Wait seconds before extracting text' , validators = [validators .Optional (), validators .NumberRange (min = 1 , message = "Should contain one or more seconds" )])
442
527
443
528
@@ -448,7 +533,6 @@ class importForm(Form):
448
533
xlsx_file = FileField ('Upload .xlsx file' , validators = [FileAllowed (['xlsx' ], 'Must be .xlsx file!' )])
449
534
file_mapping = SelectField ('File mapping' , [validators .DataRequired ()], choices = {('wachete' , 'Wachete mapping' ), ('custom' ,'Custom mapping' )})
450
535
451
-
452
536
class SingleBrowserStep (Form ):
453
537
454
538
operation = SelectField ('Operation' , [validators .Optional ()], choices = browser_step_ui_config .keys ())
@@ -466,6 +550,9 @@ class processor_text_json_diff_form(commonSettingsForm):
466
550
tags = StringTagUUID ('Group tag' , [validators .Optional ()], default = '' )
467
551
468
552
time_between_check = FormField (TimeBetweenCheckForm )
553
+
554
+ time_schedule_limit = FormField (ScheduleLimitForm )
555
+
469
556
time_between_check_use_default = BooleanField ('Use global settings for time between check' , default = False )
470
557
471
558
include_filters = StringListField ('CSS/JSONPath/JQ/XPath Filters' , [ValidateCSSJSONXPATHInput ()], default = '' )
@@ -567,6 +654,23 @@ def validate(self, **kwargs):
567
654
568
655
return result
569
656
657
+ def __init__ (
658
+ self ,
659
+ formdata = None ,
660
+ obj = None ,
661
+ prefix = "" ,
662
+ data = None ,
663
+ meta = None ,
664
+ ** kwargs ,
665
+ ):
666
+ super ().__init__ (formdata , obj , prefix , data , meta , ** kwargs )
667
+ if kwargs and kwargs .get ('default_system_settings' ):
668
+ default_tz = kwargs .get ('default_system_settings' ).get ('application' , {}).get ('timezone' )
669
+ if default_tz :
670
+ self .time_schedule_limit .form .timezone .render_kw ['placeholder' ] = default_tz
671
+
672
+
673
+
570
674
class SingleExtraProxy (Form ):
571
675
572
676
# maybe better to set some <script>var..
@@ -587,6 +691,7 @@ class DefaultUAInputForm(Form):
587
691
# datastore.data['settings']['requests']..
588
692
class globalSettingsRequestForm (Form ):
589
693
time_between_check = FormField (TimeBetweenCheckForm )
694
+ time_schedule_limit = FormField (ScheduleLimitForm )
590
695
proxy = RadioField ('Proxy' )
591
696
jitter_seconds = IntegerField ('Random jitter seconds ± check' ,
592
697
render_kw = {"style" : "width: 5em;" },
0 commit comments