kopia lustrzana https://github.com/dgtlmoon/changedetection.io
New feature - Helper button to trigger a scan/access test of all proxies for a particular watch (#1685)
rodzic
20d65cdd26
commit
1987e109e8
|
@ -13,3 +13,6 @@ include changedetection.py
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude node_modules
|
global-exclude node_modules
|
||||||
global-exclude venv
|
global-exclude venv
|
||||||
|
|
||||||
|
global-exclude test-datastore
|
||||||
|
global-exclude changedetection.io*dist-info
|
||||||
|
|
|
@ -1439,6 +1439,10 @@ def changedetection_app(config=None, datastore_o=None):
|
||||||
import changedetectionio.blueprint.tags as tags
|
import changedetectionio.blueprint.tags as tags
|
||||||
app.register_blueprint(tags.construct_blueprint(datastore), url_prefix='/tags')
|
app.register_blueprint(tags.construct_blueprint(datastore), url_prefix='/tags')
|
||||||
|
|
||||||
|
import changedetectionio.blueprint.check_proxies as check_proxies
|
||||||
|
app.register_blueprint(check_proxies.construct_blueprint(datastore=datastore), url_prefix='/check_proxy')
|
||||||
|
|
||||||
|
|
||||||
# @todo handle ctrl break
|
# @todo handle ctrl break
|
||||||
ticker_thread = threading.Thread(target=ticker_thread_check_time_launch_checks).start()
|
ticker_thread = threading.Thread(target=ticker_thread_check_time_launch_checks).start()
|
||||||
threading.Thread(target=notification_runner).start()
|
threading.Thread(target=notification_runner).start()
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import Blueprint
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
from changedetectionio.processors import text_json_diff
|
||||||
|
from changedetectionio.store import ChangeDetectionStore
|
||||||
|
|
||||||
|
|
||||||
|
STATUS_CHECKING = 0
|
||||||
|
STATUS_FAILED = 1
|
||||||
|
STATUS_OK = 2
|
||||||
|
THREADPOOL_MAX_WORKERS = 3
|
||||||
|
_DEFAULT_POOL = ThreadPoolExecutor(max_workers=THREADPOOL_MAX_WORKERS)
|
||||||
|
|
||||||
|
|
||||||
|
# Maybe use fetch-time if its >5 to show some expected load time?
|
||||||
|
def threadpool(f, executor=None):
|
||||||
|
@wraps(f)
|
||||||
|
def wrap(*args, **kwargs):
|
||||||
|
return (executor or _DEFAULT_POOL).submit(f, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def construct_blueprint(datastore: ChangeDetectionStore):
|
||||||
|
check_proxies_blueprint = Blueprint('check_proxies', __name__)
|
||||||
|
checks_in_progress = {}
|
||||||
|
|
||||||
|
@threadpool
|
||||||
|
def long_task(uuid, preferred_proxy):
|
||||||
|
import time
|
||||||
|
from changedetectionio import content_fetcher
|
||||||
|
|
||||||
|
status = {'status': '', 'length': 0, 'text': ''}
|
||||||
|
from jinja2 import Environment, BaseLoader
|
||||||
|
|
||||||
|
contents = ''
|
||||||
|
now = time.time()
|
||||||
|
try:
|
||||||
|
update_handler = text_json_diff.perform_site_check(datastore=datastore)
|
||||||
|
changed_detected, update_obj, contents = update_handler.run(uuid, preferred_proxy=preferred_proxy, skip_when_checksum_same=False)
|
||||||
|
# title, size is len contents not len xfer
|
||||||
|
except content_fetcher.Non200ErrorCodeReceived as e:
|
||||||
|
if e.status_code == 404:
|
||||||
|
status.update({'status': 'OK', 'length': len(contents), 'text': f"OK but 404 (page not found)"})
|
||||||
|
elif e.status_code == 403:
|
||||||
|
status.update({'status': 'ERROR', 'length': len(contents), 'text': f"403 - Access denied"})
|
||||||
|
else:
|
||||||
|
status.update({'status': 'ERROR', 'length': len(contents), 'text': f"Status code: {e.status_code}"})
|
||||||
|
except content_fetcher.EmptyReply as e:
|
||||||
|
status.update({'status': 'ERROR OTHER', 'length': len(contents) if contents else 0, 'text': "Empty reply, needs chrome?"})
|
||||||
|
except Exception as e:
|
||||||
|
status.update({'status': 'ERROR OTHER', 'length': len(contents) if contents else 0, 'text': 'Error: '+str(e)})
|
||||||
|
else:
|
||||||
|
status.update({'status': 'OK', 'length': len(contents), 'text': ''})
|
||||||
|
|
||||||
|
if status.get('text'):
|
||||||
|
status['text'] = Environment(loader=BaseLoader()).from_string('{{text|e}}').render({'text': status['text']})
|
||||||
|
|
||||||
|
status['time'] = "{:.2f}s".format(time.time() - now)
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
def _recalc_check_status(uuid):
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for k, v in checks_in_progress.get(uuid, {}).items():
|
||||||
|
try:
|
||||||
|
r_1 = v.result(timeout=0.05)
|
||||||
|
except Exception as e:
|
||||||
|
# If timeout error?
|
||||||
|
results[k] = {'status': 'RUNNING'}
|
||||||
|
|
||||||
|
else:
|
||||||
|
results[k] = r_1
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@check_proxies_blueprint.route("/<string:uuid>/status", methods=['GET'])
|
||||||
|
def get_recheck_status(uuid):
|
||||||
|
results = _recalc_check_status(uuid=uuid)
|
||||||
|
return results
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@check_proxies_blueprint.route("/<string:uuid>/start", methods=['GET'])
|
||||||
|
def start_check(uuid):
|
||||||
|
|
||||||
|
if not datastore.proxy_list:
|
||||||
|
return
|
||||||
|
|
||||||
|
# @todo - Cancel any existing runs
|
||||||
|
checks_in_progress[uuid] = {}
|
||||||
|
|
||||||
|
for k, v in datastore.proxy_list.items():
|
||||||
|
if not checks_in_progress[uuid].get(k):
|
||||||
|
checks_in_progress[uuid][k] = long_task(uuid=uuid, preferred_proxy=k)
|
||||||
|
|
||||||
|
results = _recalc_check_status(uuid=uuid)
|
||||||
|
return results
|
||||||
|
|
||||||
|
return check_proxies_blueprint
|
|
@ -9,7 +9,7 @@ class difference_detection_processor():
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run(self, uuid, skip_when_checksum_same=True):
|
def run(self, uuid, skip_when_checksum_same=True, preferred_proxy=None):
|
||||||
update_obj = {'last_notification_error': False, 'last_error': False}
|
update_obj = {'last_notification_error': False, 'last_error': False}
|
||||||
some_data = 'xxxxx'
|
some_data = 'xxxxx'
|
||||||
update_obj["previous_md5"] = hashlib.md5(some_data.encode('utf-8')).hexdigest()
|
update_obj["previous_md5"] = hashlib.md5(some_data.encode('utf-8')).hexdigest()
|
||||||
|
|
|
@ -50,7 +50,7 @@ class perform_site_check(difference_detection_processor):
|
||||||
|
|
||||||
return regex
|
return regex
|
||||||
|
|
||||||
def run(self, uuid, skip_when_checksum_same=True):
|
def run(self, uuid, skip_when_checksum_same=True, preferred_proxy=None):
|
||||||
changed_detected = False
|
changed_detected = False
|
||||||
screenshot = False # as bytes
|
screenshot = False # as bytes
|
||||||
stripped_text_from_html = ""
|
stripped_text_from_html = ""
|
||||||
|
@ -105,7 +105,11 @@ class perform_site_check(difference_detection_processor):
|
||||||
# If the klass doesnt exist, just use a default
|
# If the klass doesnt exist, just use a default
|
||||||
klass = getattr(content_fetcher, "html_requests")
|
klass = getattr(content_fetcher, "html_requests")
|
||||||
|
|
||||||
|
if preferred_proxy:
|
||||||
|
proxy_id = preferred_proxy
|
||||||
|
else:
|
||||||
proxy_id = self.datastore.get_preferred_proxy_for_watch(uuid=uuid)
|
proxy_id = self.datastore.get_preferred_proxy_for_watch(uuid=uuid)
|
||||||
|
|
||||||
proxy_url = None
|
proxy_url = None
|
||||||
if proxy_id:
|
if proxy_id:
|
||||||
proxy_url = self.datastore.proxy_list.get(proxy_id).get('url')
|
proxy_url = self.datastore.proxy_list.get(proxy_id).get('url')
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
$(function () {
|
||||||
|
/* add container before each proxy location to show status */
|
||||||
|
|
||||||
|
var option_li = $('.fetch-backend-proxy li').filter(function() {
|
||||||
|
return $("input",this)[0].value.length >0;
|
||||||
|
});
|
||||||
|
|
||||||
|
//var option_li = $('.fetch-backend-proxy li');
|
||||||
|
var isActive = false;
|
||||||
|
$(option_li).prepend('<div class="proxy-status"></div>');
|
||||||
|
$(option_li).append('<div class="proxy-timing"></div><div class="proxy-check-details"></div>');
|
||||||
|
|
||||||
|
function set_proxy_check_status(proxy_key, state) {
|
||||||
|
// select input by value name
|
||||||
|
const proxy_li = $("input[value=" + proxy_key + "]").parent();
|
||||||
|
if (state['status'] === 'RUNNING') {
|
||||||
|
$('.proxy-status', proxy_li).html('<span class="spinner"></span>');
|
||||||
|
}
|
||||||
|
if (state['status'] === 'OK') {
|
||||||
|
$('.proxy-status', proxy_li).html('<span style="color: green; font-weight: bold" >OK</span>');
|
||||||
|
$('.proxy-check-details', proxy_li).html(state['text']);
|
||||||
|
}
|
||||||
|
if (state['status'] === 'ERROR' || state['status'] === 'ERROR OTHER') {
|
||||||
|
$('.proxy-status', proxy_li).html('<span style="color: red; font-weight: bold" >X</span>');
|
||||||
|
$('.proxy-check-details', proxy_li).html(state['text']);
|
||||||
|
}
|
||||||
|
$('.proxy-timing', proxy_li).html(state['time']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function pollServer() {
|
||||||
|
if (isActive) {
|
||||||
|
window.setTimeout(function () {
|
||||||
|
$.ajax({
|
||||||
|
url: proxy_recheck_status_url,
|
||||||
|
success: function (data) {
|
||||||
|
var all_done = true;
|
||||||
|
$.each(data, function (proxy_key, state) {
|
||||||
|
set_proxy_check_status(proxy_key, state);
|
||||||
|
if (state['status'] === 'RUNNING') {
|
||||||
|
all_done = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (all_done) {
|
||||||
|
console.log("Shutting down poller, all done.")
|
||||||
|
isActive = false;
|
||||||
|
} else {
|
||||||
|
pollServer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
//ERROR HANDLING
|
||||||
|
pollServer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#check-all-proxies').click(function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
$('body').addClass('proxy-check-active');
|
||||||
|
$('.proxy-check-details').html('');
|
||||||
|
$('.proxy-status').html('<span class="spinner"></span>').fadeIn();
|
||||||
|
$('.proxy-timing').html('');
|
||||||
|
|
||||||
|
// Request start, needs CSRF?
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: recheck_proxy_start_url,
|
||||||
|
}).done(function (data) {
|
||||||
|
$.each(data, function (proxy_key, state) {
|
||||||
|
set_proxy_check_status(proxy_key, state['status'])
|
||||||
|
});
|
||||||
|
isActive = true;
|
||||||
|
pollServer();
|
||||||
|
|
||||||
|
}).fail(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
alert('There was an error communicating with the server.');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ ul#requests-extra_proxies {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* each proxy entry is a `table` */
|
/* each proxy entry is a `table` */
|
||||||
table {
|
table {
|
||||||
tr {
|
tr {
|
||||||
|
@ -15,3 +16,30 @@ ul#requests-extra_proxies {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#request {
|
||||||
|
/* Auto proxy scan/checker */
|
||||||
|
label[for=proxy] {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.proxy-check-active {
|
||||||
|
#request {
|
||||||
|
.proxy-status {
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-check-details {
|
||||||
|
font-size: 80%;
|
||||||
|
color: #555;
|
||||||
|
display: block;
|
||||||
|
padding-left: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-timing {
|
||||||
|
font-size: 80%;
|
||||||
|
padding-left: 1rem;
|
||||||
|
color: var(--color-link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,6 +95,25 @@ ul#requests-extra_proxies {
|
||||||
ul#requests-extra_proxies table tr {
|
ul#requests-extra_proxies table tr {
|
||||||
display: inline; }
|
display: inline; }
|
||||||
|
|
||||||
|
#request {
|
||||||
|
/* Auto proxy scan/checker */ }
|
||||||
|
#request label[for=proxy] {
|
||||||
|
display: inline-block; }
|
||||||
|
|
||||||
|
body.proxy-check-active #request .proxy-status {
|
||||||
|
width: 2em; }
|
||||||
|
|
||||||
|
body.proxy-check-active #request .proxy-check-details {
|
||||||
|
font-size: 80%;
|
||||||
|
color: #555;
|
||||||
|
display: block;
|
||||||
|
padding-left: 4em; }
|
||||||
|
|
||||||
|
body.proxy-check-active #request .proxy-timing {
|
||||||
|
font-size: 80%;
|
||||||
|
padding-left: 1rem;
|
||||||
|
color: var(--color-link); }
|
||||||
|
|
||||||
.pagination-page-info {
|
.pagination-page-info {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
@ -283,10 +302,6 @@ html[data-darkmode="true"] {
|
||||||
--color-icon-github-hover: var(--color-grey-700);
|
--color-icon-github-hover: var(--color-grey-700);
|
||||||
--color-watch-table-error: var(--color-light-red);
|
--color-watch-table-error: var(--color-light-red);
|
||||||
--color-watch-table-row-text: var(--color-grey-800); }
|
--color-watch-table-row-text: var(--color-grey-800); }
|
||||||
html[data-darkmode="true"] #toggle-light-mode .icon-light {
|
|
||||||
display: none; }
|
|
||||||
html[data-darkmode="true"] #toggle-light-mode .icon-dark {
|
|
||||||
display: block; }
|
|
||||||
html[data-darkmode="true"] .icon-spread {
|
html[data-darkmode="true"] .icon-spread {
|
||||||
filter: hue-rotate(-10deg) brightness(1.5); }
|
filter: hue-rotate(-10deg) brightness(1.5); }
|
||||||
html[data-darkmode="true"] .watch-table .title-col a[target="_blank"]::after,
|
html[data-darkmode="true"] .watch-table .title-col a[target="_blank"]::after,
|
||||||
|
@ -339,6 +354,10 @@ a.github-link {
|
||||||
width: 3rem; }
|
width: 3rem; }
|
||||||
#toggle-light-mode .icon-dark {
|
#toggle-light-mode .icon-dark {
|
||||||
display: none; }
|
display: none; }
|
||||||
|
#toggle-light-mode.dark .icon-light {
|
||||||
|
display: none; }
|
||||||
|
#toggle-light-mode.dark .icon-dark {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
#toggle-search {
|
#toggle-search {
|
||||||
width: 2rem; }
|
width: 2rem; }
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{% macro render_field(field) %}
|
{% macro render_field(field) %}
|
||||||
<div {% if field.errors %} class="error" {% endif %}>{{ field(**kwargs)|safe }}
|
|
||||||
<div {% if field.errors %} class="error" {% endif %}>{{ field.label }}</div>
|
<div {% if field.errors %} class="error" {% endif %}>{{ field.label }}</div>
|
||||||
|
<div {% if field.errors %} class="error" {% endif %}>{{ field(**kwargs)|safe }}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class=errors>
|
<ul class=errors>
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
|
@ -25,18 +24,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro render_field(field) %}
|
|
||||||
<div {% if field.errors %} class="error" {% endif %}>{{ field.label }}</div>
|
|
||||||
<div {% if field.errors %} class="error" {% endif %}>{{ field(**kwargs)|safe }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<ul class=errors>
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<li>{{ error }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro render_simple_field(field) %}
|
{% macro render_simple_field(field) %}
|
||||||
<span class="label {% if field.errors %}error{% endif %}">{{ field.label }}</span>
|
<span class="label {% if field.errors %}error{% endif %}">{{ field.label }}</span>
|
||||||
|
|
|
@ -4,18 +4,19 @@
|
||||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||||
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
<script>
|
<script>
|
||||||
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
|
|
||||||
const watch_visual_selector_data_url="{{url_for('static_content', group='visual_selector_data', filename=uuid)}}";
|
|
||||||
const screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
|
||||||
const playwright_enabled={% if playwright_enabled %} true {% else %} false {% endif %};
|
|
||||||
|
|
||||||
{% if emailprefix %}
|
|
||||||
const email_notification_prefix=JSON.parse('{{ emailprefix|tojson }}');
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
const browser_steps_config=JSON.parse('{{ browser_steps_config|tojson }}');
|
const browser_steps_config=JSON.parse('{{ browser_steps_config|tojson }}');
|
||||||
const browser_steps_start_url="{{url_for('browser_steps.browsersteps_start_session', uuid=uuid)}}";
|
const browser_steps_start_url="{{url_for('browser_steps.browsersteps_start_session', uuid=uuid)}}";
|
||||||
const browser_steps_sync_url="{{url_for('browser_steps.browsersteps_ui_update', uuid=uuid)}}";
|
const browser_steps_sync_url="{{url_for('browser_steps.browsersteps_ui_update', uuid=uuid)}}";
|
||||||
|
{% if emailprefix %}
|
||||||
|
const email_notification_prefix=JSON.parse('{{ emailprefix|tojson }}');
|
||||||
|
{% endif %}
|
||||||
|
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
|
||||||
|
const playwright_enabled={% if playwright_enabled %} true {% else %} false {% endif %};
|
||||||
|
const recheck_proxy_start_url="{{url_for('check_proxies.start_check', uuid=uuid)}}";
|
||||||
|
const proxy_recheck_status_url="{{url_for('check_proxies.get_recheck_status', uuid=uuid)}}";
|
||||||
|
const screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
||||||
|
const watch_visual_selector_data_url="{{url_for('static_content', group='visual_selector_data', filename=uuid)}}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
<script src="{{url_for('static_content', group='js', filename='browser-steps.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='browser-steps.js')}}" defer></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script src="{{url_for('static_content', group='js', filename='recheck-proxy.js')}}" defer></script>
|
||||||
|
|
||||||
<div class="edit-form monospaced-textarea">
|
<div class="edit-form monospaced-textarea">
|
||||||
|
|
||||||
<div class="tabs collapsable">
|
<div class="tabs collapsable">
|
||||||
|
@ -111,7 +114,8 @@
|
||||||
</div>
|
</div>
|
||||||
{% if form.proxy %}
|
{% if form.proxy %}
|
||||||
<div class="pure-control-group inline-radio">
|
<div class="pure-control-group inline-radio">
|
||||||
{{ render_field(form.proxy, class="fetch-backend-proxy") }}
|
<div>{{ form.proxy.label }} <a href="" id="check-all-proxies" class="pure-button button-secondary button-xsmall" >Check/Scan all</a></div>
|
||||||
|
<div>{{ form.proxy(class="fetch-backend-proxy") }}</div>
|
||||||
<span class="pure-form-message-inline">
|
<span class="pure-form-message-inline">
|
||||||
Choose a proxy for this watch
|
Choose a proxy for this watch
|
||||||
</span>
|
</span>
|
||||||
|
|
Ładowanie…
Reference in New Issue