Compare commits

..

19 Commits

Author SHA1 Message Date
Tanc
e6f8b44c27 Use cookies instead of store for darkmode (#1188) 2022-12-04 15:49:21 +01:00
dgtlmoon
49bed43cff theme switch should have its own list item 2022-12-04 15:43:11 +01:00
dgtlmoon
4218f25cb8 hidden 'last checked' colour 2022-12-04 15:30:19 +01:00
dgtlmoon
dfdd69de23 Oops - learning 2022-12-04 15:22:25 +01:00
dgtlmoon
f612df1b1b fixing placeholder text 2022-12-04 15:15:12 +01:00
dgtlmoon
c82bf98f13 tweaking colours - textarea borders were not visible, form description text was same colour as labels 2022-12-04 15:10:16 +01:00
dgtlmoon
855605c434 Set text colour of BrowserSteps start text 2022-12-04 15:01:20 +01:00
dgtlmoon
3b2b1ea2f7 cleanup callback name and enfore db update 2022-12-04 14:55:16 +01:00
dgtlmoon
5219698b07 Update changedetectionio/__init__.py 2022-12-04 14:43:46 +01:00
dgtlmoon
00be226210 Update changedetectionio/__init__.py 2022-12-04 14:43:40 +01:00
tanc
3078ddb261 956 Update compiled css 2022-12-04 14:09:39 +01:00
tanc
cdf691d5c4 956 Fix github icon hover colours 2022-12-04 14:09:13 +01:00
tanc
50e50f1caf 956 Update compiled css 2022-12-04 14:02:46 +01:00
tanc
f96340c45e 956 Clean up svgs to remove extra metadata 2022-12-04 14:02:22 +01:00
tanc
0696dfa30d 956 Rework the SCSS so it includes partials properly 2022-12-04 14:01:43 +01:00
tanc
b65f08eadb 956 Add watch task and optimise build task 2022-12-04 13:56:25 +01:00
tanc
e3a1a4275a 956 Styles to support dark mode 2022-12-04 13:55:32 +01:00
tanc
b2da26b2f1 956 Templates to support dark mode 2022-12-04 13:54:48 +01:00
tanc
427cf105a3 956 Code changes to enable dark mode 2022-12-04 13:53:58 +01:00
15 changed files with 948 additions and 790 deletions

View File

@@ -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

View File

@@ -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"})

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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`
}
});

View File

@@ -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,9 +128,8 @@ html[data-darkmode="true"] {
padding: 2em;
margin-left: 1em;
margin-right: 1em;
border-radius: 5px; }
#diff-ui #text {
font-size: 11px; }
border-radius: 5px;
font-size: 11px; }
#diff-ui table {
table-layout: fixed;
width: 100%; }

View File

@@ -7,11 +7,7 @@
margin-left: 1em;
margin-right: 1em;
border-radius: 5px;
// The first tab 'text' diff
#text {
font-size: 11px;
}
font-size: 11px;
table {
table-layout: fixed;

View File

@@ -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);
}

View File

@@ -121,16 +121,12 @@ code {
width: 100%;
font-size: 80%;
tr {
&.unviewed {
font-weight: bold;
}
color: var(--color-watch-table-row-text);
tr.unviewed {
font-weight: bold;
}
.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;

View File

@@ -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 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;

View File

@@ -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" %}

View File

@@ -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 &dash;</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>

File diff suppressed because it is too large Load Diff

View File

@@ -1,186 +1,290 @@
{% 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 %}
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
{% endif %}
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
{% if emailprefix %}
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
{% 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>
<li class="tab" id=""><a href="#general">General</a></li>
<li class="tab"><a href="#notifications">Notifications</a></li>
<li class="tab"><a href="#fetching">Fetching</a></li>
<li class="tab"><a href="#filters">Global Filters</a></li>
<li class="tab"><a href="#api">API</a></li>
</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() }}"/>
<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>
</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>
</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/>
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 %}
<span class="pure-form-message-inline">Password is locked.</span>
{% endif %}
</div>
<div class="tabs collapsable">
<ul>
<li class="tab" id=""><a href="#general">General</a></li>
<li class="tab"><a href="#notifications">Notifications</a></li>
<li class="tab"><a href="#fetching">Fetching</a></li>
<li class="tab"><a href="#filters">Global Filters</a></li>
<li class="tab"><a href="#api">API</a></li>
</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() }}" />
<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
>
</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
>
</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 />
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 %}
<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") }}
<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>.
</span>
</div>
<div class="pure-control-group">
{{ 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
>.
</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>
</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>
</div>
{% if form.requests.proxy %}
<div class="pure-control-group inline-radio">
{{ render_field(form.requests.form.proxy, class="fetch-backend-proxy") }}
<span class="pure-form-message-inline">
Choose a default proxy for all watches
</span>
</div>
{% endif %}
</fieldset>
</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
>
</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
>
</div>
{% if form.requests.proxy %}
<div class="pure-control-group inline-radio">
{{ render_field(form.requests.form.proxy,
class="fetch-backend-proxy") }}
<span class="pure-form-message-inline">
Choose a default proxy for all watches
</span>
</div>
{% endif %}
</fieldset>
</div>
<div class="tab-pane-inner" id="notifications">
<fieldset>
<div class="field-group">
{{ render_common_settings_form(form.application.form, emailprefix, settings_application) }}
</div>
</fieldset>
</div>
<div class="tab-pane-inner" id="notifications">
<fieldset>
<div class="field-group">
{{ 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") }}
<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>
</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>
</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/>
This will wait <i>n</i> seconds before extracting the text.
</div>
<div class="pure-control-group">
{{ render_field(form.application.form.webdriver_delay) }}
</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") }}
<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>
</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
>
</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 />
This will wait <i>n</i> seconds before extracting the text.
</div>
<div class="pure-control-group">
{{ render_field(form.application.form.webdriver_delay) }}
</div>
</fieldset>
</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>
</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.
</span>
</fieldset>
<fieldset class="pure-group">
{{ render_field(form.application.form.global_subtractive_selectors, rows=5, placeholder="header
<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>
</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.
</span>
</fieldset>
<fieldset class="pure-group">
{{ 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>
</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/>
<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>Use the preview/show current tab to see ignores</li>
</ul>
</span>
</fieldset>
</div>
<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>
</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 />
<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>Use the preview/show current tab to see ignores</li>
</ul>
</span>
</fieldset>
</div>
<div class="tab-pane-inner" id="api">
<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>
</div>
</div>
</div>
<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>
</div>
</div>
</div>
<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>
</div>
</div>
</form>
</div>
<div id="actions">
<div class="pure-control-group">
{{ render_button(form.save_button) }}
<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>
</div>
{% endblock %}

View File

@@ -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]