From d81e5d6c434efbda8198acc61ea1027c61a9ddf1 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Tue, 7 Jan 2025 17:53:54 +0100 Subject: [PATCH 1/9] Add error handling and tidy up automatic JSON header addition --- changedetectionio/apprise_plugin/__init__.py | 68 +++++++++++++------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index a95cb407113..ab89a09c0f7 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -2,6 +2,8 @@ from apprise.decorators import notify from loguru import logger + + @notify(on="delete") @notify(on="deletes") @notify(on="get") @@ -10,25 +12,17 @@ @notify(on="posts") @notify(on="put") @notify(on="puts") + def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): import requests import json from urllib.parse import unquote_plus from apprise.utils.parse import parse_url as apprise_parse_url - from apprise import URLBase url = kwargs['meta'].get('url') + schema = kwargs['meta'].get('schema').strip('s') + requests_method = getattr(requests, schema) - if url.startswith('post'): - r = requests.post - elif url.startswith('get'): - r = requests.get - elif url.startswith('put'): - r = requests.put - elif url.startswith('delete'): - r = requests.delete - - url = url.replace('post://', 'http://') url = url.replace('posts://', 'https://') url = url.replace('put://', 'http://') url = url.replace('puts://', 'https://') @@ -42,6 +36,8 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): headers = {} params = {} auth = None + has_error = False + # Convert /foobar?+some-header=hello to proper header dictionary results = apprise_parse_url(url) @@ -65,18 +61,40 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): elif results.get('user'): auth = (unquote_plus(results.get('user'))) - # Try to auto-guess if it's JSON - h = 'application/json; charset=utf-8' + if '{' in body[:100]: + try: + # Try to auto-guess if it's JSON + h = 'application/json; charset=utf-8' + json.loads(body) + headers['Content-Type'] = h + except ValueError as e: + logger.warning(f"Could not automatically add '{h}' header to the notification because the document failed to parse as JSON: {e}") + pass + status_str = '' + try: - json.loads(body) - headers['Content-Type'] = h - except ValueError as e: - logger.warning(f"Could not automatically add '{h}' header to the {kwargs['meta'].get('schema')}:// notification because the document failed to parse as JSON: {e}") - pass - - r(results.get('url'), - auth=auth, - data=body.encode('utf-8') if type(body) is str else body, - headers=headers, - params=params - ) \ No newline at end of file + + r = requests_method(results.get('url'), + auth=auth, + data=body.encode('utf-8') if type(body) is str else body, + headers=headers, + params=params + ) + + if r.status_code not in (requests.codes.created, requests.codes.ok): + status_str = f"Error sending '{schema}' request to {url} - Status: {r.status_code}: '{r.reason}'" + logger.error(status_str) + has_error = True + else: + logger.info(f"Sent '{schema}' request to {url}") + has_error = False + + except requests.RequestException as e: + status_str = f"Error sending '{schema}' request to {url} - {str(e)}" + logger.error(status_str) + has_error = True + + if has_error: + raise TypeError(status_str) + + return True From be252153e4c9a604890fe077cc9e4c9e154d1299 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 09:36:59 +0100 Subject: [PATCH 2/9] WIP --- changedetectionio/apprise_plugin/__init__.py | 27 +++++++++---------- .../tests/test_add_replace_remove_filter.py | 4 +-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index ab89a09c0f7..307c463a113 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -3,7 +3,6 @@ from loguru import logger - @notify(on="delete") @notify(on="deletes") @notify(on="get") @@ -12,26 +11,24 @@ @notify(on="posts") @notify(on="put") @notify(on="puts") - def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): import requests import json + import re + from urllib.parse import unquote_plus from apprise.utils.parse import parse_url as apprise_parse_url url = kwargs['meta'].get('url') - schema = kwargs['meta'].get('schema').strip('s') - requests_method = getattr(requests, schema) - - url = url.replace('posts://', 'https://') - url = url.replace('put://', 'http://') - url = url.replace('puts://', 'https://') - url = url.replace('get://', 'http://') - url = url.replace('gets://', 'https://') - url = url.replace('put://', 'http://') - url = url.replace('puts://', 'https://') - url = url.replace('delete://', 'http://') - url = url.replace('deletes://', 'https://') + schema = kwargs['meta'].get('schema').lower().strip() + + # Choose POST, GET etc from requests + requests_method = getattr(requests, re.sub(rf's$', '', schema)) + + if schema.lower().endswith('s'): + url = re.sub(rf'^{schema}', 'https', url) + else: + url = re.sub(rf'^{schema}', 'http', url) headers = {} params = {} @@ -74,7 +71,7 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): try: - r = requests_method(results.get('url'), + r = requests_method(url, auth=auth, data=body.encode('utf-8') if type(body) is str else body, headers=headers, diff --git a/changedetectionio/tests/test_add_replace_remove_filter.py b/changedetectionio/tests/test_add_replace_remove_filter.py index 867a0b552e5..48c584f4cbe 100644 --- a/changedetectionio/tests/test_add_replace_remove_filter.py +++ b/changedetectionio/tests/test_add_replace_remove_filter.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 + import os.path -import time + from flask import url_for from .util import live_server_setup, wait_for_all_checks, wait_for_notification_endpoint_output -from changedetectionio import html_tools def set_original(excluding=None, add_line=None): From 585fccdb5e7e4f6db2e33f4b1b5e340d8db92e0c Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 09:40:54 +0100 Subject: [PATCH 3/9] cleanup --- changedetectionio/apprise_plugin/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index 307c463a113..13bf11fe7ab 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -23,7 +23,8 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): schema = kwargs['meta'].get('schema').lower().strip() # Choose POST, GET etc from requests - requests_method = getattr(requests, re.sub(rf's$', '', schema)) + method = re.sub(rf's$', '', schema) + requests_method = getattr(requests, method) if schema.lower().endswith('s'): url = re.sub(rf'^{schema}', 'https', url) @@ -59,18 +60,17 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): auth = (unquote_plus(results.get('user'))) if '{' in body[:100]: + json_header = 'application/json; charset=utf-8' try: - # Try to auto-guess if it's JSON - h = 'application/json; charset=utf-8' + # Try if it's JSON json.loads(body) - headers['Content-Type'] = h + headers['Content-Type'] = json_header except ValueError as e: - logger.warning(f"Could not automatically add '{h}' header to the notification because the document failed to parse as JSON: {e}") + logger.warning(f"Could not automatically add '{json_header}' header to the notification because the document failed to parse as JSON: {e}") pass - status_str = '' + status_str = '' try: - r = requests_method(url, auth=auth, data=body.encode('utf-8') if type(body) is str else body, @@ -79,15 +79,15 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): ) if r.status_code not in (requests.codes.created, requests.codes.ok): - status_str = f"Error sending '{schema}' request to {url} - Status: {r.status_code}: '{r.reason}'" + status_str = f"Error sending '{method.upper()}' request to {url} - Status: {r.status_code}: '{r.reason}'" logger.error(status_str) has_error = True else: - logger.info(f"Sent '{schema}' request to {url}") + logger.info(f"Sent '{method.upper()}' request to {url}") has_error = False except requests.RequestException as e: - status_str = f"Error sending '{schema}' request to {url} - {str(e)}" + status_str = f"Error sending '{method.upper()}' request to {url} - {str(e)}" logger.error(status_str) has_error = True From cc74acb8cbae7ace4612e7488c92b236b2dc4604 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 09:44:23 +0100 Subject: [PATCH 4/9] cleanup --- changedetectionio/apprise_plugin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index 13bf11fe7ab..7d2d911f4b9 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -59,7 +59,7 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): elif results.get('user'): auth = (unquote_plus(results.get('user'))) - if '{' in body[:100]: + if body and '{' in body[:100]: json_header = 'application/json; charset=utf-8' try: # Try if it's JSON From 119e1385d4f5c4221809b954418dc59abb143c1b Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 09:46:54 +0100 Subject: [PATCH 5/9] Improving auto header addition --- changedetectionio/apprise_plugin/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index 7d2d911f4b9..b8937c03fb2 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -1,6 +1,7 @@ # include the decorator from apprise.decorators import notify from loguru import logger +from requests.structures import CaseInsensitiveDict @notify(on="delete") @@ -31,7 +32,7 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): else: url = re.sub(rf'^{schema}', 'http', url) - headers = {} + headers = CaseInsensitiveDict({}) params = {} auth = None has_error = False @@ -59,7 +60,8 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): elif results.get('user'): auth = (unquote_plus(results.get('user'))) - if body and '{' in body[:100]: + # If it smells like it could be JSON and no content-type was already set, offer a default content type. + if body and '{' in body[:100] and not headers.get('Content-Type'): json_header = 'application/json; charset=utf-8' try: # Try if it's JSON From fb4fc8c3ebc42d22a4a1f279edbbbd98ea3a818c Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 10:43:31 +0100 Subject: [PATCH 6/9] fixing param vs url handling --- changedetectionio/apprise_plugin/__init__.py | 51 ++++++++++---------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py index b8937c03fb2..ad8392cab1e 100644 --- a/changedetectionio/apprise_plugin/__init__.py +++ b/changedetectionio/apprise_plugin/__init__.py @@ -27,38 +27,33 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): method = re.sub(rf's$', '', schema) requests_method = getattr(requests, method) - if schema.lower().endswith('s'): - url = re.sub(rf'^{schema}', 'https', url) - else: - url = re.sub(rf'^{schema}', 'http', url) - headers = CaseInsensitiveDict({}) - params = {} + params = CaseInsensitiveDict({}) # Added to requests auth = None has_error = False # Convert /foobar?+some-header=hello to proper header dictionary results = apprise_parse_url(url) - if results: - # Add our headers that the user can potentially over-ride if they wish - # to to our returned result set and tidy entries by unquoting them - headers = {unquote_plus(x): unquote_plus(y) - for x, y in results['qsd+'].items()} - - # https://github.com/caronc/apprise/wiki/Notify_Custom_JSON#get-parameter-manipulation - # In Apprise, it relies on prefixing each request arg with "-", because it uses say &method=update as a flag for apprise - # but here we are making straight requests, so we need todo convert this against apprise's logic - for k, v in results['qsd'].items(): - if not k.strip('+-') in results['qsd+'].keys(): - params[unquote_plus(k)] = unquote_plus(v) - - # Determine Authentication - auth = '' - if results.get('user') and results.get('password'): - auth = (unquote_plus(results.get('user')), unquote_plus(results.get('user'))) - elif results.get('user'): - auth = (unquote_plus(results.get('user'))) + + # Add our headers that the user can potentially over-ride if they wish + # to to our returned result set and tidy entries by unquoting them + headers = {unquote_plus(x): unquote_plus(y) + for x, y in results['qsd+'].items()} + + # https://github.com/caronc/apprise/wiki/Notify_Custom_JSON#get-parameter-manipulation + # In Apprise, it relies on prefixing each request arg with "-", because it uses say &method=update as a flag for apprise + # but here we are making straight requests, so we need todo convert this against apprise's logic + for k, v in results['qsd'].items(): + if not k.strip('+-') in results['qsd+'].keys(): + params[unquote_plus(k)] = unquote_plus(v) + + # Determine Authentication + auth = '' + if results.get('user') and results.get('password'): + auth = (unquote_plus(results.get('user')), unquote_plus(results.get('user'))) + elif results.get('user'): + auth = (unquote_plus(results.get('user'))) # If it smells like it could be JSON and no content-type was already set, offer a default content type. if body and '{' in body[:100] and not headers.get('Content-Type'): @@ -71,6 +66,12 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs): logger.warning(f"Could not automatically add '{json_header}' header to the notification because the document failed to parse as JSON: {e}") pass + # POSTS -> HTTPS etc + if schema.lower().endswith('s'): + url = re.sub(rf'^{schema}', 'https', results.get('url')) + else: + url = re.sub(rf'^{schema}', 'http', results.get('url')) + status_str = '' try: r = requests_method(url, From 47bbe7831d35fd905a1b525e79fa03b776bc5e14 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 12:28:40 +0100 Subject: [PATCH 7/9] Push notification immediately on test --- changedetectionio/flask_app.py | 19 ++++- changedetectionio/notification.py | 6 +- changedetectionio/static/js/notifications.js | 73 ++++++++++--------- .../static/styles/scss/styles.scss | 10 ++- changedetectionio/static/styles/styles.css | 9 ++- .../templates/_common_fields.html | 4 +- changedetectionio/tests/test_notification.py | 12 +-- 7 files changed, 82 insertions(+), 51 deletions(-) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 7d0c9d5ccf9..122839b89f8 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -598,17 +598,28 @@ def ajax_callback_send_notification_test(watch_uuid=None): if 'notification_title' in request.form and request.form['notification_title'].strip(): n_object['notification_title'] = request.form.get('notification_title', '').strip() + elif datastore.data['settings']['application'].get('notification_title'): + n_object['notification_title'] = datastore.data['settings']['application'].get('notification_title') + else: + n_object['notification_title'] = "Test title" if 'notification_body' in request.form and request.form['notification_body'].strip(): n_object['notification_body'] = request.form.get('notification_body', '').strip() + elif datastore.data['settings']['application'].get('notification_body'): + n_object['notification_body'] = datastore.data['settings']['application'].get('notification_body') + else: + n_object['notification_body'] = "Test body" + n_object['as_async'] = False n_object.update(watch.extra_notification_token_values()) + from .notification import process_notification + sent_obj = process_notification(n_object, datastore) - from . import update_worker - new_worker = update_worker.update_worker(update_q, notification_q, app, datastore) - new_worker.queue_notification_for_watch(notification_q=notification_q, n_object=n_object, watch=watch) except Exception as e: - return make_response(f"Error: str(e)", 400) + e_str = str(e) + e_str=e_str.replace("DEBUG - .CustomNotifyPluginWrapper'>",'') + + return make_response(e_str, 400) return 'OK - Sent test notifications' diff --git a/changedetectionio/notification.py b/changedetectionio/notification.py index ddd859f576d..7eed328d129 100644 --- a/changedetectionio/notification.py +++ b/changedetectionio/notification.py @@ -67,6 +67,10 @@ def process_notification(n_object, datastore): sent_objs = [] from .apprise_asset import asset + + if 'as_async' in n_object: + asset.async_mode = n_object.get('as_async') + apobj = apprise.Apprise(debug=True, asset=asset) if not n_object.get('notification_urls'): @@ -157,8 +161,6 @@ def process_notification(n_object, datastore): attach=n_object.get('screenshot', None) ) - # Give apprise time to register an error - time.sleep(3) # Returns empty string if nothing found, multi-line string otherwise log_value = logs.getvalue() diff --git a/changedetectionio/static/js/notifications.js b/changedetectionio/static/js/notifications.js index 95a0763c4ec..1cccd8510b1 100644 --- a/changedetectionio/static/js/notifications.js +++ b/changedetectionio/static/js/notifications.js @@ -1,42 +1,43 @@ -$(document).ready(function() { +$(document).ready(function () { - $('#add-email-helper').click(function (e) { - e.preventDefault(); - email = prompt("Destination email"); - if(email) { - var n = $(".notification-urls"); - var p=email_notification_prefix; - $(n).val( $.trim( $(n).val() )+"\n"+email_notification_prefix+email ); - } - }); - - $('#send-test-notification').click(function (e) { - e.preventDefault(); - - data = { - notification_body: $('#notification_body').val(), - notification_format: $('#notification_format').val(), - notification_title: $('#notification_title').val(), - notification_urls: $('.notification-urls').val(), - tags: $('#tags').val(), - window_url: window.location.href, - } + $('#add-email-helper').click(function (e) { + e.preventDefault(); + email = prompt("Destination email"); + if (email) { + var n = $(".notification-urls"); + var p = email_notification_prefix; + $(n).val($.trim($(n).val()) + "\n" + email_notification_prefix + email); + } + }); + $('#send-test-notification').click(function (e) { + e.preventDefault(); - $.ajax({ - type: "POST", - url: notification_base_url, - data : data, - statusCode: { - 400: function(data) { - // More than likely the CSRF token was lost when the server restarted - alert(data.responseText); + data = { + notification_body: $('#notification_body').val(), + notification_format: $('#notification_format').val(), + notification_title: $('#notification_title').val(), + notification_urls: $('.notification-urls').val(), + tags: $('#tags').val(), + window_url: window.location.href, } - } - }).done(function(data){ - console.log(data); - alert(data); - }) - }); + + $('.notifications-wrapper .spinner').fadeIn(); + $('#notification-test-log').show(); + $.ajax({ + type: "POST", + url: notification_base_url, + data: data, + statusCode: { + 400: function (data) { + $("#notification-test-log>span").text(data.responseText); + }, + } + }).done(function (data) { + $("#notification-test-log>span").text(data); + }).always(function () { + $('.notifications-wrapper .spinner').hide(); + }) + }); }); diff --git a/changedetectionio/static/styles/scss/styles.scss b/changedetectionio/static/styles/scss/styles.scss index ecaf7ed966e..4c698088d00 100644 --- a/changedetectionio/static/styles/scss/styles.scss +++ b/changedetectionio/static/styles/scss/styles.scss @@ -380,7 +380,15 @@ a.pure-button-selected { } .notifications-wrapper { - padding: 0.5rem 0 1rem 0; + padding-top: 0.5rem; + #notification-test-log { + padding-top: 1rem; + white-space: pre-wrap; + word-break: break-word; + overflow-wrap: break-word; + max-width: 100%; + box-sizing: border-box; + } } label { diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css index 1600fde3224..d49506dc874 100644 --- a/changedetectionio/static/styles/styles.css +++ b/changedetectionio/static/styles/styles.css @@ -780,7 +780,14 @@ a.pure-button-selected { cursor: pointer; } .notifications-wrapper { - padding: 0.5rem 0 1rem 0; } + padding-top: 0.5rem; } + .notifications-wrapper #notification-test-log { + padding-top: 1rem; + white-space: pre-wrap; + word-break: break-word; + overflow-wrap: break-word; + max-width: 100%; + box-sizing: border-box; } label:hover { cursor: pointer; } diff --git a/changedetectionio/templates/_common_fields.html b/changedetectionio/templates/_common_fields.html index 9447f9035bf..53d27a50bc6 100644 --- a/changedetectionio/templates/_common_fields.html +++ b/changedetectionio/templates/_common_fields.html @@ -24,11 +24,13 @@
- Send test notification + Send test notification {% if emailprefix %} Add email Add an email address {% endif %} Notification debug logs +
+
diff --git a/changedetectionio/tests/test_notification.py b/changedetectionio/tests/test_notification.py index b558546fcd6..0c20a9f10b0 100644 --- a/changedetectionio/tests/test_notification.py +++ b/changedetectionio/tests/test_notification.py @@ -360,7 +360,10 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage #live_server_setup(live_server) set_original_response() if os.path.isfile("test-datastore/notification.txt"): - os.unlink("test-datastore/notification.txt") + os.unlink("test-datastore/notification.txt") \ + + # 1995 UTF-8 content should be encoded + test_body = 'change detection is cool 网站监测 内容更新了' # otherwise other settings would have already existed from previous tests in this file res = client.post( @@ -368,8 +371,7 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage data={ "application-fetch_backend": "html_requests", "application-minutes_between_check": 180, - #1995 UTF-8 content should be encoded - "application-notification_body": 'change detection is cool 网站监测 内容更新了', + "application-notification_body": test_body, "application-notification_format": default_notification_format, "application-notification_urls": "", "application-notification_title": "New ChangeDetection.io Notification - {{ watch_url }}", @@ -399,12 +401,10 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage assert res.status_code != 400 assert res.status_code != 500 - # Give apprise time to fire - time.sleep(4) with open("test-datastore/notification.txt", 'r') as f: x = f.read() - assert 'change detection is cool 网站监测 内容更新了' in x + assert test_body in x os.unlink("test-datastore/notification.txt") From 5988d520dfa4a57e9fb672cf824d1ab6980c9507 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 12:35:07 +0100 Subject: [PATCH 8/9] Handle low level errors too --- changedetectionio/static/js/notifications.js | 11 ++++++++++- changedetectionio/tests/util.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changedetectionio/static/js/notifications.js b/changedetectionio/static/js/notifications.js index 1cccd8510b1..fa5326ebb10 100644 --- a/changedetectionio/static/js/notifications.js +++ b/changedetectionio/static/js/notifications.js @@ -35,8 +35,17 @@ $(document).ready(function () { } }).done(function (data) { $("#notification-test-log>span").text(data); + }).fail(function (jqXHR, textStatus, errorThrown) { + // Handle connection refused or other errors + if (textStatus === "error" && errorThrown === "") { + console.error("Connection refused or server unreachable"); + $("#notification-test-log>span").text("Error: Connection refused or server is unreachable."); + } else { + console.error("Error:", textStatus, errorThrown); + $("#notification-test-log>span").text("An error occurred: " + textStatus); + } }).always(function () { - $('.notifications-wrapper .spinner').hide(); + $('.notifications-wrapper .spinner').hide(); }) }); }); diff --git a/changedetectionio/tests/util.py b/changedetectionio/tests/util.py index bf5305b1d5e..28e6efc4a95 100644 --- a/changedetectionio/tests/util.py +++ b/changedetectionio/tests/util.py @@ -218,6 +218,7 @@ def test_method(): # Where we POST to as a notification @live_server.app.route('/test_notification_endpoint', methods=['POST', 'GET']) def test_notification_endpoint(): + with open("test-datastore/notification.txt", "wb") as f: # Debug method, dump all POST to file also, used to prove #65 data = request.stream.read() From 4bfdf59ae8a8f466eae66633563e338d334901e8 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 8 Jan 2025 12:37:48 +0100 Subject: [PATCH 9/9] comment/code tweak --- changedetectionio/flask_app.py | 5 ++++- changedetectionio/tests/util.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 122839b89f8..f603e012171 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -617,7 +617,10 @@ def ajax_callback_send_notification_test(watch_uuid=None): except Exception as e: e_str = str(e) - e_str=e_str.replace("DEBUG - .CustomNotifyPluginWrapper'>",'') + # Remove this text which is not important and floods the container + e_str = e_str.replace( + "DEBUG - .CustomNotifyPluginWrapper'>", + '') return make_response(e_str, 400) diff --git a/changedetectionio/tests/util.py b/changedetectionio/tests/util.py index 28e6efc4a95..739b191b7da 100644 --- a/changedetectionio/tests/util.py +++ b/changedetectionio/tests/util.py @@ -218,7 +218,7 @@ def test_method(): # Where we POST to as a notification @live_server.app.route('/test_notification_endpoint', methods=['POST', 'GET']) def test_notification_endpoint(): - + with open("test-datastore/notification.txt", "wb") as f: # Debug method, dump all POST to file also, used to prove #65 data = request.stream.read()