mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-12-20 15:05:46 +00:00
Compare commits
19 Commits
export-dat
...
darkmode
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6f8b44c27 | ||
|
|
49bed43cff | ||
|
|
4218f25cb8 | ||
|
|
dfdd69de23 | ||
|
|
f612df1b1b | ||
|
|
c82bf98f13 | ||
|
|
855605c434 | ||
|
|
3b2b1ea2f7 | ||
|
|
5219698b07 | ||
|
|
00be226210 | ||
|
|
3078ddb261 | ||
|
|
cdf691d5c4 | ||
|
|
50e50f1caf | ||
|
|
f96340c45e | ||
|
|
0696dfa30d | ||
|
|
b65f08eadb | ||
|
|
e3a1a4275a | ||
|
|
b2da26b2f1 | ||
|
|
427cf105a3 |
@@ -810,12 +810,10 @@ def changedetection_app(config=None, datastore_o=None):
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route("/diff/<string:uuid>", methods=['GET', 'POST'])
|
||||
@app.route("/diff/<string:uuid>", methods=['GET'])
|
||||
@login_required
|
||||
def diff_history_page(uuid):
|
||||
|
||||
from changedetectionio import forms
|
||||
|
||||
# More for testing, possible to return the first/only
|
||||
if uuid == 'first':
|
||||
uuid = list(datastore.data['watching'].keys()).pop()
|
||||
@@ -827,28 +825,6 @@ def changedetection_app(config=None, datastore_o=None):
|
||||
flash("No history found for the specified link, bad link?", "error")
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# For submission of requesting an extract
|
||||
extract_form = forms.extractDataForm(request.form)
|
||||
if request.method == 'POST':
|
||||
if not extract_form.validate():
|
||||
flash("An error occurred, please see below.", "error")
|
||||
|
||||
else:
|
||||
extract_regex = request.form.get('extract_regex').strip()
|
||||
output = watch.extract_regex_from_all_history(extract_regex)
|
||||
if output:
|
||||
watch_dir = os.path.join(datastore_o.datastore_path, uuid)
|
||||
response = make_response(send_from_directory(directory=watch_dir, path=output, as_attachment=True))
|
||||
response.headers['Content-type'] = 'text/csv'
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
response.headers['Pragma'] = 'no-cache'
|
||||
response.headers['Expires'] = 0
|
||||
return response
|
||||
|
||||
|
||||
flash('Nothing matches that RegEx', 'error')
|
||||
redirect(url_for('diff_history_page', uuid=uuid)+'#extract')
|
||||
|
||||
history = watch.history
|
||||
dates = list(history.keys())
|
||||
|
||||
@@ -891,24 +867,23 @@ def changedetection_app(config=None, datastore_o=None):
|
||||
watch.get('fetch_backend', None) is None and system_uses_webdriver) else False
|
||||
|
||||
output = render_template("diff.html",
|
||||
current_diff_url=watch['url'],
|
||||
current_previous_version=str(previous_version),
|
||||
dark_mode=getDarkModeSetting(),
|
||||
watch_a=watch,
|
||||
newest=newest_version_file_contents,
|
||||
previous=previous_version_file_contents,
|
||||
extra_stylesheets=extra_stylesheets,
|
||||
dark_mode=getDarkModeSetting(),
|
||||
versions=dates[:-1], # All except current/last
|
||||
uuid=uuid,
|
||||
newest_version_timestamp=dates[-1],
|
||||
current_previous_version=str(previous_version),
|
||||
current_diff_url=watch['url'],
|
||||
extra_title=" - Diff - {}".format(watch['title'] if watch['title'] else watch['url']),
|
||||
extract_form=extract_form,
|
||||
left_sticky=True,
|
||||
screenshot=screenshot_url,
|
||||
is_html_webdriver=is_html_webdriver,
|
||||
last_error=watch['last_error'],
|
||||
last_error_screenshot=watch.get_error_snapshot(),
|
||||
last_error_text=watch.get_error_text(),
|
||||
left_sticky=True,
|
||||
newest=newest_version_file_contents,
|
||||
newest_version_timestamp=dates[-1],
|
||||
previous=previous_version_file_contents,
|
||||
screenshot=screenshot_url,
|
||||
uuid=uuid,
|
||||
versions=dates[:-1], # All except current/last
|
||||
watch_a=watch
|
||||
last_error_screenshot=watch.get_error_snapshot()
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
@@ -448,8 +448,3 @@ class globalSettingsForm(Form):
|
||||
requests = FormField(globalSettingsRequestForm)
|
||||
application = FormField(globalSettingsApplicationForm)
|
||||
save_button = SubmitField('Save', render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
|
||||
class extractDataForm(Form):
|
||||
extract_regex = StringField('RegEx to extract', validators=[validators.Length(min=1, message="Needs a RegEx")])
|
||||
extract_submit_button = SubmitField('Extract as CSV', render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
@@ -318,47 +318,3 @@ class model(dict):
|
||||
if os.path.isfile(fname):
|
||||
return fname
|
||||
return False
|
||||
|
||||
def extract_regex_from_all_history(self, regex):
|
||||
import csv
|
||||
import re
|
||||
import datetime
|
||||
csv_output_filename = False
|
||||
csv_writer = False
|
||||
f = None
|
||||
|
||||
# self.history will be keyed with the full path
|
||||
for k, fname in self.history.items():
|
||||
if os.path.isfile(fname):
|
||||
with open(fname, "r") as f:
|
||||
contents = f.read()
|
||||
res = re.findall(regex, contents, re.MULTILINE)
|
||||
if res:
|
||||
if not csv_writer:
|
||||
# A file on the disk can be transferred much faster via flask than a string reply
|
||||
csv_output_filename = 'report.csv'
|
||||
f = open(os.path.join(self.watch_data_dir, csv_output_filename), 'w')
|
||||
# @todo some headers in the future
|
||||
#fieldnames = ['Epoch seconds', 'Date']
|
||||
csv_writer = csv.writer(f,
|
||||
delimiter=',',
|
||||
quotechar='"',
|
||||
quoting=csv.QUOTE_MINIMAL,
|
||||
#fieldnames=fieldnames
|
||||
)
|
||||
csv_writer.writerow(['Epoch seconds', 'Date'])
|
||||
# csv_writer.writeheader()
|
||||
|
||||
date_str = datetime.datetime.fromtimestamp(int(k)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
for r in res:
|
||||
row = [k, date_str]
|
||||
if isinstance(r, str):
|
||||
row.append(r)
|
||||
else:
|
||||
row+=r
|
||||
csv_writer.writerow(row)
|
||||
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
return csv_output_filename
|
||||
|
||||
@@ -13,8 +13,6 @@ $(document).ready(function () {
|
||||
} else if (hash_name === '#error-screenshot') {
|
||||
$("img#error-screenshot-img").attr('src', error_screenshot_url);
|
||||
$("#settings").hide();
|
||||
} else if (hash_name === '#extract') {
|
||||
$("#settings").hide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,6 @@ $(document).ready(function () {
|
||||
};
|
||||
|
||||
const setCookieValue = (value) => {
|
||||
document.cookie = `css_dark_mode=${value};max-age=31536000;path=/`
|
||||
document.cookie = `css_dark_mode=${value};max-age=31536000`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
--color-grey-850: #eee;
|
||||
--color-grey-900: #f2f2f2;
|
||||
--color-black: #000;
|
||||
--color-dark-red: #a00;
|
||||
--color-light-red: #dd0000;
|
||||
--color-background-page: var(--color-grey-100);
|
||||
--color-background-gradient-first: #5ad8f7;
|
||||
--color-background-gradient-second: #2f50af;
|
||||
@@ -29,9 +27,9 @@
|
||||
--color-link: #1b98f8;
|
||||
--color-menu-accent: #ed5900;
|
||||
--color-background-code: var(--color-grey-850);
|
||||
--color-error: var(--color-dark-red);
|
||||
--color-error: #a00;
|
||||
--color-error-input: #ffebeb;
|
||||
--color-error-list: var(--color-light-red);
|
||||
--color-error-list: #dd0000;
|
||||
--color-table-background: var(--color-background);
|
||||
--color-table-stripe: var(--color-grey-900);
|
||||
--color-text-tab: var(--color-white);
|
||||
@@ -86,9 +84,7 @@
|
||||
--color-text-menu-link-hover: var(--color-grey-300);
|
||||
--color-shadow-jump: var(--color-grey-500);
|
||||
--color-icon-github: var(--color-black);
|
||||
--color-icon-github-hover: var(--color-grey-300);
|
||||
--color-watch-table-error: var(--color-dark-red);
|
||||
--color-watch-table-row-text: var(--color-grey-100); }
|
||||
--color-icon-github-hover: var(--color-grey-300); }
|
||||
|
||||
html[data-darkmode="true"] {
|
||||
--color-link: #59bdfb;
|
||||
@@ -118,13 +114,9 @@ html[data-darkmode="true"] {
|
||||
--color-background-snapshot-age: var(--color-grey-200);
|
||||
--color-shadow-jump: var(--color-grey-200);
|
||||
--color-icon-github: var(--color-white);
|
||||
--color-icon-github-hover: var(--color-grey-700);
|
||||
--color-watch-table-error: var(--color-light-red);
|
||||
--color-watch-table-row-text: var(--color-grey-800); }
|
||||
--color-icon-github-hover: var(--color-grey-700); }
|
||||
html[data-darkmode="true"] .watch-controls img {
|
||||
opacity: 0.4; }
|
||||
html[data-darkmode="true"] .watch-table .unviewed {
|
||||
color: #fff; }
|
||||
html[data-darkmode="true"] .icon-spread {
|
||||
filter: hue-rotate(-10deg) brightness(1.5); }
|
||||
html[data-darkmode="true"] .watch-table .title-col a[target="_blank"]::after,
|
||||
@@ -136,8 +128,7 @@ html[data-darkmode="true"] {
|
||||
padding: 2em;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
border-radius: 5px; }
|
||||
#diff-ui #text {
|
||||
border-radius: 5px;
|
||||
font-size: 11px; }
|
||||
#diff-ui table {
|
||||
table-layout: fixed;
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
border-radius: 5px;
|
||||
|
||||
// The first tab 'text' diff
|
||||
#text {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
--color-grey-850: #eee;
|
||||
--color-grey-900: #f2f2f2;
|
||||
--color-black: #000;
|
||||
--color-dark-red: #a00;
|
||||
--color-light-red: #dd0000;
|
||||
|
||||
--color-background-page: var(--color-grey-100);
|
||||
--color-background-gradient-first: #5ad8f7;
|
||||
@@ -31,9 +29,9 @@
|
||||
--color-link: #1b98f8;
|
||||
--color-menu-accent: #ed5900;
|
||||
--color-background-code: var(--color-grey-850);
|
||||
--color-error: var(--color-dark-red);
|
||||
--color-error: #a00;
|
||||
--color-error-input: #ffebeb;
|
||||
--color-error-list: var(--color-light-red);
|
||||
--color-error-list: #dd0000;
|
||||
--color-table-background: var(--color-background);
|
||||
--color-table-stripe: var(--color-grey-900);
|
||||
--color-text-tab: var(--color-white);
|
||||
@@ -98,9 +96,6 @@
|
||||
--color-shadow-jump: var(--color-grey-500);
|
||||
--color-icon-github: var(--color-black);
|
||||
--color-icon-github-hover: var(--color-grey-300);
|
||||
|
||||
--color-watch-table-error: var(--color-dark-red);
|
||||
--color-watch-table-row-text: var(--color-grey-100);
|
||||
}
|
||||
|
||||
html[data-darkmode="true"] {
|
||||
@@ -128,6 +123,7 @@ html[data-darkmode="true"] {
|
||||
--color-text-input-description: var(--color-grey-600);
|
||||
--color-text-input-placeholder: var(--color-grey-600);
|
||||
--color-text-watch-tag-list: #fa3e92;
|
||||
|
||||
--color-background-code: var(--color-grey-200);
|
||||
|
||||
--color-background-tab: rgba(0, 0, 0, 0.2);
|
||||
@@ -137,8 +133,6 @@ html[data-darkmode="true"] {
|
||||
--color-shadow-jump: var(--color-grey-200);
|
||||
--color-icon-github: var(--color-white);
|
||||
--color-icon-github-hover: var(--color-grey-700);
|
||||
--color-watch-table-error: var(--color-light-red);
|
||||
--color-watch-table-row-text: var(--color-grey-800);
|
||||
|
||||
// Anything that can't be manipulated through variables follows.
|
||||
.watch-controls {
|
||||
@@ -146,9 +140,7 @@ html[data-darkmode="true"] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
.watch-table .unviewed {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon-spread {
|
||||
filter: hue-rotate(-10deg) brightness(1.5);
|
||||
}
|
||||
|
||||
@@ -121,16 +121,12 @@ code {
|
||||
width: 100%;
|
||||
font-size: 80%;
|
||||
|
||||
tr {
|
||||
&.unviewed {
|
||||
tr.unviewed {
|
||||
font-weight: bold;
|
||||
}
|
||||
color: var(--color-watch-table-row-text);
|
||||
}
|
||||
|
||||
|
||||
.error {
|
||||
color: var(--color-watch-table-error);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -326,12 +322,6 @@ a.pure-button-selected {
|
||||
padding: 0.5rem 0 1rem 0;
|
||||
}
|
||||
|
||||
label {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#notification-customisation {
|
||||
border: 1px solid var(--color-border-notification);
|
||||
padding: 0.5rem;
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
--color-grey-850: #eee;
|
||||
--color-grey-900: #f2f2f2;
|
||||
--color-black: #000;
|
||||
--color-dark-red: #a00;
|
||||
--color-light-red: #dd0000;
|
||||
--color-background-page: var(--color-grey-100);
|
||||
--color-background-gradient-first: #5ad8f7;
|
||||
--color-background-gradient-second: #2f50af;
|
||||
@@ -32,9 +30,9 @@
|
||||
--color-link: #1b98f8;
|
||||
--color-menu-accent: #ed5900;
|
||||
--color-background-code: var(--color-grey-850);
|
||||
--color-error: var(--color-dark-red);
|
||||
--color-error: #a00;
|
||||
--color-error-input: #ffebeb;
|
||||
--color-error-list: var(--color-light-red);
|
||||
--color-error-list: #dd0000;
|
||||
--color-table-background: var(--color-background);
|
||||
--color-table-stripe: var(--color-grey-900);
|
||||
--color-text-tab: var(--color-white);
|
||||
@@ -89,9 +87,7 @@
|
||||
--color-text-menu-link-hover: var(--color-grey-300);
|
||||
--color-shadow-jump: var(--color-grey-500);
|
||||
--color-icon-github: var(--color-black);
|
||||
--color-icon-github-hover: var(--color-grey-300);
|
||||
--color-watch-table-error: var(--color-dark-red);
|
||||
--color-watch-table-row-text: var(--color-grey-100); }
|
||||
--color-icon-github-hover: var(--color-grey-300); }
|
||||
|
||||
html[data-darkmode="true"] {
|
||||
--color-link: #59bdfb;
|
||||
@@ -121,13 +117,9 @@ html[data-darkmode="true"] {
|
||||
--color-background-snapshot-age: var(--color-grey-200);
|
||||
--color-shadow-jump: var(--color-grey-200);
|
||||
--color-icon-github: var(--color-white);
|
||||
--color-icon-github-hover: var(--color-grey-700);
|
||||
--color-watch-table-error: var(--color-light-red);
|
||||
--color-watch-table-row-text: var(--color-grey-800); }
|
||||
--color-icon-github-hover: var(--color-grey-700); }
|
||||
html[data-darkmode="true"] .watch-controls img {
|
||||
opacity: 0.4; }
|
||||
html[data-darkmode="true"] .watch-table .unviewed {
|
||||
color: #fff; }
|
||||
html[data-darkmode="true"] .icon-spread {
|
||||
filter: hue-rotate(-10deg) brightness(1.5); }
|
||||
html[data-darkmode="true"] .watch-table .title-col a[target="_blank"]::after,
|
||||
@@ -339,12 +331,10 @@ code {
|
||||
.watch-table {
|
||||
width: 100%;
|
||||
font-size: 80%; }
|
||||
.watch-table tr {
|
||||
color: var(--color-watch-table-row-text); }
|
||||
.watch-table tr.unviewed {
|
||||
font-weight: bold; }
|
||||
.watch-table .error {
|
||||
color: var(--color-watch-table-error); }
|
||||
color: var(--color-error); }
|
||||
.watch-table td {
|
||||
white-space: nowrap; }
|
||||
.watch-table td.title-col {
|
||||
@@ -480,9 +470,6 @@ a.pure-button-selected {
|
||||
.notifications-wrapper {
|
||||
padding: 0.5rem 0 1rem 0; }
|
||||
|
||||
label:hover {
|
||||
cursor: pointer; }
|
||||
|
||||
#notification-customisation {
|
||||
border: 1px solid var(--color-border-notification);
|
||||
padding: 0.5rem;
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
{% if dark_mode %}
|
||||
{% set darkClass = 'dark' %}
|
||||
{% endif %}
|
||||
<button class="toggle-theme {{darkClass}}" type="button" title="Toggle Light/Dark Mode">
|
||||
<button class="toggle-theme {{darkClass}}" type="button">
|
||||
<span class="visually-hidden">Toggle light/dark mode</span>
|
||||
<span class="icon-light">
|
||||
{% include "svgs/light-mode-toggle-icon.svg" %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
const screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
||||
@@ -58,7 +58,6 @@
|
||||
{% if last_error_screenshot %}<li class="tab" id="error-screenshot-tab"><a href="#error-screenshot">Error Screenshot</a></li> {% endif %}
|
||||
<li class="tab" id=""><a href="#text">Text</a></li>
|
||||
<li class="tab" id="screenshot-tab"><a href="#screenshot">Screenshot</a></li>
|
||||
<li class="tab" id="extract-tab"><a href="#extract">Extract Data</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -109,37 +108,6 @@
|
||||
<strong>Screenshot requires Playwright/WebDriver enabled</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane-inner" id="extract">
|
||||
<form id="extract-data-form" class="pure-form pure-form-stacked edit-form"
|
||||
action="{{ url_for('diff_history_page', uuid=uuid) }}#extract"
|
||||
method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<p>This tool will extract text data from all of the watch history.</p>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(extract_form.extract_regex) }}
|
||||
<span class="pure-form-message-inline">
|
||||
A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract.<br/>
|
||||
|
||||
<p>
|
||||
For example, to extract only the numbers from text ‐</br>
|
||||
<strong>Raw text</strong>: <code>Temperature <span style="color: red">5.5</span>°C in Sydney</code></br>
|
||||
<strong>RegEx to extract:</strong> <code>Temperature ([0-9\.]+)</code><br/>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://RegExr.com/">Be sure to test your RegEx here.</a>
|
||||
</p>
|
||||
<p>
|
||||
Each RegEx group bracket <code>()</code> will be in its own column, the first column value is always the date.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_button(extract_form.extract_submit_button) }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,72 +1,130 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||
{% extends 'base.html' %} {% block content %} {% from '_helpers.jinja' import
|
||||
render_field, render_checkbox_field, render_button %} {% from
|
||||
'_common_fields.jinja' import render_common_settings_form %}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='tabs.js')}}"
|
||||
defer
|
||||
></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 %}
|
||||
{% if emailprefix %}
|
||||
const email_notification_prefix=JSON.parse('{{ emailprefix|tojson }}');
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
const browser_steps_config=JSON.parse('{{ browser_steps_config|tojson }}');
|
||||
const browser_steps_sync_url="{{url_for('browser_steps.browsersteps_ui_update', uuid=uuid)}}";
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='watch-settings.js')}}" defer></script>
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='limit.js')}}" defer></script>
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='visual-selector.js')}}" defer></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='watch-settings.js')}}"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='limit.js')}}"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='notifications.js')}}"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='visual-selector.js')}}"
|
||||
defer
|
||||
></script>
|
||||
{% if playwright_enabled %}
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='browser-steps.js')}}" defer></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='browser-steps.js')}}"
|
||||
defer
|
||||
></script>
|
||||
{% endif %}
|
||||
|
||||
<div class="edit-form monospaced-textarea">
|
||||
|
||||
<div class="tabs collapsable">
|
||||
<ul>
|
||||
<li class="tab" id=""><a href="#general">General</a></li>
|
||||
<li class="tab"><a href="#request">Request</a></li>
|
||||
{% if playwright_enabled %}
|
||||
<li class="tab"><a id="browsersteps-tab" href="#browser-steps">Browser Steps</a></li>
|
||||
<li class="tab">
|
||||
<a id="browsersteps-tab" href="#browser-steps">Browser Steps</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="tab"><a id="visualselector-tab" href="#visualselector">Visual Filter Selector</a></li>
|
||||
<li class="tab"><a href="#filters-and-triggers">Filters & Triggers</a></li>
|
||||
<li class="tab">
|
||||
<a id="visualselector-tab" href="#visualselector"
|
||||
>Visual Filter Selector</a
|
||||
>
|
||||
</li>
|
||||
<li class="tab">
|
||||
<a href="#filters-and-triggers">Filters & Triggers</a>
|
||||
</li>
|
||||
<li class="tab"><a href="#notifications">Notifications</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="box-wrap inner">
|
||||
<form class="pure-form pure-form-stacked"
|
||||
action="{{ url_for('edit_page', uuid=uuid, next = request.args.get('next'), unpause_on_save = request.args.get('unpause_on_save')) }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<form
|
||||
class="pure-form pure-form-stacked"
|
||||
action="{{ url_for('edit_page', uuid=uuid, next = request.args.get('next'), unpause_on_save = request.args.get('unpause_on_save')) }}"
|
||||
method="POST"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
<div class="tab-pane-inner" id="general">
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.url, placeholder="https://...", required=true, class="m-d") }}
|
||||
<span class="pure-form-message-inline">Some sites use JavaScript to create the content, for this you should <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver">use the Chrome/WebDriver Fetcher</a></span><br/>
|
||||
<span class="pure-form-message-inline">You can use variables in the URL, perfect for inserting the current date and other logic, <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Handling-variables-in-the-watched-URL">help and examples here</a></span><br/>
|
||||
{{ render_field(form.url, placeholder="https://...", required=true,
|
||||
class="m-d") }}
|
||||
<span class="pure-form-message-inline"
|
||||
>Some sites use JavaScript to create the content, for this you
|
||||
should
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver"
|
||||
>use the Chrome/WebDriver Fetcher</a
|
||||
></span
|
||||
><br />
|
||||
<span class="pure-form-message-inline"
|
||||
>You can use variables in the URL, perfect for inserting the
|
||||
current date and other logic,
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Handling-variables-in-the-watched-URL"
|
||||
>help and examples here</a
|
||||
></span
|
||||
><br />
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.title, class="m-d") }}
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.tag) }}
|
||||
<span class="pure-form-message-inline">Organisational tag/group name used in the main listing page</span>
|
||||
<span class="pure-form-message-inline"
|
||||
>Organisational tag/group name used in the main listing page</span
|
||||
>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.time_between_check, class="time-check-widget") }}
|
||||
{% if has_empty_checktime %}
|
||||
<span class="pure-form-message-inline">Currently using the <a
|
||||
href="{{ url_for('settings_page', uuid=uuid) }}">default global settings</a>, change to another value if you want to be specific.</span>
|
||||
{{ render_field(form.time_between_check, class="time-check-widget")
|
||||
}} {% if has_empty_checktime %}
|
||||
<span class="pure-form-message-inline"
|
||||
>Currently using the
|
||||
<a href="{{ url_for('settings_page', uuid=uuid) }}"
|
||||
>default global settings</a
|
||||
>, change to another value if you want to be specific.</span
|
||||
>
|
||||
{% else %}
|
||||
<span class="pure-form-message-inline">Set to blank to use the <a
|
||||
href="{{ url_for('settings_page', uuid=uuid) }}">default global settings</a>.</span>
|
||||
<span class="pure-form-message-inline"
|
||||
>Set to blank to use the
|
||||
<a href="{{ url_for('settings_page', uuid=uuid) }}"
|
||||
>default global settings</a
|
||||
>.</span
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
@@ -75,7 +133,9 @@
|
||||
<div class="pure-control-group">
|
||||
{{ render_checkbox_field(form.filter_failure_notification_send) }}
|
||||
<span class="pure-form-message-inline">
|
||||
Sends a notification when the filter can no longer be seen on the page, good for knowing when the page changed and your filter will not work anymore.
|
||||
Sends a notification when the filter can no longer be seen on the
|
||||
page, good for knowing when the page changed and your filter will
|
||||
not work anymore.
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -85,9 +145,20 @@
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_field(form.fetch_backend, class="fetch-backend") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<p>Use the <strong>Basic</strong> method (default) where your watched site doesn't need Javascript to render.</p>
|
||||
<p>The <strong>Chrome/Javascript</strong> method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'. </p>
|
||||
Tip: <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration#brightdata-proxy-support">Connect using BrightData Proxies, find out more here.</a>
|
||||
<p>
|
||||
Use the <strong>Basic</strong> method (default) where your watched
|
||||
site doesn't need Javascript to render.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>Chrome/Javascript</strong> method requires a network
|
||||
connection to a running WebDriver+Chrome server, set by the ENV
|
||||
var 'WEBDRIVER_URL'.
|
||||
</p>
|
||||
Tip:
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration#brightdata-proxy-support"
|
||||
>Connect using BrightData Proxies, find out more here.</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
{% if form.proxy %}
|
||||
@@ -105,48 +176,59 @@
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.webdriver_delay) }}
|
||||
<div class="pure-form-message-inline">
|
||||
<strong>If you're having trouble waiting for the page to be fully rendered (text missing etc), try increasing the 'wait' time here.</strong>
|
||||
<br/>
|
||||
This will wait <i>n</i> seconds before extracting the text.
|
||||
{% if using_global_webdriver_wait %}
|
||||
<br/><strong>Using the current global default settings</strong>
|
||||
<strong
|
||||
>If you're having trouble waiting for the page to be fully
|
||||
rendered (text missing etc), try increasing the 'wait' time
|
||||
here.</strong
|
||||
>
|
||||
<br />
|
||||
This will wait <i>n</i> seconds before extracting the text. {% if
|
||||
using_global_webdriver_wait %} <br /><strong
|
||||
>Using the current global default settings</strong
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.webdriver_js_execute_code) }}
|
||||
<div class="pure-form-message-inline">
|
||||
Run this code before performing change detection, handy for filling in fields and other actions <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Run-JavaScript-before-change-detection">More help and examples here</a>
|
||||
Run this code before performing change detection, handy for
|
||||
filling in fields and other actions
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Run-JavaScript-before-change-detection"
|
||||
>More help and examples here</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="pure-group" id="requests-override-options">
|
||||
{% if not playwright_enabled %}
|
||||
<div class="pure-form-message-inline">
|
||||
<strong>Request override is currently only used by the <i>Basic fast Plaintext/HTTP Client</i> method.</strong>
|
||||
<strong
|
||||
>Request override is currently only used by the
|
||||
<i>Basic fast Plaintext/HTTP Client</i> method.</strong
|
||||
>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="pure-control-group" id="request-method">
|
||||
{{ render_field(form.method) }}
|
||||
</div>
|
||||
<div class="pure-control-group" id="request-headers">
|
||||
{{ render_field(form.headers, rows=5, placeholder="Example
|
||||
Cookie: foobar
|
||||
User-Agent: wonderbra 1.0") }}
|
||||
{{ render_field(form.headers, rows=5, placeholder="Example
|
||||
Cookie: foobar User-Agent: wonderbra 1.0") }}
|
||||
</div>
|
||||
<div class="pure-control-group" id="request-body">
|
||||
{{ render_field(form.body, rows=5, placeholder="Example
|
||||
{
|
||||
\"name\":\"John\",
|
||||
\"age\":30,
|
||||
\"car\":null
|
||||
}") }}
|
||||
{\"name\":\"John\", \"age\":30, \"car\":null }") }}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% if playwright_enabled %}
|
||||
<div class="tab-pane-inner" id="browser-steps">
|
||||
<img class="beta-logo" src="{{url_for('static_content', group='images', filename='beta-logo.png')}}">
|
||||
<img
|
||||
class="beta-logo"
|
||||
src="{{url_for('static_content', group='images', filename='beta-logo.png')}}"
|
||||
/>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
<!--
|
||||
@@ -156,29 +238,58 @@ User-Agent: wonderbra 1.0") }}
|
||||
-->
|
||||
|
||||
<!--- Do this later -->
|
||||
<div class="checkbox" style="display: none;">
|
||||
<input type=checkbox id="include_text_elements" > <label for="include_text_elements">Turn on text finder</label>
|
||||
<div class="checkbox" style="display: none">
|
||||
<input type="checkbox" id="include_text_elements" />
|
||||
<label for="include_text_elements">Turn on text finder</label>
|
||||
</div>
|
||||
|
||||
<div id="loading-status-text" style="display: none;">Please wait, first browser step can take a little time to load..<div class="spinner"></div></div>
|
||||
<div class="flex-wrapper" >
|
||||
|
||||
<div id="browser-steps-ui" class="noselect" style="width: 100%; background-color: #eee; border-radius: 5px;">
|
||||
|
||||
<div class="noselect" id="browsersteps-selector-wrapper" style="width: 100%">
|
||||
<span class="loader" >
|
||||
<div id="loading-status-text" style="display: none">
|
||||
Please wait, first browser step can take a little time to load..
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<div class="flex-wrapper">
|
||||
<div
|
||||
id="browser-steps-ui"
|
||||
class="noselect"
|
||||
style="width: 100%; background-color: #eee; border-radius: 5px"
|
||||
>
|
||||
<div
|
||||
class="noselect"
|
||||
id="browsersteps-selector-wrapper"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span class="loader">
|
||||
<span id="browsersteps-click-start">
|
||||
<h2 >Click here to Start</h2>
|
||||
<h2>Click here to Start</h2>
|
||||
Please allow 10-15 seconds for the browser to connect.
|
||||
</span>
|
||||
<div class="spinner" style="display: none;"></div>
|
||||
<div class="spinner" style="display: none"></div>
|
||||
</span>
|
||||
<img class="noselect" id="browsersteps-img" src="" style="max-width: 100%; width: 100%;" />
|
||||
<canvas class="noselect" id="browsersteps-selector-canvas" style="max-width: 100%; width: 100%;"></canvas>
|
||||
<img
|
||||
class="noselect"
|
||||
id="browsersteps-img"
|
||||
src=""
|
||||
style="max-width: 100%; width: 100%"
|
||||
/>
|
||||
<canvas
|
||||
class="noselect"
|
||||
id="browsersteps-selector-canvas"
|
||||
style="max-width: 100%; width: 100%"
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div id="browser-steps-fieldlist" style="padding-left: 1em; width: 350px; font-size: 80%;" >
|
||||
<span id="browserless-seconds-remaining">Loading</span> <span style="font-size: 80%;"> (<a target=_new href="https://github.com/dgtlmoon/changedetection.io/pull/478/files#diff-1a79d924d1840c485238e66772391268a89c95b781d69091384cf1ea1ac146c9R4">?</a>) </span>
|
||||
<div
|
||||
id="browser-steps-fieldlist"
|
||||
style="padding-left: 1em; width: 350px; font-size: 80%"
|
||||
>
|
||||
<span id="browserless-seconds-remaining">Loading</span>
|
||||
<span style="font-size: 80%">
|
||||
(<a
|
||||
target="_new"
|
||||
href="https://github.com/dgtlmoon/changedetection.io/pull/478/files#diff-1a79d924d1840c485238e66772391268a89c95b781d69091384cf1ea1ac146c9R4"
|
||||
>?</a
|
||||
>)
|
||||
</span>
|
||||
{{ render_field(form.browser_steps) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,147 +307,278 @@ User-Agent: wonderbra 1.0") }}
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_checkbox_field(form.notification_screenshot) }}
|
||||
<span class="pure-form-message-inline">
|
||||
<strong>Use with caution!</strong> This will easily fill up your email storage quota or flood other storages.
|
||||
<strong>Use with caution!</strong> This will easily fill up your
|
||||
email storage quota or flood other storages.
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="field-group" id="notification-field-group">
|
||||
{% if has_default_notification_urls %}
|
||||
<div class="inline-warning">
|
||||
<img class="inline-warning-icon" src="{{url_for('static_content', group='images', filename='notice.svg')}}" alt="Look out!" title="Lookout!"/>
|
||||
There are <a href="{{ url_for('settings_page')}}#notifications">system-wide notification URLs enabled</a>, this form will override notification settings for this watch only ‐ an empty Notification URL list here will still send notifications.
|
||||
<img
|
||||
class="inline-warning-icon"
|
||||
src="{{url_for('static_content', group='images', filename='notice.svg')}}"
|
||||
alt="Look out!"
|
||||
title="Lookout!"
|
||||
/>
|
||||
There are
|
||||
<a href="{{ url_for('settings_page')}}#notifications"
|
||||
>system-wide notification URLs enabled</a
|
||||
>, this form will override notification settings for this watch
|
||||
only ‐ an empty Notification URL list here will still send
|
||||
notifications.
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="#notifications" id="notification-setting-reset-to-default" class="pure-button button-xsmall" style="right: 20px; top: 20px; position: absolute; background-color: #5f42dd; border-radius: 4px; font-size: 70%; color: #fff">Use system defaults</a>
|
||||
<a
|
||||
href="#notifications"
|
||||
id="notification-setting-reset-to-default"
|
||||
class="pure-button button-xsmall"
|
||||
style="
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
position: absolute;
|
||||
background-color: #5f42dd;
|
||||
border-radius: 4px;
|
||||
font-size: 70%;
|
||||
color: #fff;
|
||||
"
|
||||
>Use system defaults</a
|
||||
>
|
||||
|
||||
{{ render_common_settings_form(form, emailprefix, settings_application) }}
|
||||
{{ render_common_settings_form(form, emailprefix,
|
||||
settings_application) }}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane-inner" id="filters-and-triggers">
|
||||
<div class="pure-control-group">
|
||||
<strong>Pro-tips:</strong><br/>
|
||||
<strong>Pro-tips:</strong><br />
|
||||
<ul>
|
||||
<li>
|
||||
Use the preview page to see your filters and triggers highlighted.
|
||||
</li>
|
||||
<li>
|
||||
Some sites use JavaScript to create the content, for this you should <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver">use the Chrome/WebDriver Fetcher</a>
|
||||
Some sites use JavaScript to create the content, for this you
|
||||
should
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver"
|
||||
>use the Chrome/WebDriver Fetcher</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_checkbox_field(form.check_unique_lines) }}
|
||||
<span class="pure-form-message-inline">Good for websites that just move the content around, and you want to know when NEW content is added, compares new lines against all history for this watch.</span>
|
||||
<span class="pure-form-message-inline"
|
||||
>Good for websites that just move the content around, and you want
|
||||
to know when NEW content is added, compares new lines against all
|
||||
history for this watch.</span
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pure-control-group">
|
||||
{% set field = render_field(form.include_filters,
|
||||
rows=5,
|
||||
{% set field = render_field(form.include_filters, rows=5,
|
||||
placeholder="#example
|
||||
xpath://body/div/span[contains(@class, 'example-class')]",
|
||||
class="m-d")
|
||||
%}
|
||||
{{ field }}
|
||||
{% if '/text()' in field %}
|
||||
<span class="pure-form-message-inline"><strong>Note!: //text() function does not work where the <element> contains <![CDATA[]]></strong></span><br/>
|
||||
xpath://body/div/span[contains(@class, 'example-class')]", class="m-d") %} {{ field }} {% if '/text()' in
|
||||
field %}
|
||||
<span class="pure-form-message-inline"
|
||||
><strong
|
||||
>Note!: //text() function does not work where the <element>
|
||||
contains <![CDATA[]]></strong
|
||||
></span
|
||||
><br />
|
||||
{% endif %}
|
||||
<span class="pure-form-message-inline">One rule per line, <i>any</i> rules that matches will be used.<br/>
|
||||
|
||||
<span class="pure-form-message-inline"
|
||||
>One rule per line, <i>any</i> rules that matches will be used.<br />
|
||||
<ul>
|
||||
<li>CSS - Limit text to this CSS rule, only text matching this CSS rule is included.</li>
|
||||
<li>JSON - Limit text to this JSON rule, using either <a href="https://pypi.org/project/jsonpath-ng/" target="new">JSONPath</a> or <a href="https://stedolan.github.io/jq/" target="new">jq</a> (if installed).
|
||||
<li>
|
||||
CSS - Limit text to this CSS rule, only text matching this CSS
|
||||
rule is included.
|
||||
</li>
|
||||
<li>
|
||||
JSON - Limit text to this JSON rule, using either
|
||||
<a href="https://pypi.org/project/jsonpath-ng/" target="new"
|
||||
>JSONPath</a
|
||||
>
|
||||
or
|
||||
<a href="https://stedolan.github.io/jq/" target="new">jq</a> (if
|
||||
installed).
|
||||
<ul>
|
||||
<li>JSONPath: Prefix with <code>json:</code>, use <code>json:$</code> to force re-formatting if required, <a href="https://jsonpath.com/" target="new">test your JSONPath here</a>.</li>
|
||||
<li>
|
||||
JSONPath: Prefix with <code>json:</code>, use
|
||||
<code>json:$</code> to force re-formatting if required,
|
||||
<a href="https://jsonpath.com/" target="new"
|
||||
>test your JSONPath here</a
|
||||
>.
|
||||
</li>
|
||||
{% if jq_support %}
|
||||
<li>jq: Prefix with <code>jq:</code> and <a href="https://jqplay.org/" target="new">test your jq here</a>. Using <a href="https://stedolan.github.io/jq/" target="new">jq</a> allows for complex filtering and processing of JSON data with built-in functions, regex, filtering, and more. See examples and documentation <a href="https://stedolan.github.io/jq/manual/" target="new">here</a>.</li>
|
||||
<li>
|
||||
jq: Prefix with <code>jq:</code> and
|
||||
<a href="https://jqplay.org/" target="new"
|
||||
>test your jq here</a
|
||||
>. Using
|
||||
<a href="https://stedolan.github.io/jq/" target="new">jq</a>
|
||||
allows for complex filtering and processing of JSON data
|
||||
with built-in functions, regex, filtering, and more. See
|
||||
examples and documentation
|
||||
<a href="https://stedolan.github.io/jq/manual/" target="new"
|
||||
>here</a
|
||||
>.
|
||||
</li>
|
||||
{% else %}
|
||||
<li>jq support not installed</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li>XPath - Limit text to this XPath rule, simply start with a forward-slash,
|
||||
<li>
|
||||
XPath - Limit text to this XPath rule, simply start with a
|
||||
forward-slash,
|
||||
<ul>
|
||||
<li>Example: <code>//*[contains(@class, 'sametext')]</code> or <code>xpath://*[contains(@class, 'sametext')]</code>, <a
|
||||
href="http://xpather.com/" target="new">test your XPath here</a></li>
|
||||
<li>Example: Get all titles from an RSS feed <code>//title/text()</code></li>
|
||||
<li>
|
||||
Example: <code>//*[contains(@class, 'sametext')]</code> or
|
||||
<code>xpath://*[contains(@class, 'sametext')]</code>,
|
||||
<a href="http://xpather.com/" target="new"
|
||||
>test your XPath here</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
Example: Get all titles from an RSS feed
|
||||
<code>//title/text()</code>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
Please be sure that you thoroughly understand how to write CSS, JSONPath, XPath{% if jq_support %}, or jq selector{%endif%} rules before filing an issue on GitHub! <a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/CSS-Selector-help">here for more CSS selector help</a>.<br/>
|
||||
Please be sure that you thoroughly understand how to write CSS,
|
||||
JSONPath, XPath{% if jq_support %}, or jq selector{%endif%} rules
|
||||
before filing an issue on GitHub!
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/CSS-Selector-help"
|
||||
>here for more CSS selector help</a
|
||||
>.<br />
|
||||
</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.subtractive_selectors, rows=5, placeholder="header
|
||||
footer
|
||||
nav
|
||||
.stockticker") }}
|
||||
{{ render_field(form.subtractive_selectors, rows=5,
|
||||
placeholder="header footer nav .stockticker") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li> Remove HTML element(s) by CSS selector before text conversion. </li>
|
||||
<li> Add multiple elements or CSS selectors per line to ignore multiple parts of the HTML. </li>
|
||||
<li>
|
||||
Remove HTML element(s) by CSS selector before text conversion.
|
||||
</li>
|
||||
<li>
|
||||
Add multiple elements or CSS selectors per line to ignore
|
||||
multiple parts of the HTML.
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
<fieldset class="pure-group">
|
||||
{{ render_field(form.ignore_text, rows=5, placeholder="Some text to ignore in a line
|
||||
/some.regex\d{2}/ for case-INsensitive regex
|
||||
") }}
|
||||
{{ render_field(form.ignore_text, rows=5, placeholder="Some text to ignore in a line /some.regex\d{2}/ for case-INsensitive regex ") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
||||
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
||||
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
||||
<li>
|
||||
Each line processed separately, any line matching will be
|
||||
ignored (removed before creating the checksum)
|
||||
</li>
|
||||
<li>
|
||||
Regular Expression support, wrap the entire line in forward
|
||||
slash <code>/regex/</code>
|
||||
</li>
|
||||
<li>
|
||||
Changing this will affect the comparison checksum which may
|
||||
trigger an alert
|
||||
</li>
|
||||
<li>Use the preview/show current tab to see ignores</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.trigger_text, rows=5, placeholder="Some text to wait for in a line
|
||||
/some.regex\d{2}/ for case-INsensitive regex
|
||||
") }}
|
||||
{{ render_field(form.trigger_text, rows=5, placeholder="Some text to wait for in a line /some.regex\d{2}/ for case-INsensitive regex ")
|
||||
}}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>Text to wait for before triggering a change/notification, all text and regex are tested <i>case-insensitive</i>.</li>
|
||||
<li>Trigger text is processed from the result-text that comes out of any CSS/JSON Filters for this watch</li>
|
||||
<li>Each line is processed separately (think of each line as "OR")</li>
|
||||
<li>Note: Wrap in forward slash / to use regex example: <code>/foo\d/</code></li>
|
||||
<li>
|
||||
Text to wait for before triggering a change/notification, all
|
||||
text and regex are tested <i>case-insensitive</i>.
|
||||
</li>
|
||||
<li>
|
||||
Trigger text is processed from the result-text that comes out
|
||||
of any CSS/JSON Filters for this watch
|
||||
</li>
|
||||
<li>
|
||||
Each line is processed separately (think of each line as "OR")
|
||||
</li>
|
||||
<li>
|
||||
Note: Wrap in forward slash / to use regex example:
|
||||
<code>/foo\d/</code>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.text_should_not_be_present, rows=5, placeholder="For example: Out of stock
|
||||
{{ render_field(form.text_should_not_be_present, rows=5,
|
||||
placeholder="For example:
|
||||
|
||||
Out of stock
|
||||
Sold out
|
||||
Not in stock
|
||||
Unavailable") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>Block change-detection while this text is on the page, all text and regex are tested <i>case-insensitive</i>, good for waiting for when a product is available again</li>
|
||||
<li>Block text is processed from the result-text that comes out of any CSS/JSON Filters for this watch</li>
|
||||
<li>All lines here must not exist (think of each line as "OR")</li>
|
||||
<li>Note: Wrap in forward slash / to use regex example: <code>/foo\d/</code></li>
|
||||
<li>
|
||||
Block change-detection while this text is on the page, all
|
||||
text and regex are tested <i>case-insensitive</i>, good for
|
||||
waiting for when a product is available again
|
||||
</li>
|
||||
<li>
|
||||
Block text is processed from the result-text that comes out of
|
||||
any CSS/JSON Filters for this watch
|
||||
</li>
|
||||
<li>
|
||||
All lines here must not exist (think of each line as "OR")
|
||||
</li>
|
||||
<li>
|
||||
Note: Wrap in forward slash / to use regex example:
|
||||
<code>/foo\d/</code>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.extract_text, rows=5, placeholder="\d+ online") }}
|
||||
{{ render_field(form.extract_text, rows=5, placeholder="\d+ online")
|
||||
}}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>Extracts text in the final output (line by line) after other filters using regular expressions;
|
||||
<li>
|
||||
Extracts text in the final output (line by line) after other
|
||||
filters using regular expressions;
|
||||
<ul>
|
||||
<li>Regular expression ‐ example <code>/reports.+?2022/i</code></li>
|
||||
<li>Use <code>//(?aiLmsux))</code> type flags (more <a href="https://docs.python.org/3/library/re.html#index-15">information here</a>)<br/></li>
|
||||
<li>Keyword example ‐ example <code>Out of stock</code></li>
|
||||
<li>Use groups to extract just that text ‐ example <code>/reports.+?(\d+)/i</code> returns a list of years only</li>
|
||||
<li>
|
||||
Regular expression ‐ example
|
||||
<code>/reports.+?2022/i</code>
|
||||
</li>
|
||||
<li>
|
||||
Use <code>//(?aiLmsux))</code> type flags (more
|
||||
<a
|
||||
href="https://docs.python.org/3/library/re.html#index-15"
|
||||
>information here</a
|
||||
>)<br />
|
||||
</li>
|
||||
<li>
|
||||
Keyword example ‐ example <code>Out of stock</code>
|
||||
</li>
|
||||
<li>
|
||||
Use groups to extract just that text ‐ example
|
||||
<code>/reports.+?(\d+)/i</code> returns a list of years
|
||||
only
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>One line per regular-expression/ string match</li>
|
||||
@@ -347,18 +589,30 @@ Unavailable") }}
|
||||
</div>
|
||||
|
||||
<div class="tab-pane-inner visual-selector-ui" id="visualselector">
|
||||
<img class="beta-logo" src="{{url_for('static_content', group='images', filename='beta-logo.png')}}">
|
||||
<img
|
||||
class="beta-logo"
|
||||
src="{{url_for('static_content', group='images', filename='beta-logo.png')}}"
|
||||
/>
|
||||
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{% if visualselector_enabled %}
|
||||
<span class="pure-form-message-inline">
|
||||
The Visual Selector tool lets you select the <i>text</i> elements that will be used for the change detection ‐ after the <i>Browser Steps</i> has completed.<br/><br/>
|
||||
The Visual Selector tool lets you select the <i>text</i> elements
|
||||
that will be used for the change detection ‐ after the
|
||||
<i>Browser Steps</i> has completed.<br /><br />
|
||||
</span>
|
||||
|
||||
<div id="selector-header">
|
||||
<a id="clear-selector" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Clear selection</a>
|
||||
<i class="fetching-update-notice" style="font-size: 80%;">One moment, fetching screenshot and element information..</i>
|
||||
<a
|
||||
id="clear-selector"
|
||||
class="pure-button button-secondary button-xsmall"
|
||||
style="font-size: 70%"
|
||||
>Clear selection</a
|
||||
>
|
||||
<i class="fetching-update-notice" style="font-size: 80%"
|
||||
>One moment, fetching screenshot and element information..</i
|
||||
>
|
||||
</div>
|
||||
<div id="selector-wrapper" style="display: none">
|
||||
<!-- request the screenshot and get the element offset info ready -->
|
||||
@@ -367,12 +621,27 @@ Unavailable") }}
|
||||
<img id="selector-background" />
|
||||
<canvas id="selector-canvas"></canvas>
|
||||
</div>
|
||||
<div id="selector-current-xpath" style="overflow-x: hidden"><strong>Currently:</strong> <span class="text">Loading...</span></div>
|
||||
<div id="selector-current-xpath" style="overflow-x: hidden">
|
||||
<strong>Currently:</strong> <span class="text"
|
||||
>Loading...</span
|
||||
>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="pure-form-message-inline">
|
||||
<p>Sorry, this functionality only works with Playwright/Chrome enabled watches.</p>
|
||||
<p>Enable the Playwright Chrome fetcher, or alternatively try our <a href="https://lemonade.changedetection.io/start">very affordable subscription based service</a>.</p>
|
||||
<p>This is because Selenium/WebDriver can not extract full page screenshots reliably.</p>
|
||||
<p>
|
||||
Sorry, this functionality only works with Playwright/Chrome
|
||||
enabled watches.
|
||||
</p>
|
||||
<p>
|
||||
Enable the Playwright Chrome fetcher, or alternatively try our
|
||||
<a href="https://lemonade.changedetection.io/start"
|
||||
>very affordable subscription based service</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
This is because Selenium/WebDriver can not extract full page
|
||||
screenshots reliably.
|
||||
</p>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -382,12 +651,19 @@ Unavailable") }}
|
||||
<div id="actions">
|
||||
<div class="pure-control-group">
|
||||
{{ render_button(form.save_button) }}
|
||||
<a href="{{url_for('form_delete', uuid=uuid)}}"
|
||||
class="pure-button button-small button-error ">Delete</a>
|
||||
<a href="{{url_for('clear_watch_history', uuid=uuid)}}"
|
||||
class="pure-button button-small button-error ">Clear History</a>
|
||||
<a href="{{url_for('form_clone', uuid=uuid)}}"
|
||||
class="pure-button button-small ">Create Copy</a>
|
||||
<a
|
||||
href="{{url_for('form_delete', uuid=uuid)}}"
|
||||
class="pure-button button-warning"
|
||||
>Delete</a
|
||||
>
|
||||
<a
|
||||
href="{{url_for('clear_watch_history', uuid=uuid)}}"
|
||||
class="pure-button button-warning"
|
||||
>Clear History</a
|
||||
>
|
||||
<a href="{{url_for('form_clone', uuid=uuid)}}" class="pure-button"
|
||||
>Create Copy</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||
{% extends 'base.html' %} {% block content %} {% from '_helpers.jinja' import
|
||||
render_field, render_checkbox_field, render_button %} {% from
|
||||
'_common_fields.jinja' import render_common_settings_form %}
|
||||
<script>
|
||||
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
|
||||
{% if emailprefix %}
|
||||
{% if emailprefix %}
|
||||
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</script>
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='tabs.js')}}"
|
||||
defer
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='notifications.js')}}"
|
||||
defer
|
||||
></script>
|
||||
|
||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='global-settings.js')}}" defer></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{url_for('static_content', group='js', filename='global-settings.js')}}"
|
||||
defer
|
||||
></script>
|
||||
<div class="edit-form">
|
||||
<div class="tabs collapsable">
|
||||
<ul>
|
||||
@@ -24,58 +34,91 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="box-wrap inner">
|
||||
<form class="pure-form pure-form-stacked settings" action="{{url_for('settings_page')}}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<form
|
||||
class="pure-form pure-form-stacked settings"
|
||||
action="{{url_for('settings_page')}}"
|
||||
method="POST"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="tab-pane-inner" id="general">
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.requests.form.time_between_check, class="time-check-widget") }}
|
||||
<span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span>
|
||||
{{ render_field(form.requests.form.time_between_check,
|
||||
class="time-check-widget") }}
|
||||
<span class="pure-form-message-inline"
|
||||
>Default time for all watches, when the watch does not have a
|
||||
specific time setting.</span
|
||||
>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.requests.form.jitter_seconds, class="jitter_seconds") }}
|
||||
<span class="pure-form-message-inline">Example - 3 seconds random jitter could trigger up to 3 seconds earlier or up to 3 seconds later</span>
|
||||
{{ render_field(form.requests.form.jitter_seconds,
|
||||
class="jitter_seconds") }}
|
||||
<span class="pure-form-message-inline"
|
||||
>Example - 3 seconds random jitter could trigger up to 3 seconds
|
||||
earlier or up to 3 seconds later</span
|
||||
>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.application.form.filter_failure_notification_threshold_attempts, class="filter_failure_notification_threshold_attempts") }}
|
||||
<span class="pure-form-message-inline">After this many consecutive times that the CSS/xPath filter is missing, send a notification
|
||||
<br/>
|
||||
{{
|
||||
render_field(form.application.form.filter_failure_notification_threshold_attempts,
|
||||
class="filter_failure_notification_threshold_attempts") }}
|
||||
<span class="pure-form-message-inline"
|
||||
>After this many consecutive times that the CSS/xPath filter is
|
||||
missing, send a notification
|
||||
<br />
|
||||
Set to <strong>0</strong> to disable
|
||||
</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{% if not hide_remove_pass %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ render_button(form.application.form.removepassword_button) }}
|
||||
{% else %}
|
||||
{{ render_field(form.application.form.password) }}
|
||||
<span class="pure-form-message-inline">Password protection for your changedetection.io application.</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if not hide_remove_pass %} {% if current_user.is_authenticated %}
|
||||
{{ render_button(form.application.form.removepassword_button) }} {%
|
||||
else %} {{ render_field(form.application.form.password) }}
|
||||
<span class="pure-form-message-inline"
|
||||
>Password protection for your changedetection.io
|
||||
application.</span
|
||||
>
|
||||
{% endif %} {% else %}
|
||||
<span class="pure-form-message-inline">Password is locked.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.application.form.base_url, placeholder="http://yoursite.com:5000/",
|
||||
class="m-d") }}
|
||||
{{ render_field(form.application.form.base_url,
|
||||
placeholder="http://yoursite.com:5000/", class="m-d") }}
|
||||
<span class="pure-form-message-inline">
|
||||
Base URL used for the <code>{base_url}</code> token in notifications and RSS links.<br/>Default value is the ENV var 'BASE_URL' (Currently "{{settings_application['current_base_url']}}"),
|
||||
<a href="https://github.com/dgtlmoon/changedetection.io/wiki/Configurable-BASE_URL-setting">read more here</a>.
|
||||
Base URL used for the <code>{base_url}</code> token in
|
||||
notifications and RSS links.<br />Default value is the ENV var
|
||||
'BASE_URL' (Currently
|
||||
"{{settings_application['current_base_url']}}"),
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Configurable-BASE_URL-setting"
|
||||
>read more here</a
|
||||
>.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_checkbox_field(form.application.form.extract_title_as_title) }}
|
||||
<span class="pure-form-message-inline">Note: This will automatically apply to all existing watches.</span>
|
||||
{{
|
||||
render_checkbox_field(form.application.form.extract_title_as_title)
|
||||
}}
|
||||
<span class="pure-form-message-inline"
|
||||
>Note: This will automatically apply to all existing
|
||||
watches.</span
|
||||
>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_checkbox_field(form.application.form.empty_pages_are_a_change) }}
|
||||
<span class="pure-form-message-inline">When a page contains HTML, but no renderable text appears (empty page), is this considered a change?</span>
|
||||
{{
|
||||
render_checkbox_field(form.application.form.empty_pages_are_a_change)
|
||||
}}
|
||||
<span class="pure-form-message-inline"
|
||||
>When a page contains HTML, but no renderable text appears (empty
|
||||
page), is this considered a change?</span
|
||||
>
|
||||
</div>
|
||||
{% if form.requests.proxy %}
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_field(form.requests.form.proxy, class="fetch-backend-proxy") }}
|
||||
{{ render_field(form.requests.form.proxy,
|
||||
class="fetch-backend-proxy") }}
|
||||
<span class="pure-form-message-inline">
|
||||
Choose a default proxy for all watches
|
||||
</span>
|
||||
@@ -87,25 +130,42 @@
|
||||
<div class="tab-pane-inner" id="notifications">
|
||||
<fieldset>
|
||||
<div class="field-group">
|
||||
{{ render_common_settings_form(form.application.form, emailprefix, settings_application) }}
|
||||
{{ render_common_settings_form(form.application.form, emailprefix,
|
||||
settings_application) }}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane-inner" id="fetching">
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_field(form.application.form.fetch_backend, class="fetch-backend") }}
|
||||
{{ render_field(form.application.form.fetch_backend,
|
||||
class="fetch-backend") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<p>Use the <strong>Basic</strong> method (default) where your watched sites don't need Javascript to render.</p>
|
||||
<p>The <strong>Chrome/Javascript</strong> method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'. </p>
|
||||
<p>
|
||||
Use the <strong>Basic</strong> method (default) where your watched
|
||||
sites don't need Javascript to render.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>Chrome/Javascript</strong> method requires a network
|
||||
connection to a running WebDriver+Chrome server, set by the ENV
|
||||
var 'WEBDRIVER_URL'.
|
||||
</p>
|
||||
</span>
|
||||
<br/>
|
||||
Tip: <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration#brightdata-proxy-support">Connect using BrightData Proxies, find out more here.</a>
|
||||
<br />
|
||||
Tip:
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration#brightdata-proxy-support"
|
||||
>Connect using BrightData Proxies, find out more here.</a
|
||||
>
|
||||
</div>
|
||||
<fieldset class="pure-group" id="webdriver-override-options">
|
||||
<div class="pure-form-message-inline">
|
||||
<strong>If you're having trouble waiting for the page to be fully rendered (text missing etc), try increasing the 'wait' time here.</strong>
|
||||
<br/>
|
||||
<strong
|
||||
>If you're having trouble waiting for the page to be fully
|
||||
rendered (text missing etc), try increasing the 'wait' time
|
||||
here.</strong
|
||||
>
|
||||
<br />
|
||||
This will wait <i>n</i> seconds before extracting the text.
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
@@ -115,43 +175,71 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-pane-inner" id="filters">
|
||||
|
||||
<fieldset class="pure-group">
|
||||
{{ render_checkbox_field(form.application.form.ignore_whitespace) }}
|
||||
<span class="pure-form-message-inline">Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected.<br/>
|
||||
<i>Note:</i> Changing this will change the status of your existing watches, possibly trigger alerts etc.
|
||||
<span class="pure-form-message-inline"
|
||||
>Ignore whitespace, tabs and new-lines/line-feeds when considering
|
||||
if a change was detected.<br />
|
||||
<i>Note:</i> Changing this will change the status of your existing
|
||||
watches, possibly trigger alerts etc.
|
||||
</span>
|
||||
</fieldset>
|
||||
<fieldset class="pure-group">
|
||||
{{ render_checkbox_field(form.application.form.render_anchor_tag_content) }}
|
||||
<span class="pure-form-message-inline">Render anchor tag content, default disabled, when enabled renders links as <code>(link text)[https://somesite.com]</code>
|
||||
<br/>
|
||||
<i>Note:</i> Changing this could affect the content of your existing watches, possibly trigger alerts etc.
|
||||
{{
|
||||
render_checkbox_field(form.application.form.render_anchor_tag_content)
|
||||
}}
|
||||
<span class="pure-form-message-inline"
|
||||
>Render anchor tag content, default disabled, when enabled renders
|
||||
links as <code>(link text)[https://somesite.com]</code>
|
||||
<br />
|
||||
<i>Note:</i> Changing this could affect the content of your existing
|
||||
watches, possibly trigger alerts etc.
|
||||
</span>
|
||||
</fieldset>
|
||||
<fieldset class="pure-group">
|
||||
{{ render_field(form.application.form.global_subtractive_selectors, rows=5, placeholder="header
|
||||
{{ render_field(form.application.form.global_subtractive_selectors,
|
||||
rows=5, placeholder="header
|
||||
footer
|
||||
nav
|
||||
.stockticker") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li> Remove HTML element(s) by CSS selector before text conversion. </li>
|
||||
<li> Add multiple elements or CSS selectors per line to ignore multiple parts of the HTML. </li>
|
||||
<li>
|
||||
Remove HTML element(s) by CSS selector before text conversion.
|
||||
</li>
|
||||
<li>
|
||||
Add multiple elements or CSS selectors per line to ignore
|
||||
multiple parts of the HTML.
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</fieldset>
|
||||
<fieldset class="pure-group">
|
||||
{{ render_field(form.application.form.global_ignore_text, rows=5, placeholder="Some text to ignore in a line
|
||||
/some.regex\d{2}/ for case-INsensitive regex
|
||||
") }}
|
||||
<span class="pure-form-message-inline">Note: This is applied globally in addition to the per-watch rules.</span><br/>
|
||||
{{ render_field(form.application.form.global_ignore_text, rows=5,
|
||||
placeholder="Some text to ignore in a line /some.regex\d{2}/ for
|
||||
case-INsensitive regex ") }}
|
||||
<span class="pure-form-message-inline"
|
||||
>Note: This is applied globally in addition to the per-watch
|
||||
rules.</span
|
||||
><br />
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>Note: This is applied globally in addition to the per-watch rules.</li>
|
||||
<li>Each line processed separately, any line matching will be ignored (removed before creating the checksum)</li>
|
||||
<li>Regular Expression support, wrap the entire line in forward slash <code>/regex/</code></li>
|
||||
<li>Changing this will affect the comparison checksum which may trigger an alert</li>
|
||||
<li>
|
||||
Note: This is applied globally in addition to the per-watch
|
||||
rules.
|
||||
</li>
|
||||
<li>
|
||||
Each line processed separately, any line matching will be
|
||||
ignored (removed before creating the checksum)
|
||||
</li>
|
||||
<li>
|
||||
Regular Expression support, wrap the entire line in forward
|
||||
slash <code>/regex/</code>
|
||||
</li>
|
||||
<li>
|
||||
Changing this will affect the comparison checksum which may
|
||||
trigger an alert
|
||||
</li>
|
||||
<li>Use the preview/show current tab to see ignores</li>
|
||||
</ul>
|
||||
</span>
|
||||
@@ -159,14 +247,25 @@ nav
|
||||
</div>
|
||||
|
||||
<div class="tab-pane-inner" id="api">
|
||||
|
||||
<p>Drive your changedetection.io via API, More about <a href="https://github.com/dgtlmoon/changedetection.io/wiki/API-Reference">API access here</a></p>
|
||||
<p>
|
||||
Drive your changedetection.io via API, More about
|
||||
<a
|
||||
href="https://github.com/dgtlmoon/changedetection.io/wiki/API-Reference"
|
||||
>API access here</a
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_checkbox_field(form.application.form.api_access_token_enabled) }}
|
||||
<div class="pure-form-message-inline">Restrict API access limit by using <code>x-api-key</code> header</div><br/>
|
||||
<div class="pure-form-message-inline"><br/>API Key <span id="api-key">{{api_key}}</span>
|
||||
<span style="display:none;" id="api-key-copy" >copy</span>
|
||||
{{
|
||||
render_checkbox_field(form.application.form.api_access_token_enabled)
|
||||
}}
|
||||
<div class="pure-form-message-inline">
|
||||
Restrict API access limit by using <code>x-api-key</code> header
|
||||
</div>
|
||||
<br />
|
||||
<div class="pure-form-message-inline">
|
||||
<br />API Key <span id="api-key">{{api_key}}</span>
|
||||
<span style="display: none" id="api-key-copy">copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,10 +273,15 @@ nav
|
||||
<div id="actions">
|
||||
<div class="pure-control-group">
|
||||
{{ render_button(form.save_button) }}
|
||||
<a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Back</a>
|
||||
<a href="{{url_for('clear_all_history')}}" class="pure-button button-small button-cancel">Clear Snapshot History</a>
|
||||
<a href="{{url_for('index')}}" class="pure-button button-cancel"
|
||||
>Back</a
|
||||
>
|
||||
<a
|
||||
href="{{url_for('clear_all_history')}}"
|
||||
class="pure-button button-cancel"
|
||||
>Clear Snapshot History</a
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import time
|
||||
from flask import url_for
|
||||
from urllib.request import urlopen
|
||||
from .util import set_original_response, set_modified_response, live_server_setup, wait_for_all_checks
|
||||
|
||||
sleep_time_for_fetch_thread = 3
|
||||
|
||||
|
||||
|
||||
def test_check_extract_text_from_diff(client, live_server):
|
||||
import time
|
||||
with open("test-datastore/endpoint-content.txt", "w") as f:
|
||||
f.write("Now it's {} seconds since epoch, time flies!".format(str(time.time())))
|
||||
|
||||
live_server_setup(live_server)
|
||||
|
||||
# Add our URL to the import page
|
||||
res = client.post(
|
||||
url_for("import_page"),
|
||||
data={"urls": url_for('test_endpoint', _external=True)},
|
||||
follow_redirects=True
|
||||
)
|
||||
|
||||
assert b"1 Imported" in res.data
|
||||
time.sleep(1)
|
||||
|
||||
# Load in 5 different numbers/changes
|
||||
last_date=""
|
||||
for n in range(5):
|
||||
# Give the thread time to pick it up
|
||||
print("Bumping snapshot and checking.. ", n)
|
||||
last_date = str(time.time())
|
||||
with open("test-datastore/endpoint-content.txt", "w") as f:
|
||||
f.write("Now it's {} seconds since epoch, time flies!".format(last_date))
|
||||
|
||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.post(
|
||||
url_for("diff_history_page", uuid="first"),
|
||||
data={"extract_regex": "Now it's ([0-9\.]+)",
|
||||
"extract_submit_button": "Extract as CSV"},
|
||||
follow_redirects=False
|
||||
)
|
||||
|
||||
assert b'Nothing matches that RegEx' not in res.data
|
||||
assert res.content_type == 'text/csv'
|
||||
|
||||
# Read the csv reply as stringio
|
||||
from io import StringIO
|
||||
import csv
|
||||
|
||||
f = StringIO(res.data.decode('utf-8'))
|
||||
reader = csv.reader(f, delimiter=',')
|
||||
output=[]
|
||||
|
||||
for row in reader:
|
||||
output.append(row)
|
||||
|
||||
assert output[0][0] == 'Epoch seconds'
|
||||
|
||||
# Header line + 1 origin/first + 5 changes
|
||||
assert(len(output) == 7)
|
||||
|
||||
# We expect to find the last bumped date in the changes in the last field of the spreadsheet
|
||||
assert(output[6][2] == last_date)
|
||||
# And nothing else, only that group () of the decimal and .
|
||||
assert "time flies" not in output[6][2]
|
||||
Reference in New Issue
Block a user