mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-07 18:17:19 +00:00
Compare commits
2 Commits
ui-html-va
...
UI-browser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2483e26593 | ||
|
|
c4001ec3ee |
@@ -112,35 +112,6 @@ def build_watch_json_schema(d):
|
|||||||
|
|
||||||
schema['properties']['time_between_check'] = build_time_between_check_json_schema()
|
schema['properties']['time_between_check'] = build_time_between_check_json_schema()
|
||||||
|
|
||||||
schema['properties']['browser_steps'] = {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"operation": {
|
|
||||||
"type": ["string", "null"],
|
|
||||||
"maxLength": 5000 # Allows null and any string up to 5000 chars (including "")
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"type": ["string", "null"],
|
|
||||||
"maxLength": 5000
|
|
||||||
},
|
|
||||||
"optional_value": {
|
|
||||||
"type": ["string", "null"],
|
|
||||||
"maxLength": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["operation", "selector", "optional_value"],
|
|
||||||
"additionalProperties": False # No extra keys allowed
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{"type": "null"}, # Allows null for `browser_steps`
|
|
||||||
{"type": "array", "maxItems": 0} # Allows empty array []
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# headers ?
|
# headers ?
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ class steppable_browser_interface():
|
|||||||
page = None
|
page = None
|
||||||
start_url = None
|
start_url = None
|
||||||
|
|
||||||
action_timeout = 10 * 1000
|
|
||||||
|
|
||||||
def __init__(self, start_url):
|
def __init__(self, start_url):
|
||||||
self.start_url = start_url
|
self.start_url = start_url
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ class steppable_browser_interface():
|
|||||||
return
|
return
|
||||||
elem = self.page.get_by_text(value)
|
elem = self.page.get_by_text(value)
|
||||||
if elem.count():
|
if elem.count():
|
||||||
elem.first.click(delay=randint(200, 500), timeout=self.action_timeout)
|
elem.first.click(delay=randint(200, 500), timeout=3000)
|
||||||
|
|
||||||
def action_click_element_containing_text_if_exists(self, selector=None, value=''):
|
def action_click_element_containing_text_if_exists(self, selector=None, value=''):
|
||||||
logger.debug("Clicking element containing text if exists")
|
logger.debug("Clicking element containing text if exists")
|
||||||
@@ -113,7 +111,7 @@ class steppable_browser_interface():
|
|||||||
elem = self.page.get_by_text(value)
|
elem = self.page.get_by_text(value)
|
||||||
logger.debug(f"Clicking element containing text - {elem.count()} elements found")
|
logger.debug(f"Clicking element containing text - {elem.count()} elements found")
|
||||||
if elem.count():
|
if elem.count():
|
||||||
elem.first.click(delay=randint(200, 500), timeout=self.action_timeout)
|
elem.first.click(delay=randint(200, 500), timeout=3000)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -121,7 +119,7 @@ class steppable_browser_interface():
|
|||||||
if not len(selector.strip()):
|
if not len(selector.strip()):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.page.fill(selector, value, timeout=self.action_timeout)
|
self.page.fill(selector, value, timeout=10 * 1000)
|
||||||
|
|
||||||
def action_execute_js(self, selector, value):
|
def action_execute_js(self, selector, value):
|
||||||
response = self.page.evaluate(value)
|
response = self.page.evaluate(value)
|
||||||
@@ -132,7 +130,7 @@ class steppable_browser_interface():
|
|||||||
if not len(selector.strip()):
|
if not len(selector.strip()):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.page.click(selector=selector, timeout=self.action_timeout + 20 * 1000, delay=randint(200, 500))
|
self.page.click(selector=selector, timeout=30 * 1000, delay=randint(200, 500))
|
||||||
|
|
||||||
def action_click_element_if_exists(self, selector, value):
|
def action_click_element_if_exists(self, selector, value):
|
||||||
import playwright._impl._errors as _api_types
|
import playwright._impl._errors as _api_types
|
||||||
@@ -140,7 +138,7 @@ class steppable_browser_interface():
|
|||||||
if not len(selector.strip()):
|
if not len(selector.strip()):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.page.click(selector, timeout=self.action_timeout, delay=randint(200, 500))
|
self.page.click(selector, timeout=10 * 1000, delay=randint(200, 500))
|
||||||
except _api_types.TimeoutError as e:
|
except _api_types.TimeoutError as e:
|
||||||
return
|
return
|
||||||
except _api_types.Error as e:
|
except _api_types.Error as e:
|
||||||
@@ -187,10 +185,10 @@ class steppable_browser_interface():
|
|||||||
self.page.keyboard.press("PageDown", delay=randint(200, 500))
|
self.page.keyboard.press("PageDown", delay=randint(200, 500))
|
||||||
|
|
||||||
def action_check_checkbox(self, selector, value):
|
def action_check_checkbox(self, selector, value):
|
||||||
self.page.locator(selector).check(timeout=self.action_timeout)
|
self.page.locator(selector).check(timeout=1000)
|
||||||
|
|
||||||
def action_uncheck_checkbox(self, selector, value):
|
def action_uncheck_checkbox(self, selector, value):
|
||||||
self.page.locator(selector).uncheck(timeout=self.action_timeout)
|
self.page.locator(selector, timeout=1000).uncheck(timeout=1000)
|
||||||
|
|
||||||
|
|
||||||
# Responsible for maintaining a live 'context' with the chrome CDP
|
# Responsible for maintaining a live 'context' with the chrome CDP
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ class model(watch_base):
|
|||||||
# Iterate over all history texts and see if something new exists
|
# Iterate over all history texts and see if something new exists
|
||||||
# Always applying .strip() to start/end but optionally replace any other whitespace
|
# Always applying .strip() to start/end but optionally replace any other whitespace
|
||||||
def lines_contain_something_unique_compared_to_history(self, lines: list, ignore_whitespace=False):
|
def lines_contain_something_unique_compared_to_history(self, lines: list, ignore_whitespace=False):
|
||||||
local_lines = set([])
|
local_lines = []
|
||||||
if lines:
|
if lines:
|
||||||
if ignore_whitespace:
|
if ignore_whitespace:
|
||||||
if isinstance(lines[0], str): # Can be either str or bytes depending on what was on the disk
|
if isinstance(lines[0], str): # Can be either str or bytes depending on what was on the disk
|
||||||
|
|||||||
@@ -1,66 +1,48 @@
|
|||||||
(function ($) {
|
// Rewrite this is a plugin.. is all this JS really 'worth it?'
|
||||||
$.fn.hashTabs = function (options) {
|
|
||||||
var settings = $.extend({
|
|
||||||
tabContainer: ".tabs ul",
|
|
||||||
tabSelector: "li a",
|
|
||||||
tabContent: ".tab-pane-inner",
|
|
||||||
activeClass: "active",
|
|
||||||
errorClass: ".messages .error",
|
|
||||||
bodyClassToggle: "full-width"
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
var $tabs = $(settings.tabContainer).find(settings.tabSelector);
|
window.addEventListener('hashchange', function () {
|
||||||
|
var tabs = document.getElementsByClassName('active');
|
||||||
|
while (tabs[0]) {
|
||||||
|
tabs[0].classList.remove('active');
|
||||||
|
document.body.classList.remove('full-width');
|
||||||
|
}
|
||||||
|
set_active_tab();
|
||||||
|
}, false);
|
||||||
|
|
||||||
function setActiveTab() {
|
var has_errors = document.querySelectorAll(".messages .error");
|
||||||
var hash = window.location.hash;
|
if (!has_errors.length) {
|
||||||
var $activeTab = $tabs.filter("[href='" + hash + "']");
|
if (document.location.hash == "") {
|
||||||
|
location.replace(document.querySelector(".tabs ul li:first-child a").hash);
|
||||||
|
} else {
|
||||||
|
set_active_tab();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
focus_error_tab();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove active class from all tabs
|
function set_active_tab() {
|
||||||
$(settings.tabContainer).find("li").removeClass(settings.activeClass);
|
document.body.classList.remove('full-width');
|
||||||
|
var tab = document.querySelectorAll("a[href='" + location.hash + "']");
|
||||||
|
if (tab.length) {
|
||||||
|
tab[0].parentElement.className = "active";
|
||||||
|
}
|
||||||
|
|
||||||
// Add active class to selected tab
|
}
|
||||||
if ($activeTab.length) {
|
|
||||||
$activeTab.parent().addClass(settings.activeClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the correct content
|
function focus_error_tab() {
|
||||||
$(settings.tabContent).hide();
|
// time to use jquery or vuejs really,
|
||||||
if (hash) {
|
// activate the tab with the error
|
||||||
$(hash).show();
|
var tabs = document.querySelectorAll('.tabs li a'), i;
|
||||||
}
|
for (i = 0; i < tabs.length; ++i) {
|
||||||
|
var tab_name = tabs[i].hash.replace('#', '');
|
||||||
|
var pane_errors = document.querySelectorAll('#' + tab_name + ' .error')
|
||||||
|
if (pane_errors.length) {
|
||||||
|
document.location.hash = '#' + tab_name;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function focusErrorTab() {
|
return false;
|
||||||
$tabs.each(function () {
|
}
|
||||||
var tabName = this.hash.replace("#", "");
|
|
||||||
if ($("#" + tabName).find(settings.errorClass).length) {
|
|
||||||
window.location.hash = "#" + tabName;
|
|
||||||
return false; // Stop loop on first error tab
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeTabs() {
|
|
||||||
if ($(settings.errorClass).length) {
|
|
||||||
focusErrorTab();
|
|
||||||
} else if (!window.location.hash) {
|
|
||||||
window.location.replace($tabs.first().attr("href"));
|
|
||||||
} else {
|
|
||||||
setActiveTab();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for hash changes
|
|
||||||
$(window).on("hashchange", setActiveTab);
|
|
||||||
|
|
||||||
// Initialize on page load
|
|
||||||
initializeTabs();
|
|
||||||
|
|
||||||
return this; // Enable jQuery chaining
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$(".tabs").hashTabs();
|
|
||||||
});
|
|
||||||
@@ -945,7 +945,15 @@ $form-edge-padding: 20px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane-inner {
|
.tab-pane-inner {
|
||||||
display: none;
|
|
||||||
|
&:not(:target) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:target {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
// doesnt need padding because theres another row of buttons/activity
|
// doesnt need padding because theres another row of buttons/activity
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1159,8 +1159,11 @@ textarea::placeholder {
|
|||||||
border-radius: 5px; }
|
border-radius: 5px; }
|
||||||
|
|
||||||
.tab-pane-inner {
|
.tab-pane-inner {
|
||||||
display: none;
|
|
||||||
padding: 0px; }
|
padding: 0px; }
|
||||||
|
.tab-pane-inner:not(:target) {
|
||||||
|
display: none; }
|
||||||
|
.tab-pane-inner:target {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
.beta-logo {
|
.beta-logo {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|||||||
@@ -12,13 +12,13 @@
|
|||||||
}}
|
}}
|
||||||
<div class="pure-form-message-inline">
|
<div class="pure-form-message-inline">
|
||||||
<p>
|
<p>
|
||||||
<strong>Tip:</strong> Use <a target="newwindow" href="https://github.com/caronc/apprise">AppRise Notification URLs</a> for notification to just about any service! <i><a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.<br>
|
<strong>Tip:</strong> Use <a target=_new href="https://github.com/caronc/apprise">AppRise Notification URLs</a> for notification to just about any service! <i><a target=_new href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.<br>
|
||||||
</p>
|
</p>
|
||||||
<div data-target="#advanced-help-notifications" class="toggle-show pure-button button-tag button-xsmall">Show advanced help and tips</div>
|
<div data-target="#advanced-help-notifications" class="toggle-show pure-button button-tag button-xsmall">Show advanced help and tips</div>
|
||||||
<ul style="display: none" id="advanced-help-notifications">
|
<ul style="display: none" id="advanced-help-notifications">
|
||||||
<li><code><a target="newwindow" href="https://github.com/caronc/apprise/wiki/Notify_discord">discord://</a></code> (or <code>https://discord.com/api/webhooks...</code>)) only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li>
|
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_discord">discord://</a></code> (or <code>https://discord.com/api/webhooks...</code>)) only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li>
|
||||||
<li><code><a target="newwindow" href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> bots can't send messages to other bots, so you should specify chat ID of non-bot user.</li>
|
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> bots can't send messages to other bots, so you should specify chat ID of non-bot user.</li>
|
||||||
<li><code><a target="newwindow" href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> only supports very limited HTML and can fail when extra tags are sent, <a href="https://core.telegram.org/bots/api#html-style">read more here</a> (or use plaintext/markdown format)</li>
|
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> only supports very limited HTML and can fail when extra tags are sent, <a href="https://core.telegram.org/bots/api#html-style">read more here</a> (or use plaintext/markdown format)</li>
|
||||||
<li><code>gets://</code>, <code>posts://</code>, <code>puts://</code>, <code>deletes://</code> for direct API calls (or omit the "<code>s</code>" for non-SSL ie <code>get://</code>) <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes#postposts">more help here</a></li>
|
<li><code>gets://</code>, <code>posts://</code>, <code>puts://</code>, <code>deletes://</code> for direct API calls (or omit the "<code>s</code>" for non-SSL ie <code>get://</code>) <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes#postposts">more help here</a></li>
|
||||||
<li>Accepts the <code>{{ '{{token}}' }}</code> placeholders listed below</li>
|
<li>Accepts the <code>{{ '{{token}}' }}</code> placeholders listed below</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
<div class="pure-form-message-inline">
|
<div class="pure-form-message-inline">
|
||||||
<p>
|
<p>
|
||||||
Warning: Contents of <code>{{ '{{diff}}' }}</code>, <code>{{ '{{diff_removed}}' }}</code>, and <code>{{ '{{diff_added}}' }}</code> depend on how the difference algorithm perceives the change. <br>
|
Warning: Contents of <code>{{ '{{diff}}' }}</code>, <code>{{ '{{diff_removed}}' }}</code>, and <code>{{ '{{diff_added}}' }}</code> depend on how the difference algorithm perceives the change. <br>
|
||||||
For example, an addition or removal could be perceived as a change in some cases. <a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Using-the-%7B%7Bdiff%7D%7D,-%7B%7Bdiff_added%7D%7D,-and-%7B%7Bdiff_removed%7D%7D-notification-tokens">More Here</a> <br>
|
For example, an addition or removal could be perceived as a change in some cases. <a target="_new" href="https://github.com/dgtlmoon/changedetection.io/wiki/Using-the-%7B%7Bdiff%7D%7D,-%7B%7Bdiff_added%7D%7D,-and-%7B%7Bdiff_removed%7D%7D-notification-tokens">More Here</a> <br>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
For JSON payloads, use <strong>|tojson</strong> without quotes for automatic escaping, for example - <code>{ "name": {{ '{{ watch_title|tojson }}' }} }</code>
|
For JSON payloads, use <strong>|tojson</strong> without quotes for automatic escaping, for example - <code>{ "name": {{ '{{ watch_title|tojson }}' }} }</code>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
<a id="chrome-extension-link"
|
<a id="chrome-extension-link"
|
||||||
title="Try our new Chrome Extension!"
|
title="Try our new Chrome Extension!"
|
||||||
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
|
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
|
||||||
<img alt="Chrome store icon" src="{{url_for('static_content', group='images', filename='Google-Chrome-icon.png')}}">
|
<img src="{{url_for('static_content', group='images', filename='Google-Chrome-icon.png')}}">
|
||||||
Chrome Webstore
|
Chrome Webstore
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ Math: {{ 1 + 1 }}") }}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="browser-steps-fieldlist" >
|
<div id="browser-steps-fieldlist" >
|
||||||
<span id="browser-seconds-remaining">Loading</span> <span style="font-size: 80%;"> (<a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/pull/478/files#diff-1a79d924d1840c485238e66772391268a89c95b781d69091384cf1ea1ac146c9R4">?</a>) </span>
|
<span id="browser-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) }}
|
{{ render_field(form.browser_steps) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -306,7 +306,7 @@ xpath://body/div/span[contains(@class, 'example-class')]",
|
|||||||
<span class="pure-form-message-inline"><strong>Note!: //text() function does not work where the <element> contains <![CDATA[]]></strong></span><br>
|
<span class="pure-form-message-inline"><strong>Note!: //text() function does not work where the <element> contains <![CDATA[]]></strong></span><br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="pure-form-message-inline">One CSS, xPath, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used.<br>
|
<span class="pure-form-message-inline">One CSS, xPath, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used.<br>
|
||||||
<span data-target="#advanced-help-selectors" class="toggle-show pure-button button-tag button-xsmall">Show advanced help and tips</span><br>
|
<p><div data-target="#advanced-help-selectors" class="toggle-show pure-button button-tag button-xsmall">Show advanced help and tips</div><br></p>
|
||||||
<ul id="advanced-help-selectors" style="display: none;">
|
<ul id="advanced-help-selectors" style="display: none;">
|
||||||
<li>CSS - Limit text to this CSS rule, only text matching this CSS rule is included.</li>
|
<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>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).
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ nav
|
|||||||
<a id="chrome-extension-link"
|
<a id="chrome-extension-link"
|
||||||
title="Try our new Chrome Extension!"
|
title="Try our new Chrome Extension!"
|
||||||
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
|
href="https://chromewebstore.google.com/detail/changedetectionio-website/kefcfmgmlhmankjmnbijimhofdjekbop">
|
||||||
<img alt="Chrome store icon" src="{{ url_for('static_content', group='images', filename='Google-Chrome-icon.png') }}" alt="Chrome">
|
<img src="{{ url_for('static_content', group='images', filename='Google-Chrome-icon.png') }}" alt="Chrome">
|
||||||
Chrome Webstore
|
Chrome Webstore
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -280,7 +280,9 @@ nav
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p>
|
||||||
|
Your proxy provider may need to whitelist our IP of <code>204.15.192.195</code>
|
||||||
|
</p>
|
||||||
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.
|
<p><strong>Tip</strong>: "Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.
|
||||||
|
|
||||||
<div class="pure-control-group" id="extra-proxies-setting">
|
<div class="pure-control-group" id="extra-proxies-setting">
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
or ( watch.get_fetch_backend == "system" and system_default_fetcher == 'html_webdriver' )
|
or ( watch.get_fetch_backend == "system" and system_default_fetcher == 'html_webdriver' )
|
||||||
or "extra_browser_" in watch.get_fetch_backend
|
or "extra_browser_" in watch.get_fetch_backend
|
||||||
%}
|
%}
|
||||||
<img class="status-icon" src="{{url_for('static_content', group='images', filename='Google-Chrome-icon.png')}}" alt="Using a Chrome browser" title="Using a Chrome browser" >
|
<img class="status-icon" src="{{url_for('static_content', group='images', filename='Google-Chrome-icon.png')}}" title="Using a Chrome browser" >
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{%if watch.is_pdf %}<img class="status-icon" src="{{url_for('static_content', group='images', filename='pdf-icon.svg')}}" title="Converting PDF to text" >{% endif %}
|
{%if watch.is_pdf %}<img class="status-icon" src="{{url_for('static_content', group='images', filename='pdf-icon.svg')}}" title="Converting PDF to text" >{% endif %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: '3.2'
|
||||||
services:
|
services:
|
||||||
changedetection:
|
changedetection:
|
||||||
image: ghcr.io/dgtlmoon/changedetection.io
|
image: ghcr.io/dgtlmoon/changedetection.io
|
||||||
|
|||||||
Reference in New Issue
Block a user