From 8d1fb96d18646944be0e0ff067beb75804982e3d Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Tue, 14 May 2024 13:51:03 +0200 Subject: [PATCH] UI - Refactor of the Recheck Time Settings, Added "Use default recheck time" checkbox and refactor/simplify system handling (#2362) --- changedetectionio/flask_app.py | 34 ++----- changedetectionio/forms.py | 1 + changedetectionio/model/Watch.py | 3 +- changedetectionio/static/js/watch-settings.js | 17 ++++ .../static/styles/scss/styles.scss | 27 ++--- changedetectionio/static/styles/styles.css | 21 ++-- changedetectionio/store.py | 13 +++ changedetectionio/templates/edit.html | 12 +-- changedetectionio/templates/settings.html | 2 +- .../tests/test_watch_fields_storage.py | 99 ------------------- docker-compose.yml | 4 +- 11 files changed, 75 insertions(+), 158 deletions(-) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index ee132424..49880b5d 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -619,7 +619,6 @@ def changedetection_app(config=None, datastore_o=None): from .blueprint.browser_steps.browser_steps import browser_step_ui_config from . import processors - using_default_check_time = True # More for testing, possible to return the first/only if not datastore.data['watching'].keys(): flash("No watches to edit", "error") @@ -644,10 +643,6 @@ def changedetection_app(config=None, datastore_o=None): # be sure we update with a copy instead of accidently editing the live object by reference default = deepcopy(datastore.data['watching'][uuid]) - # Show system wide default if nothing configured - if all(value == 0 or value == None for value in datastore.data['watching'][uuid]['time_between_check'].values()): - default['time_between_check'] = deepcopy(datastore.data['settings']['requests']['time_between_check']) - # Defaults for proxy choice if datastore.proxy_list is not None: # When enabled # @todo @@ -685,18 +680,8 @@ def changedetection_app(config=None, datastore_o=None): if request.args.get('unpause_on_save'): extra_update_obj['paused'] = False - # Re #110, if they submit the same as the default value, set it to None, so we continue to follow the default - # Assume we use the default value, unless something relevant is different, then use the form value - # values could be None, 0 etc. - # Set to None unless the next for: says that something is different - extra_update_obj['time_between_check'] = dict.fromkeys(form.time_between_check.data) - for k, v in form.time_between_check.data.items(): - if v and v != datastore.data['settings']['requests']['time_between_check'][k]: - extra_update_obj['time_between_check'] = form.time_between_check.data - using_default_check_time = False - break - + extra_update_obj['time_between_check'] = form.time_between_check.data # Ignore text form_ignore_text = form.ignore_text.data @@ -777,7 +762,6 @@ def changedetection_app(config=None, datastore_o=None): extra_title=f" - Edit - {watch.label}", form=form, has_default_notification_urls=True if len(datastore.data['settings']['application']['notification_urls']) else False, - has_empty_checktime=using_default_check_time, has_extra_headers_file=len(datastore.get_all_headers_in_textfile_for_watch(uuid=uuid)) > 0, has_special_tag_options=_watch_has_tag_options_set(watch=watch), is_html_webdriver=is_html_webdriver, @@ -863,11 +847,13 @@ def changedetection_app(config=None, datastore_o=None): flash("An error occurred, please see below.", "error") output = render_template("settings.html", - form=form, - hide_remove_pass=os.getenv("SALTED_PASS", False), api_key=datastore.data['settings']['application'].get('api_access_token'), emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False), - settings_application=datastore.data['settings']['application']) + form=form, + hide_remove_pass=os.getenv("SALTED_PASS", False), + min_system_recheck_seconds=int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 3)), + settings_application=datastore.data['settings']['application'] + ) return output @@ -1668,14 +1654,14 @@ def notification_runner(): # Trim the log length notification_debug_log = notification_debug_log[-100:] -# Thread runner to check every minute, look for new watches to feed into the Queue. +# Threaded runner, look for new watches to feed into the Queue. def ticker_thread_check_time_launch_checks(): import random from changedetectionio import update_worker proxy_last_called_time = {} - recheck_time_minimum_seconds = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 20)) + recheck_time_minimum_seconds = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 3)) logger.debug(f"System env MINIMUM_SECONDS_RECHECK_TIME {recheck_time_minimum_seconds}") # Spin up Workers that do the fetching @@ -1729,9 +1715,7 @@ def ticker_thread_check_time_launch_checks(): continue # If they supplied an individual entry minutes to threshold. - - watch_threshold_seconds = watch.threshold_seconds() - threshold = watch_threshold_seconds if watch_threshold_seconds > 0 else recheck_time_system_seconds + threshold = recheck_time_system_seconds if watch.get('time_between_check_use_default') else watch.threshold_seconds() # #580 - Jitter plus/minus amount of time to make the check seem more random to the server jitter = datastore.data['settings']['requests'].get('jitter_seconds', 0) diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 4f74f978..2d64a227 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -453,6 +453,7 @@ class watchForm(commonSettingsForm): tags = StringTagUUID('Group tag', [validators.Optional()], default='') time_between_check = FormField(TimeBetweenCheckForm) + time_between_check_use_default = BooleanField('Use global settings for time between check', default=False) include_filters = StringListField('CSS/JSONPath/JQ/XPath Filters', [ValidateCSSJSONXPATHInput()], default='') diff --git a/changedetectionio/model/Watch.py b/changedetectionio/model/Watch.py index deffe9e5..e262ac4e 100644 --- a/changedetectionio/model/Watch.py +++ b/changedetectionio/model/Watch.py @@ -12,7 +12,7 @@ from loguru import logger # file:// is further checked by ALLOW_FILE_URI SAFE_PROTOCOL_REGEX='^(http|https|ftp|file):' -minimum_seconds_recheck_time = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 60)) +minimum_seconds_recheck_time = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 3)) mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7} from changedetectionio.notification import ( @@ -69,6 +69,7 @@ base_config = { # Requires setting to None on submit if it's the same as the default # Should be all None by default, so we use the system default in this case. 'time_between_check': {'weeks': None, 'days': None, 'hours': None, 'minutes': None, 'seconds': None}, + 'time_between_check_use_default': True, 'title': None, 'trigger_text': [], # List of text or regex to wait for until a change is detected 'url': '', diff --git a/changedetectionio/static/js/watch-settings.js b/changedetectionio/static/js/watch-settings.js index 22bf48ed..73c66191 100644 --- a/changedetectionio/static/js/watch-settings.js +++ b/changedetectionio/static/js/watch-settings.js @@ -1,3 +1,17 @@ +function toggleOpacity(checkboxSelector, fieldSelector) { + const checkbox = document.querySelector(checkboxSelector); + const fields = document.querySelectorAll(fieldSelector); + function updateOpacity() { + const opacityValue = checkbox.checked ? 0.6 : 1; + fields.forEach(field => { + field.style.opacity = opacityValue; + }); + } + // Initial setup + updateOpacity(); + checkbox.addEventListener('change', updateOpacity); +} + $(document).ready(function () { $('#notification-setting-reset-to-default').click(function (e) { $('#notification_title').val(''); @@ -10,4 +24,7 @@ $(document).ready(function () { e.preventDefault(); $('#notification-tokens-info').toggle(); }); + + toggleOpacity('#time_between_check_use_default', '#time_between_check'); }); + diff --git a/changedetectionio/static/styles/scss/styles.scss b/changedetectionio/static/styles/scss/styles.scss index 3f37ebd8..55bcc4cb 100644 --- a/changedetectionio/static/styles/scss/styles.scss +++ b/changedetectionio/static/styles/scss/styles.scss @@ -928,23 +928,26 @@ body.full-width { font-size: .875em; } } - .text-filtering { - h3 { - margin-top: 0; - } - border: 1px solid #ccc; - padding: 1rem; - border-radius: 5px; - margin-bottom: 1rem; - fieldset:last-of-type { +} + +.border-fieldset { + h3 { + margin-top: 0; + } + border: 1px solid #ccc; + padding: 1rem; + border-radius: 5px; + margin-bottom: 1rem; + fieldset:last-of-type { + padding-bottom: 0; + .pure-control-group { padding-bottom: 0; - .pure-control-group { - padding-bottom: 0; - } } } } + + ul { padding-left: 1em; padding-top: 0px; diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css index 939745b3..c18d8d96 100644 --- a/changedetectionio/static/styles/styles.css +++ b/changedetectionio/static/styles/styles.css @@ -1041,17 +1041,18 @@ body.full-width .edit-form { color: var(--color-text-input-description); } .edit-form .pure-form-message-inline code { font-size: .875em; } - .edit-form .text-filtering { - border: 1px solid #ccc; - padding: 1rem; - border-radius: 5px; - margin-bottom: 1rem; } - .edit-form .text-filtering h3 { - margin-top: 0; } - .edit-form .text-filtering fieldset:last-of-type { + +.border-fieldset { + border: 1px solid #ccc; + padding: 1rem; + border-radius: 5px; + margin-bottom: 1rem; } + .border-fieldset h3 { + margin-top: 0; } + .border-fieldset fieldset:last-of-type { + padding-bottom: 0; } + .border-fieldset fieldset:last-of-type .pure-control-group { padding-bottom: 0; } - .edit-form .text-filtering fieldset:last-of-type .pure-control-group { - padding-bottom: 0; } ul { padding-left: 1em; diff --git a/changedetectionio/store.py b/changedetectionio/store.py index 43202140..884c617a 100644 --- a/changedetectionio/store.py +++ b/changedetectionio/store.py @@ -872,3 +872,16 @@ class ChangeDetectionStore: self.__data["watching"][awatch]['include_filters'][num] = 'xpath1:' + selector if selector.startswith('xpath:'): self.__data["watching"][awatch]['include_filters'][num] = selector.replace('xpath:', 'xpath1:', 1) + + # Use more obvious default time setting + def update_15(self): + for uuid in self.__data["watching"]: + if self.__data["watching"][uuid]['time_between_check'] == self.__data['settings']['requests']['time_between_check']: + # What the old logic was, which was pretty confusing + self.__data["watching"][uuid]['time_between_check_use_default'] = True + elif all(value is None or value == 0 for value in self.__data["watching"][uuid]['time_between_check'].values()): + self.__data["watching"][uuid]['time_between_check_use_default'] = True + else: + # Something custom here + self.__data["watching"][uuid]['time_between_check_use_default'] = False + diff --git a/changedetectionio/templates/edit.html b/changedetectionio/templates/edit.html index 79aa9a3e..f8c0eba4 100644 --- a/changedetectionio/templates/edit.html +++ b/changedetectionio/templates/edit.html @@ -87,15 +87,9 @@ {{ render_field(form.tags) }} Organisational tag/group name used in the main listing page -
+
{{ render_field(form.time_between_check, class="time-check-widget") }} - {% if has_empty_checktime %} - Currently using the default global settings, change to another value if you want to be specific. - {% else %} - Set to blank to use the default global settings. - {% endif %} + {{ render_checkbox_field(form.time_between_check_use_default, class="use-default-timecheck") }}
{{ render_checkbox_field(form.extract_title_as_title) }} @@ -330,7 +324,7 @@ nav -
+

Text filtering

Limit trigger/ignore/block/extract to;
diff --git a/changedetectionio/templates/settings.html b/changedetectionio/templates/settings.html index 78387a48..e72c7818 100644 --- a/changedetectionio/templates/settings.html +++ b/changedetectionio/templates/settings.html @@ -31,7 +31,7 @@
{{ render_field(form.requests.form.time_between_check, class="time-check-widget") }} - Default time for all watches, when the watch does not have a specific time setting. + Default recheck time for all watches, current system minimum is {{min_system_recheck_seconds}} seconds (more info).
{{ render_field(form.requests.form.jitter_seconds, class="jitter_seconds") }} diff --git a/changedetectionio/tests/test_watch_fields_storage.py b/changedetectionio/tests/test_watch_fields_storage.py index 5044598c..7dc3f748 100644 --- a/changedetectionio/tests/test_watch_fields_storage.py +++ b/changedetectionio/tests/test_watch_fields_storage.py @@ -54,102 +54,3 @@ def test_check_watch_field_storage(client, live_server): assert b"woohoo" in res.data assert b"curl: foo" in res.data - - -# Re https://github.com/dgtlmoon/changedetection.io/issues/110 -def test_check_recheck_global_setting(client, live_server): - - res = client.post( - url_for("settings_page"), - data={ - "requests-time_between_check-minutes": 1566, - 'application-fetch_backend': "html_requests" - }, - follow_redirects=True - ) - assert b"Settings updated." in res.data - - # Now add a record - - test_url = "http://somerandomsitewewatch.com" - - res = client.post( - url_for("import_page"), - data={"urls": test_url}, - follow_redirects=True - ) - assert b"1 Imported" in res.data - - # Now visit the edit page, it should have the default minutes - - res = client.get( - url_for("edit_page", uuid="first"), - follow_redirects=True - ) - - # Should show the default minutes - assert b"change to another value if you want to be specific" in res.data - assert b"1566" in res.data - - res = client.post( - url_for("settings_page"), - data={ - "requests-time_between_check-minutes": 222, - 'application-fetch_backend': "html_requests" - }, - follow_redirects=True - ) - assert b"Settings updated." in res.data - - res = client.get( - url_for("edit_page", uuid="first"), - follow_redirects=True - ) - - # Should show the default minutes - assert b"change to another value if you want to be specific" in res.data - assert b"222" in res.data - - # Now change it specifically, it should show the new minutes - res = client.post( - url_for("edit_page", uuid="first"), - data={"url": test_url, - "time_between_check-minutes": 55, - 'fetch_backend': "html_requests" - }, - follow_redirects=True - ) - - res = client.get( - url_for("edit_page", uuid="first"), - follow_redirects=True - ) - assert b"55" in res.data - - # Now submit an empty field, it should give back the default global minutes - res = client.post( - url_for("settings_page"), - data={ - "requests-time_between_check-minutes": 666, - "application-fetch_backend": "html_requests" - }, - follow_redirects=True - ) - assert b"Settings updated." in res.data - - res = client.post( - url_for("edit_page", uuid="first"), - data={"url": test_url, - "time_between_check-minutes": "", - 'fetch_backend': "html_requests" - }, - follow_redirects=True - ) - - assert b"Updated watch." in res.data - - res = client.get( - url_for("edit_page", uuid="first"), - follow_redirects=True - ) - assert b"666" in res.data diff --git a/docker-compose.yml b/docker-compose.yml index 1b5bd9af..4cf17605 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,7 +54,9 @@ services: # # Default number of parallel/concurrent fetchers # - FETCH_WORKERS=10 - + # + # Absolute minimum seconds to recheck, overrides any watch minimum, change to 0 to disable + # - MINIMUM_SECONDS_RECHECK_TIME=3 # Comment out ports: when using behind a reverse proxy , enable networks: etc. ports: - 5000:5000