mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-09-26 15:26:13 +00:00
Add watch UI should show which browser(s) are available and set default browser (#4334)
This commit is contained in:
@@ -3,6 +3,7 @@ from loguru import logger
|
||||
|
||||
from changedetectionio import forms
|
||||
from changedetectionio.auth_decorator import login_optionally_required
|
||||
from . import browser_config
|
||||
from changedetectionio.store import ChangeDetectionStore
|
||||
from changedetectionio.validate_url import is_fetch_url_allowed
|
||||
|
||||
@@ -19,11 +20,17 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
form = forms.quickWatchForm(None)
|
||||
llm_configured = bool(_get_llm_config(datastore))
|
||||
|
||||
# Start on the browser the live preview would actually use
|
||||
form.fetch_backend.data = browser_config.default_visual_browser(datastore)
|
||||
|
||||
return render_template(
|
||||
"add-watch-ui.html",
|
||||
form=form,
|
||||
llm_configured=llm_configured,
|
||||
llm_intent_watch_placeholder=LLM_INTENT_WATCH_PLACEHOLDER,
|
||||
# Listed but not selectable (the system default when it can't render a preview)
|
||||
unusable_browsers=browser_config.unusable_values(datastore),
|
||||
system_default_browser=browser_config.system_default_description(datastore),
|
||||
)
|
||||
|
||||
@add_watch_ui_blueprint.route("/snapshot", methods=['GET'])
|
||||
@@ -61,15 +68,27 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
logger.warning(f"Add-watch snapshot: refused '{url}' - {reason}")
|
||||
return make_response(reason, 400)
|
||||
|
||||
# Use whatever fetcher the application is configured to use by default
|
||||
# (e.g. CloakBrowser, Playwright/sockpuppet) so the preview matches real checks.
|
||||
fetcher_name = datastore.data['settings']['application'].get('fetch_backend', 'html_requests')
|
||||
logger.debug(f"Add-watch snapshot: fetching '{url}' using system default fetcher '{fetcher_name}'")
|
||||
# Which browser to preview with. The page posts the one picked in its browser list;
|
||||
# with nothing asked for we fall back to whatever it would have preselected.
|
||||
# Either way it has to be able to render a preview - the plain HTTP client
|
||||
# produces no screenshot and no element data, so previewing with it is pointless
|
||||
# (and it used to be the silent default here, see the system-default bug).
|
||||
fetcher_name = (request.args.get('fetch_backend') or '').strip() or browser_config.default_visual_browser(datastore)
|
||||
if not fetcher_name or not browser_config.is_visual_capable(fetcher_name, datastore):
|
||||
logger.warning(f"Add-watch snapshot: refused browser '{fetcher_name}' for '{url}'")
|
||||
return make_response('No interactive browser available that can render a live preview '
|
||||
'(needs screenshots + element data)', 400)
|
||||
|
||||
# acquire_browser_for_fetcher() looks the name up as a fetcher class, so 'system'
|
||||
# has to be collapsed to the real backend first or a fetcher that launches its own
|
||||
# browser would be skipped in favour of the CDP endpoint.
|
||||
resolved_fetcher = browser_config.resolve_backend(fetcher_name, datastore)
|
||||
logger.debug(f"Add-watch snapshot: fetching '{url}' using '{fetcher_name}' (resolved: '{resolved_fetcher}')")
|
||||
|
||||
async def _fetch_snapshot():
|
||||
keepalive_ms = 30 * 1000
|
||||
browser, playwright_context = await acquire_browser_for_fetcher(
|
||||
fetcher_name, proxy=None, keepalive_ms=keepalive_ms
|
||||
resolved_fetcher, proxy=None, keepalive_ms=keepalive_ms
|
||||
)
|
||||
|
||||
stepper = browsersteps_live_ui(playwright_browser=browser, proxy=None, start_url=url)
|
||||
@@ -119,6 +138,13 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
with open(os.path.join(temp_dir, "preload-fetch.json"), 'w', encoding='utf-8') as f:
|
||||
json.dump({"content": html, "status_code": 200,
|
||||
"headers": {"content-type": "text/html"}}, f)
|
||||
# This directory becomes the new watch's data_dir on submit, so the watch's own
|
||||
# settings file is where the previewing browser belongs: record it here and the
|
||||
# saved watch checks with the browser that actually rendered this snapshot,
|
||||
# instead of falling back to a system default that may not even do screenshots.
|
||||
# Read back by make_temporary_watch_active_watch(); commit() then rewrites it in full.
|
||||
with open(os.path.join(temp_dir, "watch.json"), 'w', encoding='utf-8') as f:
|
||||
json.dump({"fetch_backend": fetcher_name}, f)
|
||||
except Exception as e:
|
||||
logger.error(f"Add-watch snapshot: could not park temporary data for {url}: {e}")
|
||||
temp_uuid = None
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Which content fetchers can drive the Add-Watch live preview / visual selector.
|
||||
|
||||
Single source of truth for "can this browser render a live preview": the Add-Watch
|
||||
browser list, the /snapshot endpoint and the submit-time form validator all resolve
|
||||
through here, so the UI can never offer - and the server can never accept - a fetcher
|
||||
that is unable to produce what the visual selector needs.
|
||||
|
||||
The preview is rendered by browsersteps_live_ui() (see blueprint/browser_steps), so a
|
||||
usable browser needs `supports_browser_steps` *as well as* screenshots and xpath
|
||||
element data. That third flag is what rules out Selenium/WebDriver: it can screenshot
|
||||
during a normal check, but it cannot drive the interactive session the preview needs
|
||||
(acquire_browser_for_fetcher() would quietly connect to PLAYWRIGHT_DRIVER_URL instead,
|
||||
which is not the browser the user picked). It also rules out settings.requests
|
||||
extra_browsers, which are WebDriver connection URLs, so they are not offered here.
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
SYSTEM_DEFAULT = 'system'
|
||||
|
||||
# Every flag a fetcher must set to be usable for the Add-Watch live preview.
|
||||
REQUIRED_CAPABILITIES = (
|
||||
'supports_browser_steps',
|
||||
'supports_screenshots',
|
||||
'supports_xpath_element_data',
|
||||
)
|
||||
|
||||
|
||||
def is_visual_capable(fetch_backend, datastore):
|
||||
"""True when this backend can render the Add-Watch live preview.
|
||||
|
||||
An unknown name - including anything a client made up - resolves to no fetcher
|
||||
class and so to all-False capabilities, so it can never pass. That is what makes
|
||||
the posted value safe without any string filtering of our own.
|
||||
|
||||
Reads the flags off the class the same way Watch.fetcher_supports_screenshots does,
|
||||
rather than via pluggy_interface.get_fetcher_capabilities(): that logs every lookup
|
||||
at INFO, and this runs for the whole fetcher list on any page carrying the quick-add
|
||||
form. Plugin fetchers are registered as module attributes, so they resolve here too.
|
||||
"""
|
||||
from changedetectionio import content_fetchers
|
||||
from changedetectionio.content_fetchers.base import FetcherCapabilities
|
||||
|
||||
name = fetch_backend or SYSTEM_DEFAULT
|
||||
if name == SYSTEM_DEFAULT:
|
||||
name = datastore.data['settings']['application'].get('fetch_backend') or 'html_requests'
|
||||
|
||||
caps = FetcherCapabilities.from_fetcher(getattr(content_fetchers, name, None))
|
||||
missing = [flag for flag in REQUIRED_CAPABILITIES if not getattr(caps, flag, False)]
|
||||
logger.debug(f"Add-watch browser '{fetch_backend or SYSTEM_DEFAULT}' -> '{name}': "
|
||||
f"{', '.join(f'{k}={v}' for k, v in caps.model_dump().items())} - "
|
||||
f"{'usable for live preview' if not missing else 'not offered, missing ' + ', '.join(missing)}")
|
||||
return not missing
|
||||
|
||||
|
||||
def resolve_backend(fetch_backend, datastore):
|
||||
"""The concrete fetcher name behind a choice, so 'system' can be acted on.
|
||||
|
||||
Needed because acquire_browser_for_fetcher() looks the name up as a class: handing
|
||||
it 'system' would skip a fetcher that launches its own browser (CloakBrowser) and
|
||||
fall through to the CDP endpoint instead.
|
||||
"""
|
||||
from changedetectionio import content_fetchers
|
||||
|
||||
_fetcher_class, resolved, _custom_url = content_fetchers.resolve_content_fetcher(
|
||||
{'fetch_backend': fetch_backend or SYSTEM_DEFAULT}, datastore)
|
||||
return resolved
|
||||
|
||||
|
||||
def list_visual_browser_choices(datastore):
|
||||
"""(value, label) for every fetcher that can drive the visual selector.
|
||||
|
||||
is_visual_capable() logs each candidate's capabilities as it goes, so a user
|
||||
wondering why their browser isn't in the list can see the missing flag at debug
|
||||
level.
|
||||
"""
|
||||
from changedetectionio import content_fetchers
|
||||
|
||||
choices = [(name, str(description)) for name, description in content_fetchers.available_fetchers()
|
||||
if is_visual_capable(name, datastore)]
|
||||
logger.debug(f"Add-watch browsers offered for the live preview: "
|
||||
f"{[name for name, _label in choices] or 'none'}")
|
||||
return choices
|
||||
|
||||
|
||||
def default_visual_browser(datastore):
|
||||
"""Which browser the Add-Watch page should start on.
|
||||
|
||||
'system' when the global default happens to be capable (so the new watch keeps
|
||||
following the system setting), otherwise the first capable browser, and None when
|
||||
there is nothing usable at all.
|
||||
"""
|
||||
if is_visual_capable(SYSTEM_DEFAULT, datastore):
|
||||
return SYSTEM_DEFAULT
|
||||
choices = list_visual_browser_choices(datastore)
|
||||
return choices[0][0] if choices else None
|
||||
|
||||
|
||||
def system_default_description(datastore):
|
||||
"""Label for whatever backend 'system' currently points at."""
|
||||
from changedetectionio import content_fetchers
|
||||
|
||||
system_backend = datastore.data['settings']['application'].get('fetch_backend') or 'html_requests'
|
||||
return str(dict(content_fetchers.available_fetchers()).get(system_backend, system_backend))
|
||||
|
||||
|
||||
def radio_choices(datastore):
|
||||
"""(value, label) for the Add-Watch browser radio list, system default first.
|
||||
|
||||
'System settings default' is always listed - with the reason in its own label when
|
||||
the global default cannot render a preview, because greying it out explains why it
|
||||
is unavailable where silently dropping it does not. Which entries are unusable is
|
||||
reported separately by unusable_values(); WTForms cannot carry per-option render_kw
|
||||
through a RadioField, so the disabled attribute is applied when rendering.
|
||||
"""
|
||||
from flask_babel import gettext
|
||||
|
||||
described = system_default_description(datastore)
|
||||
|
||||
if is_visual_capable(SYSTEM_DEFAULT, datastore):
|
||||
system_label = gettext('System settings default (%(browser)s)', browser=described)
|
||||
else:
|
||||
system_label = gettext('System settings default (%(browser)s - no live preview)', browser=described)
|
||||
|
||||
return [(SYSTEM_DEFAULT, system_label)] + list_visual_browser_choices(datastore)
|
||||
|
||||
|
||||
def unusable_values(datastore):
|
||||
"""Values that are listed for explanation only and must render disabled."""
|
||||
return set() if is_visual_capable(SYSTEM_DEFAULT, datastore) else {SYSTEM_DEFAULT}
|
||||
@@ -14,6 +14,12 @@ $(document).ready(() => {
|
||||
const $includeFilters = $('#include_filters');
|
||||
const $temporaryUuid = $('#temporary_uuid');
|
||||
|
||||
// When the LLM intent box isn't available (LLM_FEATURES_DISABLED / not configured) the
|
||||
// template renders no "Select by element" checkbox - element selection is the only thing
|
||||
// this page can do, so it is on from the start and can't be turned off.
|
||||
const selectionAlwaysOn = $byElement.length === 0;
|
||||
const selectionEnabled = () => selectionAlwaysOn || $byElement.is(':checked');
|
||||
|
||||
const vs = window.initVisualSelector({
|
||||
$canvas: $('#selector-canvas'),
|
||||
$includeFilters: $includeFilters,
|
||||
@@ -22,7 +28,7 @@ $(document).ready(() => {
|
||||
$fetchingNotice: $('#add-watch-spinner .fetching-update-notice'),
|
||||
$wrapper: $wrapper,
|
||||
$clearButton: $clear,
|
||||
enableSelection: false, // off until the user opts into "Select by element"
|
||||
enableSelection: selectionAlwaysOn, // otherwise off until the user opts into "Select by element"
|
||||
processorIsImage: false,
|
||||
// The snapshot comes from the live browser-steps capture, so scale X by the page
|
||||
// CSS width (browser_width) like browser-steps.js - handles device-scale-factor != 1.
|
||||
@@ -36,8 +42,8 @@ $(document).ready(() => {
|
||||
$error.toggle(which === 'error');
|
||||
const ready = which === 'ready';
|
||||
$wrapper.toggle(ready);
|
||||
$xpathRow.toggle(ready && $byElement.is(':checked'));
|
||||
$clear.toggle(ready && $byElement.is(':checked'));
|
||||
$xpathRow.toggle(ready && selectionEnabled());
|
||||
$clear.toggle(ready && selectionEnabled());
|
||||
}
|
||||
|
||||
function fetchSnapshot() {
|
||||
@@ -53,7 +59,9 @@ $(document).ready(() => {
|
||||
|
||||
$.ajax({
|
||||
url: add_watch_snapshot_url,
|
||||
data: {url: url},
|
||||
// Preview with the browser picked in the list - that same browser is what
|
||||
// gets saved on the watch, so what you see here is what it will check with.
|
||||
data: {url: url, fetch_backend: $('input[name="fetch_backend"]:checked').val() || ''},
|
||||
dataType: 'json',
|
||||
}).done((data) => {
|
||||
showState('ready');
|
||||
|
||||
@@ -54,10 +54,30 @@
|
||||
{{ render_simple_field(form.processor) }}
|
||||
</div>
|
||||
|
||||
<div class="add-watch-option-group" id="quick-watch-fetch-backend">
|
||||
{# Rendered by hand rather than with render_field(): the system-default entry
|
||||
is listed for explanation and has to render disabled, and WTForms can't
|
||||
carry a per-option disabled attribute through a RadioField's choices. #}
|
||||
<span class="label"><label>{{ form.fetch_backend.label.text }}</label></span>
|
||||
<ul>
|
||||
{%- for browser in form.fetch_backend -%}
|
||||
{%- set unusable = browser.data in unusable_browsers -%}
|
||||
<li{% if unusable %} class="unusable" title="{{ _('%(browser)s cannot render a live preview, choose a browser below', browser=system_default_browser) }}"{% endif %}>
|
||||
{{ browser(disabled=true) if unusable else browser() }}{{ browser.label }}
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="add-watch-option-group" id="by-element-toggle-group">
|
||||
{%- if llm_configured -%}
|
||||
<label class="pure-checkbox" for="by-element-toggle">
|
||||
<input type="checkbox" id="by-element-toggle"> {{ _('Select by element') }}
|
||||
</label>
|
||||
{%- endif -%}
|
||||
{# No LLM intent available? Then narrowing by element is the only thing this
|
||||
page can do - selection is always on, so nothing is labelled or offered
|
||||
here, just the hint on how to use the preview. #}
|
||||
<span class="pure-form-message-inline">{{ _('Hover & click the preview to watch just one part of the page.') }}</span>
|
||||
<a id="clear-selector" class="pure-button button-secondary button-xsmall" style="display: none;">{{ _('Clear selection') }}</a>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +91,13 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
processor = request.form.get('processor', processors.get_default_processor())
|
||||
llm_intent = request.form.get('llm_intent', '').strip()
|
||||
extras = {'paused': add_paused, 'processor': processor}
|
||||
|
||||
# Browser picked on the Add Watch page (validated against the live-preview capable
|
||||
# list by the form). Absent from the watch-list quick-add, which leaves the new
|
||||
# watch on the system default as before. A snapshot that recorded its own browser
|
||||
# overrides this inside make_temporary_watch_active_watch().
|
||||
if form.fetch_backend.data:
|
||||
extras['fetch_backend'] = form.fetch_backend.data
|
||||
if llm_intent:
|
||||
extras['llm_intent'] = llm_intent
|
||||
|
||||
|
||||
@@ -487,6 +487,39 @@ class ValidateContentFetcherIsReady(object):
|
||||
# raise ValidationError(message % (field.data, e))
|
||||
|
||||
|
||||
class ValidateKnownContentFetcher(object):
|
||||
"""The posted fetch_backend has to name a fetcher this install actually has.
|
||||
|
||||
Deliberately *not* a live-preview capability check. This validator sits on the
|
||||
shared quick-add form, whose POST endpoint is also how the watch list (and tests,
|
||||
and scripts) add a watch with any legal backend - 'html_requests' included. Which
|
||||
browsers the Add-Watch page *offers* is a rendering decision (see the add_watch_ui
|
||||
blueprint's browser_config), and whether one can render a live preview is enforced
|
||||
where that matters, in /snapshot.
|
||||
|
||||
Optional: no value posted means "leave it on the system default", as before.
|
||||
"""
|
||||
|
||||
def __init__(self, message=None):
|
||||
self.message = message
|
||||
|
||||
def __call__(self, form, field):
|
||||
from flask import current_app
|
||||
from changedetectionio import content_fetchers
|
||||
|
||||
if not field.data:
|
||||
return
|
||||
|
||||
allowed = {'system'} | {name for name, _description in content_fetchers.available_fetchers()}
|
||||
datastore = current_app.config.get('DATASTORE')
|
||||
if datastore:
|
||||
allowed |= {value for value, _label in datastore.extra_browsers}
|
||||
|
||||
if field.data not in allowed:
|
||||
logger.warning(f"Rejected unknown fetch_backend {field.data!r} - known: {sorted(allowed)}")
|
||||
raise ValidationError(self.message or gettext("Unknown fetch method."))
|
||||
|
||||
|
||||
class ValidateNotificationBodyAndTitleWhenURLisSet(object):
|
||||
"""
|
||||
Validates that they entered something in both notification title+body when the URL is set
|
||||
@@ -784,11 +817,41 @@ class ValidateStartsWithRegex(object):
|
||||
if not self.pattern.match(stripped):
|
||||
raise ValidationError(self.message or _l("Invalid value."))
|
||||
|
||||
def visual_browser_choices():
|
||||
"""Browsers that can render the Add-Watch live preview, as RadioField choices.
|
||||
|
||||
Lazy import (the add_watch_ui blueprint imports this module) and empty outside an
|
||||
app context, because WTForms evaluates a choices callable on field construction.
|
||||
"""
|
||||
from flask import current_app, has_app_context
|
||||
from changedetectionio.blueprint.add_watch_ui import browser_config
|
||||
|
||||
if not has_app_context():
|
||||
return []
|
||||
datastore = current_app.config.get('DATASTORE')
|
||||
return browser_config.radio_choices(datastore) if datastore else []
|
||||
|
||||
|
||||
class quickWatchForm(Form):
|
||||
url = StringField('URL', validators=[validateURL()])
|
||||
tags = StringTagUUID(_l('Group tag'), validators=[validators.Optional()])
|
||||
watch_submit_button = SubmitField(_l('Watch'), render_kw={"class": "pure-button pure-button-primary"})
|
||||
processor = RadioField(_l('Processor'), choices=lambda: processors.available_processors(), default=processors.get_default_processor)
|
||||
# Only the Add-Watch page renders this; the watch-list quick-add posts nothing, which
|
||||
# leaves the new watch on 'system' exactly as before.
|
||||
#
|
||||
# A radio list rather than a dropdown: fetcher descriptions run long (they include the
|
||||
# driver URL) and a wrapping label reads fine in a narrow pane, where a <select> would
|
||||
# either overflow or need truncating.
|
||||
#
|
||||
# choices is only what the Add-Watch page *offers* (browsers that can render a live
|
||||
# preview), so validate_choice has to stay off: this same endpoint legitimately receives
|
||||
# any installed backend from the watch-list quick-add, and pre_validate() would reject
|
||||
# e.g. 'html_requests' for not being in the offered list.
|
||||
fetch_backend = RadioField(_l('Browser'),
|
||||
choices=visual_browser_choices,
|
||||
validate_choice=False,
|
||||
validators=[ValidateKnownContentFetcher()])
|
||||
edit_and_watch_submit_button = SubmitField(_l('Edit > Watch'), render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
|
||||
|
||||
@@ -119,13 +119,17 @@ window.initVisualSelector = function (opts) {
|
||||
c = $selectorCanvasElem[0];
|
||||
xctx = c.getContext("2d");
|
||||
ctx = c.getContext("2d");
|
||||
// Drop any handlers left over from a previous load BEFORE (re)building the
|
||||
// selector - applyElementData() runs synchronously for inline xpathData and
|
||||
// binds the element handlers itself, so unbinding afterwards would silently
|
||||
// kill hover/click selection on the add-watch snapshot path.
|
||||
$selectorCanvasElem.off("mousemove mousedown mouseleave");
|
||||
if (source.xpathData) {
|
||||
// Inline data (add-watch snapshot) - no extra round trip needed
|
||||
applyElementData(source.xpathData);
|
||||
} else {
|
||||
fetchData();
|
||||
}
|
||||
$selectorCanvasElem.off("mousemove mousedown");
|
||||
});
|
||||
|
||||
// data: URIs must be used verbatim; real URLs get a cache-buster
|
||||
|
||||
@@ -169,6 +169,54 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// Browser picker: fetcher descriptions are long (they carry the driver URL), so this is
|
||||
// a radio list whose labels wrap inside the narrow pane instead of a <select> that would
|
||||
// overflow it. The system-default entry is listed even when it can't render a preview.
|
||||
#quick-watch-fetch-backend {
|
||||
ul {
|
||||
margin: 0.35rem 0 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5em;
|
||||
padding: 0.15rem 0;
|
||||
|
||||
input[type="radio"] {
|
||||
flex: 0 0 auto;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
label {
|
||||
// Wrap rather than push the pane wider
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
}
|
||||
|
||||
li.unusable {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
|
||||
label {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.pure-form-message-inline {
|
||||
display: block;
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
#by-element-toggle-group {
|
||||
.pure-form-message-inline {
|
||||
display: block;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -35,7 +35,8 @@ except ImportError:
|
||||
from ..processors import get_custom_watch_obj_for_processor, find_processors
|
||||
|
||||
# Import the base class and helpers
|
||||
from .file_saving_datastore import FileSavingDataStore, load_all_watches, load_all_tags, save_json_atomic
|
||||
from .file_saving_datastore import (FileSavingDataStore, load_all_watches, load_all_tags, load_watch_from_file,
|
||||
save_json_atomic)
|
||||
from .updates import DatastoreUpdatesMixin
|
||||
|
||||
# Because the server will run as a daemon and wont know the URL for notification links when firing off a notification
|
||||
@@ -852,10 +853,26 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
last-screenshot.png + elements.deflate in final on-disk format) is renamed into
|
||||
place as the new watch's data_dir - no re-fetch, no copy. If the temp_uuid is
|
||||
missing/expired/invalid we fall back to a normal add_watch() so the UI still works.
|
||||
|
||||
Settings the snapshot recorded for itself in its own watch.json (currently just
|
||||
fetch_backend - the browser that rendered the preview) win over the posted form,
|
||||
because they describe what actually fetched. add_watch() -> commit() rewrites that
|
||||
file in full once the directory has been promoted.
|
||||
"""
|
||||
seed_dir = self.get_temporary_watch_dir(temp_uuid)
|
||||
if not (seed_dir and os.path.isdir(seed_dir)):
|
||||
seed_dir = None
|
||||
|
||||
extras = dict(extras or {})
|
||||
seed_watch_json = os.path.join(seed_dir, "watch.json") if seed_dir else None
|
||||
# isfile() first - a snapshot parked before this file existed is normal, not an error
|
||||
if seed_watch_json and os.path.isfile(seed_watch_json):
|
||||
seed_watch = load_watch_from_file(seed_watch_json, temp_uuid, self.rehydrate_entity)
|
||||
if seed_watch and seed_watch.get('fetch_backend'):
|
||||
extras['fetch_backend'] = seed_watch.get('fetch_backend')
|
||||
logger.debug(f"Promoting temporary watch {temp_uuid} with its recorded "
|
||||
f"fetch_backend '{extras['fetch_backend']}'")
|
||||
|
||||
return self.add_watch(url=url, tag=tag, extras=extras, seed_data_dir=seed_dir)
|
||||
|
||||
def cleanup_temporary_watches(self, ttl_seconds=3600):
|
||||
|
||||
@@ -48,6 +48,16 @@ def test_llm_features_disabled_hides_ui(client, live_server, monkeypatch):
|
||||
assert b'name="llm_intent"' not in res.data
|
||||
assert b'name="llm_change_summary"' not in res.data
|
||||
|
||||
# 4. Add-watch page - with no "what matters" intent box there is nothing to choose
|
||||
# between, so element selection is always on and the opt-in checkbox is not rendered.
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_index'))
|
||||
assert res.status_code == 200
|
||||
_llm_markers_absent(res.data, where='add-watch-ui')
|
||||
assert b'name="llm_intent"' not in res.data
|
||||
assert b'id="by-element-toggle"' not in res.data
|
||||
assert b'for="by-element-toggle"' not in res.data # no label either - it isn't a choice
|
||||
assert b'id="by-element-toggle-group"' in res.data
|
||||
|
||||
|
||||
def test_llm_features_enabled_by_default(client, live_server, monkeypatch):
|
||||
"""When LLM_FEATURES_DISABLED is unset, the AI / LLM surfaces are still rendered."""
|
||||
@@ -60,3 +70,11 @@ def test_llm_features_enabled_by_default(client, live_server, monkeypatch):
|
||||
assert res.status_code == 200
|
||||
# The AI / LLM settings tab anchor should be present when not disabled
|
||||
assert b'href="#ai"' in res.data
|
||||
|
||||
# With an LLM configured, the Add-watch page offers both ways of narrowing a watch,
|
||||
# so "Select by element" goes back to being an opt-in checkbox.
|
||||
monkeypatch.setenv('LLM_MODEL', 'gemini/gemini-2.5-flash')
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_index'))
|
||||
assert res.status_code == 200
|
||||
assert b'name="llm_intent"' in res.data
|
||||
assert b'id="by-element-toggle"' in res.data
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The Add-Watch page's browser picker.
|
||||
|
||||
The live preview needs a browser that can render screenshots + xpath element data, and
|
||||
whatever rendered the preview is what the saved watch must check with - otherwise you
|
||||
visually pick an element with Chrome and the watch then re-checks it with the plain HTTP
|
||||
client. These tests cover the offered list, the server-side gate, and that the browser
|
||||
which rendered a parked snapshot wins over the posted form.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import url_for
|
||||
|
||||
|
||||
def _datastore(client):
|
||||
return client.application.config.get('DATASTORE')
|
||||
|
||||
|
||||
def test_browser_picker_lists_only_live_preview_capable(client, live_server, measure_memory_usage, datastore_path, monkeypatch):
|
||||
"""Capable browsers are offered; the plain HTTP client never is."""
|
||||
from changedetectionio.blueprint.add_watch_ui import browser_config
|
||||
|
||||
# html_webdriver is only preview-capable when it resolves to playwright/puppeteer, so
|
||||
# pretend it does (a WEBDRIVER_URL-only deployment resolves it to selenium, which can't).
|
||||
monkeypatch.setattr(browser_config, 'is_visual_capable',
|
||||
lambda name, datastore: name == 'html_webdriver')
|
||||
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_index'))
|
||||
assert res.status_code == 200
|
||||
assert b'name="fetch_backend"' in res.data
|
||||
assert b'value="html_webdriver"' in res.data
|
||||
# The plain HTTP client can't render a preview, so it is not an option
|
||||
assert b'value="html_requests"' not in res.data
|
||||
# The system default resolves to html_requests here, so it is listed but disabled
|
||||
assert b'value="system"' in res.data
|
||||
assert b'disabled' in res.data
|
||||
|
||||
|
||||
def test_browser_picker_disables_incapable_system_default(client, live_server, measure_memory_usage, datastore_path, monkeypatch):
|
||||
"""With nothing capable at all, 'system' is still shown (disabled) rather than vanishing."""
|
||||
from changedetectionio.blueprint.add_watch_ui import browser_config
|
||||
monkeypatch.setattr(browser_config, 'is_visual_capable', lambda name, datastore: False)
|
||||
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_index'))
|
||||
assert res.status_code == 200
|
||||
assert b'name="fetch_backend"' in res.data
|
||||
assert b'value="system"' in res.data
|
||||
assert b'disabled' in res.data
|
||||
# ...and nothing else is offered
|
||||
assert b'value="html_' not in res.data
|
||||
|
||||
|
||||
def test_snapshot_refuses_browser_that_cannot_preview(client, live_server, measure_memory_usage, datastore_path, monkeypatch):
|
||||
"""/snapshot won't spend a fetch on a browser that produces no screenshot."""
|
||||
from changedetectionio.blueprint.add_watch_ui import browser_config
|
||||
monkeypatch.setattr(browser_config, 'is_visual_capable', lambda name, datastore: False)
|
||||
|
||||
# Nothing capable, and no explicit browser asked for -> nothing to preview with
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_snapshot', url='https://example.com'))
|
||||
assert res.status_code == 400
|
||||
assert b'No interactive browser' in res.data
|
||||
|
||||
# Explicitly asking for a browser that can't preview is refused just the same
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_snapshot', url='https://example.com',
|
||||
fetch_backend='html_requests'))
|
||||
assert res.status_code == 400
|
||||
|
||||
# A made-up name never resolves to a capable fetcher either (real capability lookup here)
|
||||
monkeypatch.undo()
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_snapshot', url='https://example.com',
|
||||
fetch_backend='../../etc/passwd'))
|
||||
assert res.status_code == 400
|
||||
res = client.get(url_for('add_watch_ui.add_watch_ui_snapshot', url='https://example.com',
|
||||
fetch_backend='os'))
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_submit_rejects_unknown_fetcher(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""A posted browser is checked server side, so a doctored form can't pin a junk fetcher."""
|
||||
datastore = _datastore(client)
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
for bad in ('os', 'html_requests\n', '{{7*7}}', '../../etc/passwd', 'html_nope'):
|
||||
before = len(datastore.data['watching'])
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "fetch_backend": bad},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert res.status_code == 200
|
||||
assert b"Watch added" not in res.data
|
||||
assert len(datastore.data['watching']) == before, f"{bad!r} should not have created a watch"
|
||||
|
||||
|
||||
def test_submit_still_accepts_any_installed_fetcher(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""This endpoint is shared with the watch-list quick-add, which may legitimately name a
|
||||
backend that can't render a live preview (restock tests add watches with html_requests) -
|
||||
only *offering* it on the Add-Watch page is restricted, not adding a watch with it."""
|
||||
datastore = _datastore(client)
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "processor": "restock_diff", "fetch_backend": "html_requests"},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
uuid = next(iter(datastore.data['watching']))
|
||||
assert datastore.data['watching'][uuid].get('fetch_backend') == 'html_requests'
|
||||
|
||||
|
||||
def test_submit_saves_chosen_browser(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""The picked browser lands on the watch instead of leaving it on 'system'."""
|
||||
datastore = _datastore(client)
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "fetch_backend": "html_webdriver"},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
uuid = next(iter(datastore.data['watching']))
|
||||
assert datastore.data['watching'][uuid].get('fetch_backend') == 'html_webdriver'
|
||||
|
||||
|
||||
def test_parked_snapshot_browser_wins_over_form(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""The browser that actually rendered the snapshot is the one the watch keeps.
|
||||
|
||||
/snapshot records it in the temporary watch's own watch.json; promoting the snapshot
|
||||
must prefer that over whatever the form posted, since the parked screenshot and
|
||||
element data came from it.
|
||||
"""
|
||||
datastore = _datastore(client)
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
temp_uuid = '11111111-2222-3333-4444-555555555555'
|
||||
temp_dir = datastore.get_temporary_watch_dir(temp_uuid)
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
with open(os.path.join(temp_dir, "watch.json"), 'w') as f:
|
||||
json.dump({"fetch_backend": "html_webdriver"}, f)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "fetch_backend": "html_requests", "temporary_uuid": temp_uuid},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
uuid = next(iter(datastore.data['watching']))
|
||||
assert datastore.data['watching'][uuid].get('fetch_backend') == 'html_webdriver'
|
||||
# The snapshot dir was promoted into the watch, not left behind
|
||||
assert not os.path.isdir(temp_dir)
|
||||
|
||||
|
||||
def test_watchlist_quick_add_is_unaffected(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""The watch-list quick-add posts no browser at all - that still means 'system'."""
|
||||
datastore = _datastore(client)
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
uuid = next(iter(datastore.data['watching']))
|
||||
assert datastore.data['watching'][uuid].get('fetch_backend') in (None, '', 'system')
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Přidejte nové sledování zjišťování změn webové stránky"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "V současné době:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Vyberte podle prvku"
|
||||
@@ -2604,10 +2619,6 @@ msgstr "Štítek"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3002,6 +3013,10 @@ msgstr "Minuty"
|
||||
msgid "Seconds"
|
||||
msgstr "Sekundy"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Při použití adresy URL oznámení je vyžadováno tělo a název oznámení"
|
||||
@@ -3052,6 +3067,10 @@ msgstr "Monitorovat"
|
||||
msgid "Processor"
|
||||
msgstr "Procesor"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Upravit > Monitorovat"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Fügen Sie eine neue Überwachung zur Erkennung von Webseitenänderungen hinzu"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "Momentan:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Nach Element auswählen"
|
||||
@@ -2649,10 +2664,6 @@ msgstr "Tag"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3051,6 +3062,10 @@ msgstr "Minuten"
|
||||
msgid "Seconds"
|
||||
msgstr "Sekunden"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Benachrichtigungstext und Titel sind erforderlich, wenn eine Benachrichtigungs-URL verwendet wird"
|
||||
@@ -3101,6 +3116,10 @@ msgstr "Überwachen"
|
||||
msgid "Processor"
|
||||
msgstr "Prozessor"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr ""
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr ""
|
||||
@@ -2598,10 +2613,6 @@ msgstr ""
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -2994,6 +3005,10 @@ msgstr ""
|
||||
msgid "Seconds"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr ""
|
||||
@@ -3044,6 +3059,10 @@ msgstr ""
|
||||
msgid "Processor"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr ""
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr ""
|
||||
@@ -2598,10 +2613,6 @@ msgstr ""
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -2994,6 +3005,10 @@ msgstr ""
|
||||
msgid "Seconds"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr ""
|
||||
@@ -3044,6 +3059,10 @@ msgstr ""
|
||||
msgid "Processor"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
@@ -14,6 +14,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Agregar un nuevo monitor de detección de cambios en páginas web"
|
||||
@@ -39,6 +49,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "Actualmente:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Seleccionar por elemento"
|
||||
@@ -2665,10 +2680,6 @@ msgstr "Etiqueta"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3067,6 +3078,10 @@ msgstr "Minutos"
|
||||
msgid "Seconds"
|
||||
msgstr "Segundos"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Se requiere el cuerpo y el título de la notificación cuando se utiliza una URL de notificación"
|
||||
@@ -3117,6 +3132,10 @@ msgstr "Monitor"
|
||||
msgid "Processor"
|
||||
msgstr "Procesador"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Editar > Ver"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Ajouter une nouvelle surveillance de détection de changement de page Web"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "Actuellement:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Sélectionner par élément"
|
||||
@@ -2609,10 +2624,6 @@ msgstr "Étiqueter"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3007,6 +3018,10 @@ msgstr "Minutes"
|
||||
msgid "Seconds"
|
||||
msgstr "secondes"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Le corps et le titre de la notification sont requis lorsqu'une URL de notification est utilisée"
|
||||
@@ -3057,6 +3072,10 @@ msgstr "Moniteur"
|
||||
msgid "Processor"
|
||||
msgstr "Processeur"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Modifier > Surveiller"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Aggiungi un nuovo monitoraggio modifiche pagina web"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr ""
|
||||
@@ -2600,10 +2615,6 @@ msgstr "Tag"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -2996,6 +3007,10 @@ msgstr "Minuti"
|
||||
msgid "Seconds"
|
||||
msgstr "Secondi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Corpo e titolo notifica sono richiesti quando si usa un URL di notifica"
|
||||
@@ -3046,6 +3061,10 @@ msgstr "Monitora"
|
||||
msgid "Processor"
|
||||
msgstr "Processore"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Modifica > Monitora"
|
||||
|
||||
@@ -19,6 +19,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "新しいウェブページ変更検知ウォッチを追加"
|
||||
@@ -44,6 +54,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "現在:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "要素で選択"
|
||||
@@ -2617,10 +2632,6 @@ msgstr "タグ"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3013,6 +3024,10 @@ msgstr "分"
|
||||
msgid "Seconds"
|
||||
msgstr "秒"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "通知URLを使用する場合は通知本文とタイトルが必要です"
|
||||
@@ -3063,6 +3078,10 @@ msgstr "ウォッチ"
|
||||
msgid "Processor"
|
||||
msgstr "プロセッサー"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "編集 > ウォッチ"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "새 웹페이지 변경 감지 모니터링 추가"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "현재:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "요소별로 선택"
|
||||
@@ -2608,10 +2623,6 @@ msgstr "태그"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3004,6 +3015,10 @@ msgstr "분"
|
||||
msgid "Seconds"
|
||||
msgstr "초"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "알림 URL을 사용하는 경우 알림 본문 및 제목이 필요합니다."
|
||||
@@ -3054,6 +3069,10 @@ msgstr "모니터링"
|
||||
msgid "Processor"
|
||||
msgstr "프로세서"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "편집 > 모니터링"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.55.8\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-08-24 21:15+0200\n"
|
||||
"POT-Creation-Date: 2026-08-25 14:13+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -17,6 +17,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr ""
|
||||
@@ -42,6 +52,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr ""
|
||||
@@ -2597,10 +2612,6 @@ msgstr ""
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -2993,6 +3004,10 @@ msgstr ""
|
||||
msgid "Seconds"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr ""
|
||||
@@ -3043,6 +3058,10 @@ msgstr ""
|
||||
msgid "Processor"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
@@ -14,6 +14,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Dodaj obserwację wykrywającą zmiany na nowej stronie internetowej"
|
||||
@@ -39,6 +49,11 @@ msgstr "Pobieranie zrzutu ekranu i informacji o elemencie…"
|
||||
msgid "Currently:"
|
||||
msgstr "Obecnie:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Wybierz według elementu"
|
||||
@@ -2753,10 +2768,6 @@ msgstr "Etykieta"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr "Ustaw przeglądarkę / metodę pobierania dla zaznaczonych obserwacji"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr "Przeglądarka"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr "Zastosuj"
|
||||
@@ -3153,6 +3164,10 @@ msgstr "Minut"
|
||||
msgid "Seconds"
|
||||
msgstr "Sekundy"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "W przypadku korzystania z adresu URL powiadomienia należy podać treść i tytuł powiadomienia"
|
||||
@@ -3203,6 +3218,10 @@ msgstr "Obserwacja"
|
||||
msgid "Processor"
|
||||
msgstr "Procesor"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr "Przeglądarka"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Edycja > Obserwuj"
|
||||
|
||||
@@ -19,6 +19,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Adicionar um novo monitoramento de detecção de mudança de página"
|
||||
@@ -44,6 +54,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "Atualmente:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Selecionar por elemento"
|
||||
@@ -2646,10 +2661,6 @@ msgstr "Tag"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3044,6 +3055,10 @@ msgstr "Minutos"
|
||||
msgid "Seconds"
|
||||
msgstr "Segundos"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Corpo e Título da Notificação são obrigatórios quando uma URL de Notificação é usada"
|
||||
@@ -3094,6 +3109,10 @@ msgstr "Monitoramento"
|
||||
msgid "Processor"
|
||||
msgstr "Processador"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Editar > Monitoramento"
|
||||
|
||||
@@ -17,6 +17,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Добавить новый контроль обнаружения изменений веб-страницы"
|
||||
@@ -42,6 +52,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "В настоящее время:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Выбрать по элементу"
|
||||
@@ -2712,10 +2727,6 @@ msgstr "Ярлык"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3112,6 +3123,10 @@ msgstr "Минуты"
|
||||
msgid "Seconds"
|
||||
msgstr "Секунды"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Текст и заголовок уведомления требуются при использовании URL-адреса уведомления."
|
||||
@@ -3162,6 +3177,10 @@ msgstr "Смотреть"
|
||||
msgid "Processor"
|
||||
msgstr "Процессор"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Редактировать > Смотреть"
|
||||
|
||||
@@ -19,6 +19,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Yeni bir web sayfası değişiklik tespiti izleyicisi ekle"
|
||||
@@ -44,6 +54,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "Şu anda:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Öğeye göre seç"
|
||||
@@ -2651,10 +2666,6 @@ msgstr "Etiket"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3047,6 +3058,10 @@ msgstr "Dakika"
|
||||
msgid "Seconds"
|
||||
msgstr "Saniye"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Bir Bildirim URL'si kullanıldığında Bildirim Gövdesi ve Başlığı gereklidir"
|
||||
@@ -3097,6 +3112,10 @@ msgstr "İzleyici"
|
||||
msgid "Processor"
|
||||
msgstr "İşlemci"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Düzenle > İzleyici"
|
||||
|
||||
@@ -17,6 +17,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "Додати нове завдання відстеження змін веб-сторінки"
|
||||
@@ -42,6 +52,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "В даний час:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "Вибір за елементом"
|
||||
@@ -2630,10 +2645,6 @@ msgstr "Тег"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3026,6 +3037,10 @@ msgstr "Хвилини"
|
||||
msgid "Seconds"
|
||||
msgstr "Секунди"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "Тіло та заголовок сповіщення обов'язкові, якщо використовується URL сповіщення"
|
||||
@@ -3076,6 +3091,10 @@ msgstr "Завдання"
|
||||
msgid "Processor"
|
||||
msgstr "Процесор"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "Редагувати > Завдання"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "新增网页变更监控"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "当前:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "按元素选择"
|
||||
@@ -2604,10 +2619,6 @@ msgstr "标签"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3000,6 +3011,10 @@ msgstr "分钟"
|
||||
msgid "Seconds"
|
||||
msgstr "秒"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "使用通知 URL 时,必须填写通知正文和标题"
|
||||
@@ -3050,6 +3065,10 @@ msgstr "监控项"
|
||||
msgid "Processor"
|
||||
msgstr "处理器"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "编辑 > 监控项"
|
||||
|
||||
@@ -18,6 +18,16 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/browser_config.py
|
||||
#, python-format
|
||||
msgid "System settings default (%(browser)s - no live preview)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
msgid "Add a new web page change detection watch"
|
||||
msgstr "新增網頁變更檢測任務"
|
||||
@@ -43,6 +53,11 @@ msgstr ""
|
||||
msgid "Currently:"
|
||||
msgstr "目前:"
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html
|
||||
#, python-format
|
||||
msgid "%(browser)s cannot render a live preview, choose a browser below"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/add_watch_ui/templates/add-watch-ui.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Select by element"
|
||||
msgstr "按元素選擇"
|
||||
@@ -2604,10 +2619,6 @@ msgstr "標籤"
|
||||
msgid "Set browser / fetch method for selected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
@@ -3000,6 +3011,10 @@ msgstr "分鐘"
|
||||
msgid "Seconds"
|
||||
msgstr "秒"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Unknown fetch method."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body and Title is required when a Notification URL is used"
|
||||
msgstr "使用通知 URL 時,必須填寫通知內容與標題"
|
||||
@@ -3050,6 +3065,10 @@ msgstr "監測任務"
|
||||
msgid "Processor"
|
||||
msgstr "處理器"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/forms.py
|
||||
msgid "Browser"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Edit > Watch"
|
||||
msgstr "編輯 > 監測任務"
|
||||
|
||||
Reference in New Issue
Block a user