diff --git a/changedetectionio/blueprint/ui/browser_config.py b/changedetectionio/blueprint/ui/browser_config.py index 13c137c9..8e4e2c65 100644 --- a/changedetectionio/blueprint/ui/browser_config.py +++ b/changedetectionio/blueprint/ui/browser_config.py @@ -42,6 +42,19 @@ def _base_fetchers(datastore): return out +def _autocomplete_choices(): + """(locales, timezones) for the datalist autocompletes - same sources the FetcherConfig + validators use (babel CLDR + stdlib zoneinfo).""" + from zoneinfo import available_timezones + timezones = sorted(available_timezones()) + try: + from babel.localedata import locale_identifiers + locales = sorted({lid.replace('_', '-') for lid in locale_identifiers()}) + except Exception: + locales = ['en-US', 'en-GB', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'ja-JP', 'zh-CN', 'pt-BR', 'nl-NL'] + return locales, timezones + + def _caps_for(base_name): """Capability dict for an engine, so the form renders only the fields it can honour (e.g. html_requests has no screenshots -> no viewport/locale/timezone).""" @@ -131,8 +144,10 @@ def construct_blueprint(datastore: ChangeDetectionStore): flash(gettext("Browser added")) return redirect(url_for('ui.browser_config.browsers_overview')) + locale_choices, timezone_choices = _autocomplete_choices() return render_template("browser-config-form.html", form=form, mode='add', base_fetcher=base_fetcher, base_label=base_label, caps=caps.model_dump(), + locale_choices=locale_choices, timezone_choices=timezone_choices, form_action=url_for('ui.browser_config.browser_config_add', base_fetcher=base_fetcher)) @browser_config_blueprint.route("/browsers/edit/", methods=['GET', 'POST']) @@ -173,10 +188,12 @@ def construct_blueprint(datastore: ChangeDetectionStore): else: form = BrowserOptionsForm(data=_entry_to_formdata(entry) if entry else {'label': base_label}) + locale_choices, timezone_choices = _autocomplete_choices() return render_template("browser-config-form.html", form=form, mode='edit', config_id=config_id, is_builtin=is_builtin, base_fetcher=base, base_label=base_label, caps=_caps_for(base) if base else {}, + locale_choices=locale_choices, timezone_choices=timezone_choices, form_action=url_for('ui.browser_config.browser_config_edit', config_id=config_id)) @browser_config_blueprint.route("/browsers/remove/", methods=['POST']) diff --git a/changedetectionio/blueprint/ui/form_browseroptions.py b/changedetectionio/blueprint/ui/form_browseroptions.py index e5df34d2..1b165c4f 100644 --- a/changedetectionio/blueprint/ui/form_browseroptions.py +++ b/changedetectionio/blueprint/ui/form_browseroptions.py @@ -56,9 +56,9 @@ class BrowserOptionsForm(Form): validators.Optional(), validators.NumberRange(min=1, max=10000)]) locale = StringField(_l('Locale'), validators=[validators.Optional(), validators.Length(max=35)], - render_kw={"placeholder": "de-DE"}) + render_kw={"placeholder": "de-DE", "list": "locale-datalist", "autocomplete": "off"}) timezone_id = StringField(_l('Timezone'), validators=[validators.Optional(), validators.Length(max=64)], - render_kw={"placeholder": "Europe/Berlin"}) + render_kw={"placeholder": "Europe/Berlin", "list": "timezone-datalist", "autocomplete": "off"}) screenshot_format = SelectField(_l('Screenshot format'), choices=SCREENSHOT_FORMATS, default='JPEG') diff --git a/changedetectionio/blueprint/ui/templates/_browser_config_fields.html b/changedetectionio/blueprint/ui/templates/_browser_config_fields.html index b0cbad8b..c1ebca5c 100644 --- a/changedetectionio/blueprint/ui/templates/_browser_config_fields.html +++ b/changedetectionio/blueprint/ui/templates/_browser_config_fields.html @@ -44,11 +44,17 @@ {{ _('Language & timezone') }}
{{ render_field(form.locale) }} - {{ _('e.g. de-DE, en-GB.')|safe }} + + {% for l in locale_choices or [] %}{% endfor %} + + {{ _('Start typing to search - e.g. de-DE, en-GB.')|safe }}
{{ render_field(form.timezone_id) }} - {{ _('e.g. Europe/Berlin.')|safe }} + + {% for tz in timezone_choices or [] %}{% endfor %} + + {{ _('Start typing to search - e.g. Europe/Berlin.')|safe }}
{% endif %} diff --git a/changedetectionio/content_fetchers/playwright_builtin.py b/changedetectionio/content_fetchers/playwright_builtin.py index e396b3d1..cf0f5e7c 100644 --- a/changedetectionio/content_fetchers/playwright_builtin.py +++ b/changedetectionio/content_fetchers/playwright_builtin.py @@ -10,6 +10,7 @@ Only registered when the playwright library is importable (see register_builtin_ Cross-platform temp isolation: a per-fetch temp dir with best-effort cleanup (tolerates the Windows file-lock case). Browser processes are separate OS processes reclaimed on close(). """ +import os import shutil import tempfile @@ -37,6 +38,9 @@ class fetcher(playwright_fetcher): async def _get_browser(self, browser_type): # Dedicated per-fetch dir; tempfile respects TMPDIR/%TEMP% so it's cross-platform. self._local_tmp_dir = tempfile.mkdtemp(prefix='cdio-playwright-') + engine = self._resolve_browser_type_name() + logger.info(f"html_playwright_builtin: launching LOCAL headless '{engine}' for watch " + f"{getattr(self, 'watch_uuid', None)} - temp/downloads dir: {self._local_tmp_dir}") return await browser_type.launch(headless=True, downloads_path=self._local_tmp_dir) async def quit(self, watch=None): @@ -45,11 +49,20 @@ class fetcher(playwright_fetcher): try: await super().quit(watch=watch) finally: + tmp = self._local_tmp_dir bc = getattr(self, 'browser_config', None) delete = True if bc is None else bool(getattr(bc, 'delete_created_files', True)) - if self._local_tmp_dir and delete: + if not tmp: + logger.info("html_playwright_builtin: no local temp dir to clean up") + elif delete: + existed = os.path.isdir(tmp) # ignore_errors tolerates Windows file locks / an already-removed dir. - shutil.rmtree(self._local_tmp_dir, ignore_errors=True) + shutil.rmtree(tmp, ignore_errors=True) + logger.info(f"html_playwright_builtin: cleaned up temp browser dir {tmp} " + f"(existed={existed}, removed={not os.path.isdir(tmp)})") + else: + logger.info(f"html_playwright_builtin: keeping temp browser dir {tmp} " + f"(delete_created_files is off)") self._local_tmp_dir = None diff --git a/changedetectionio/processors/base.py b/changedetectionio/processors/base.py index d4c16a53..e71c5f37 100644 --- a/changedetectionio/processors/base.py +++ b/changedetectionio/processors/base.py @@ -222,6 +222,13 @@ class difference_detection_processor(): # Inject the resolved per-watch browser behaviour; fetchers that read it apply what # they can, others ignore it. Never None so consumers can read attributes freely. self.fetcher.browser_config = browser_config + try: + logger.debug( + f"Watch {self.watch.get('uuid')} fetch: backend='{prefer_fetch_backend}' " + f"browser_config={browser_config.model_dump() if browser_config else None}" + ) + except Exception as e: + logger.debug(f"Could not log browser_config: {e}") if self.watch.has_browser_steps: self.fetcher.browser_steps = browser_steps_get_valid_steps(self.watch.get('browser_steps', []))