UI - Fixing lots of actions that should be a POST style action which led to a 404 (#4370)

This commit is contained in:
dgtlmoon
2026-09-04 10:56:23 +02:00
committed by GitHub
parent 050172129e
commit e0fb224d41
33 changed files with 99 additions and 54 deletions
@@ -14,7 +14,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q: PriorityQueue
price_data_follower_blueprint = Blueprint('price_data_follower', __name__)
@price_data_follower_blueprint.route("/<uuid_str:uuid>/accept", methods=['GET'])
@price_data_follower_blueprint.route("/<uuid_str:uuid>/accept", methods=['POST'])
@login_optionally_required
def accept(uuid):
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_ACCEPT
@@ -24,7 +24,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q: PriorityQueue
worker_pool.queue_item_async_safe(update_q, queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))
return redirect(url_for("watchlist.index"))
@price_data_follower_blueprint.route("/<uuid_str:uuid>/reject", methods=['GET'])
@price_data_follower_blueprint.route("/<uuid_str:uuid>/reject", methods=['POST'])
@login_optionally_required
def reject(uuid):
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_REJECT
@@ -278,7 +278,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return output
@settings_blueprint.route("/reset-api-key", methods=['GET'])
@settings_blueprint.route("/reset-api-key", methods=['POST'])
@login_optionally_required
def settings_reset_api_key():
secret = secrets.token_hex(16)
@@ -295,7 +295,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
logs=notification_debug_log if len(notification_debug_log) else ["Notification logs are empty - no notifications sent yet."])
return output
@settings_blueprint.route("/toggle-all-paused", methods=['GET'])
@settings_blueprint.route("/toggle-all-paused", methods=['POST'])
@login_optionally_required
def toggle_all_paused():
current_state = datastore.data['settings']['application'].get('all_paused', False)
@@ -309,7 +309,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return redirect(url_for('watchlist.index'))
@settings_blueprint.route("/toggle-all-muted", methods=['GET'])
@settings_blueprint.route("/toggle-all-muted", methods=['POST'])
@login_optionally_required
def toggle_all_muted():
current_state = datastore.data['settings']['application'].get('all_muted', False)
@@ -208,7 +208,7 @@ nav
</div>
</div>
<div class="pure-control-group">
<a href="{{url_for('settings.settings_reset_api_key')}}" class="pure-button button-small button-cancel">{{ _('Regenerate API key') }}</a>
<button type="submit" formmethod="post" formnovalidate formaction="{{url_for('settings.settings_reset_api_key')}}" class="pure-button button-small button-cancel">{{ _('Regenerate API key') }}</button>
</div>
<div class="pure-control-group">
<h4>{{ _('Chrome Extension') }}</h4>
+1 -1
View File
@@ -62,7 +62,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return redirect(url_for('tags.tags_overview_page'))
@tags_blueprint.route("/mute/<uuid_str:uuid>", methods=['GET'])
@tags_blueprint.route("/mute/<uuid_str:uuid>", methods=['POST'])
@login_optionally_required
def mute(uuid):
tag = datastore.data['settings']['application']['tags'].get(uuid)
@@ -68,7 +68,10 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
{#-{{ loop.cycle('pure-table-odd', 'pure-table-even') }}-#}
<tr id="{{ uuid }}" class="">
<td class="watch-controls">
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}" aria-label="{{ _('Mute notifications') }}" title="{{ _('Mute notifications') }}"><i data-feather="{{ 'bell-off' if tag.notification_muted else 'bell' }}" class="icon icon-mute"></i></a>
<form method="POST" action="{{url_for('tags.mute', uuid=tag.uuid)}}" style="display: inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="bare-btn link-mute state-{{'on' if tag.notification_muted else 'off'}}" aria-label="{{ _('Mute notifications') }}" title="{{ _('Mute notifications') }}"><i data-feather="{{ 'bell-off' if tag.notification_muted else 'bell' }}" class="icon icon-mute"></i></button>
</form>
</td>
<td class="watch-count">{{ "{:,}".format(tag_count[uuid]) if uuid in tag_count else 0 }}</td>
<td class="title-col inline"> <a href="{{url_for('watchlist.index', tag=uuid) }}" class="watch-tag-list tag-{{ tag.title|sanitize_tag_class }}">{{ tag.title }}</a></td>
+1 -1
View File
@@ -407,7 +407,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_pool,
return redirect(url_for('watchlist.index'))
@ui_blueprint.route("/share-url/<uuid_str:uuid>", methods=['GET'])
@ui_blueprint.route("/share-url/<uuid_str:uuid>", methods=['POST'])
@login_optionally_required
def form_share_put_watch(uuid):
"""Given a watch UUID, upload the info and return a share-link
@@ -18,6 +18,24 @@ from changedetectionio.blueprint.watchlist.row_context import watch_row_context
def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMetaData):
watchlist_blueprint = Blueprint('watchlist', __name__, template_folder="templates")
@watchlist_blueprint.route("/toggle", methods=['POST'])
@login_optionally_required
def toggle():
op = request.args.get('op')
uuid = request.args.get('uuid')
watch = datastore.data['watching'].get(uuid)
if not watch:
flash(_('Watch not found'), 'error')
else:
if op == 'pause':
watch.toggle_pause()
elif op == 'mute':
watch.toggle_mute()
watch.commit()
return redirect(url_for('watchlist.index', tag=request.args.get('tag')))
@watchlist_blueprint.route("/", methods=['GET'])
@login_optionally_required
def index():
@@ -36,17 +54,6 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
if request.args.get('rss'):
return redirect(url_for('rss.feed', tag=active_tag_uuid))
op = request.args.get('op')
if op:
uuid = request.args.get('uuid')
if op == 'pause':
datastore.data['watching'][uuid].toggle_pause()
elif op == 'mute':
datastore.data['watching'][uuid].toggle_mute()
datastore.data['watching'][uuid].commit()
return redirect(url_for('watchlist.index', tag = active_tag_uuid))
# Sort by last_changed and add the uuid which is usually the key..
sorted_watches = []
active_processor = request.args.get('processor', '').strip()
@@ -35,10 +35,12 @@
<td class="inline checkbox-uuid" ><div><input name="uuids" type="checkbox" value="{{ watch.uuid}} " >{# <span class="counter-i">{{ loop.index+pagination.skip }}</span>#}</div></td>
<td class="inline watch-controls">
<div>
<a class="ajax-op state-off pause-toggle" data-op="pause" aria-label="{{ _('Pause checks') }}" title="{{ _('Pause checks') }}" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><i data-feather="pause" class="icon icon-pause"></i></a>
<a class="ajax-op state-on pause-toggle" data-op="pause" style="display: none" aria-label="{{ _('UnPause checks') }}" title="{{ _('UnPause checks') }}" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><i data-feather="play" class="icon icon-unpause"></i></a>
<a class="ajax-op state-off mute-toggle" data-op="mute" aria-label="{{ _('Mute notification') }}" title="{{ _('Mute notification') }}" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><i data-feather="bell" class="icon icon-mute"></i></a>
<a class="ajax-op state-on mute-toggle" data-op="mute" style="display: none" aria-label="{{ _('UnMute notification') }}" title="{{ _('UnMute notification') }}" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><i data-feather="bell-off" class="icon icon-mute"></i></a>
{%- set pause_action = url_for('watchlist.toggle', op='pause', uuid=watch.uuid, tag=active_tag_uuid) -%}
{%- set mute_action = url_for('watchlist.toggle', op='mute', uuid=watch.uuid, tag=active_tag_uuid) -%}
<button type="submit" formmethod="post" formaction="{{ pause_action }}" class="bare-btn ajax-op state-off pause-toggle" data-op="pause" aria-label="{{ _('Pause checks') }}" title="{{ _('Pause checks') }}"><i data-feather="pause" class="icon icon-pause"></i></button>
<button type="submit" formmethod="post" formaction="{{ pause_action }}" class="bare-btn ajax-op state-on pause-toggle" data-op="pause" style="display: none" aria-label="{{ _('UnPause checks') }}" title="{{ _('UnPause checks') }}"><i data-feather="play" class="icon icon-unpause"></i></button>
<button type="submit" formmethod="post" formaction="{{ mute_action }}" class="bare-btn ajax-op state-off mute-toggle" data-op="mute" aria-label="{{ _('Mute notification') }}" title="{{ _('Mute notification') }}"><i data-feather="bell" class="icon icon-mute"></i></button>
<button type="submit" formmethod="post" formaction="{{ mute_action }}" class="bare-btn ajax-op state-on mute-toggle" data-op="mute" style="display: none" aria-label="{{ _('UnMute notification') }}" title="{{ _('UnMute notification') }}"><i data-feather="bell-off" class="icon icon-mute"></i></button>
</div>
</td>
@@ -81,13 +83,13 @@
<div class="error-text" style="display:none;">{{ error_texts|safe }}</div>
{%- if watch['processor'] == 'text_json_diff' -%}
{%- if watch['has_ldjson_price_data'] and not watch['track_ldjson_price_data'] -%}
<div class="ldjson-price-track-offer">Switch to Restock & Price watch mode? <a href="{{url_for('price_data_follower.accept', uuid=watch.uuid)}}" class="pure-button button-xsmall">Yes</a> <a href="{{url_for('price_data_follower.reject', uuid=watch.uuid)}}" class="">No</a></div>
<div class="ldjson-price-track-offer">Switch to Restock & Price watch mode? <button type="submit" formmethod="post" formaction="{{url_for('price_data_follower.accept', uuid=watch.uuid)}}" class="pure-button button-xsmall">Yes</button> <button type="submit" formmethod="post" formaction="{{url_for('price_data_follower.reject', uuid=watch.uuid)}}" class="bare-btn bare-btn--link">No</button></div>
{%- endif -%}
{%- endif -%}
</div>
<div class="status-icons">
<a class="link-spread" href="{{url_for('ui.form_share_put_watch', uuid=watch.uuid)}}"><img src="{{url_for('static_content', group='images', filename='spread.svg')}}" class="status-icon icon icon-spread" title="{{ _('Create a link to share watch config with others') }}" ></a>
<button type="submit" formmethod="post" formaction="{{url_for('ui.form_share_put_watch', uuid=watch.uuid)}}" class="bare-btn link-spread"><img src="{{url_for('static_content', group='images', filename='spread.svg')}}" class="status-icon icon icon-spread" title="{{ _('Create a link to share watch config with others') }}" ></button>
{%- set effective_fetcher = watch.get_fetch_backend if watch.get_fetch_backend != "system" else system_default_fetcher -%}
{%- if effective_fetcher and ("html_webdriver" in effective_fetcher or "html_" in effective_fetcher or "extra_browser_" in effective_fetcher) -%}
{{ effective_fetcher|fetcher_status_icons }}
@@ -163,7 +165,9 @@
<div>
{%- set target_attr = ' target="' ~ watch.uuid ~ '"' if datastore.data['settings']['application']['ui'].get('open_diff_in_new_tab') else '' -%}
<a href="" class="already-in-queue-button recheck cdio-btn cdio-btn--primary cdio-btn--sm" style="display: none;" disabled="disabled"><i data-feather="clock"></i>{{ _('Queued') }}</a>
<a href="{{ url_for('ui.form_watch_checknow', uuid=watch.uuid, tag=request.args.get('tag')) }}" data-op='recheck' class="ajax-op recheck cdio-btn cdio-btn--primary cdio-btn--sm"><i data-feather="refresh-cw"></i>{{ _('Recheck') }}</a>
<button type="submit" formmethod="post"
formaction="{{ url_for('ui.form_watch_checknow', uuid=watch.uuid, tag=request.args.get('tag')) }}"
data-op='recheck' class="ajax-op recheck cdio-btn cdio-btn--primary cdio-btn--sm"><i data-feather="refresh-cw"></i>{{ _('Recheck') }}</button>
<a href="{{ url_for('ui.ui_edit.edit_page', uuid=watch.uuid, tag=active_tag_uuid)}}#general" class="cdio-btn cdio-btn--primary cdio-btn--sm">{{ _('Edit') }}</a>
<a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=watch.uuid)}}" {{target_attr}} class="cdio-btn cdio-btn--primary cdio-btn--sm history-link ai-history-btn" style="display: none;" data-uuid="{{ watch.uuid }}" data-summary-url="{{ url_for('ui.ui_diff.diff_llm_summary', uuid=watch.uuid) }}" data-processor-data-url="{{ url_for('ui.ui_diff.diff_history_page_processor_data', uuid=watch.uuid) }}"><span class="btn-label-history">{{ _('History') }}</span><span class="btn-label-summary">&#x2728; {{ _('Summary') }}</span></a>
<a href="{{ url_for('ui.ui_preview.preview_page', uuid=watch.uuid)}}" {{target_attr}} class="cdio-btn cdio-btn--primary cdio-btn--sm preview-link" style="display: none;">{{ _('Preview') }}</a>
@@ -91,3 +91,26 @@
&:hover { color: #d68a00; border-color: #d68a00; }
}
}
// State-mutating controls have to POST, and only a <button> can submit - these strip
// the browser's button chrome so they render exactly like the <a> they replaced.
.bare-btn {
background: none;
border: 0;
padding: 0;
margin: 0;
font: inherit;
// Matches `a { color: var(--color-link) }` - these replace anchors, and the row
// icons stroke with currentColor, so `inherit` would pick up .watch-controls red.
color: var(--color-link);
cursor: pointer;
&:focus-visible {
outline: 2px solid var(--color-link);
outline-offset: 2px;
}
&--link {
text-decoration: underline;
}
}
@@ -128,6 +128,8 @@ ul#top-right-menu {
font-weight: 600;
white-space: nowrap;
text-decoration: none;
font-family: inherit;
cursor: pointer;
&:hover {
background: rgba(255, 255, 255, 0.18);
@@ -205,7 +205,7 @@ body.watch-selection-active #checkbox-operations {
}
&.queued {
a.recheck {
.recheck {
display: none !important;
}
@@ -216,7 +216,7 @@ body.watch-selection-active #checkbox-operations {
}
&.paused {
a.pause-toggle {
.pause-toggle {
&.state-on {
display: inline !important;
}
@@ -228,7 +228,7 @@ body.watch-selection-active #checkbox-operations {
}
&.notification_muted {
a.mute-toggle {
.mute-toggle {
&.state-on {
display: inline !important;
}
File diff suppressed because one or more lines are too long
+8 -2
View File
@@ -5,10 +5,16 @@
{% if current_user.is_authenticated or not has_password %}
{% if not current_diff_url %}
<li class="pure-menu-item" id="menu-pause">
<a class="status-pill {{ 'paused' if all_paused }}" href="{{ url_for('settings.toggle_all_paused') }}" aria-label="{% if all_paused %}{{ _('Resume automatic scheduling') }}{% else %}{{ _('Pause auto-queue scheduling of watches') }}{% endif %}" title="{% if all_paused %}{{ _('Scheduling paused — click to resume') }}{% else %}{{ _('Scheduling active — click to pause all') }}{% endif %}"><span class="live-dot"></span>{% if all_paused %}{{ _('Paused') }}{% else %}{{ _('Running') }}{% endif %}</a>
<form method="POST" action="{{ url_for('settings.toggle_all_paused') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="status-pill {{ 'paused' if all_paused }}" aria-label="{% if all_paused %}{{ _('Resume automatic scheduling') }}{% else %}{{ _('Pause auto-queue scheduling of watches') }}{% endif %}" title="{% if all_paused %}{{ _('Scheduling paused — click to resume') }}{% else %}{{ _('Scheduling active — click to pause all') }}{% endif %}"><span class="live-dot"></span>{% if all_paused %}{{ _('Paused') }}{% else %}{{ _('Running') }}{% endif %}</button>
</form>
</li>
<li class="pure-menu-item " id="menu-mute">
<a class="status-pill {{ 'muted' if all_muted }}" href="{{ url_for('settings.toggle_all_muted') }}" aria-label="{% if all_muted %}{{ _('Unmute notifications') }}{% else %}{{ _('Mute notifications') }}{% endif %}" title="{% if all_muted %}{{ _('Notifications are muted - click to unmute') }}{% else %}{{ _('Mute notifications') }}{% endif %}"><i data-feather="{{ 'bell-off' if all_muted else 'bell' }}" class="action-icon"></i>{% if all_muted %}{{ _('Muted') }}{% else %}{{ _('Alerts on') }}{% endif %}</a>
<form method="POST" action="{{ url_for('settings.toggle_all_muted') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="status-pill {{ 'muted' if all_muted }}" aria-label="{% if all_muted %}{{ _('Unmute notifications') }}{% else %}{{ _('Mute notifications') }}{% endif %}" title="{% if all_muted %}{{ _('Notifications are muted - click to unmute') }}{% else %}{{ _('Mute notifications') }}{% endif %}"><i data-feather="{{ 'bell-off' if all_muted else 'bell' }}" class="action-icon"></i>{% if all_muted %}{{ _('Muted') }}{% else %}{{ _('Alerts on') }}{% endif %}</button>
</form>
</li>
{%- if current_user.is_authenticated -%}
<li class="pure-menu-item menu-collapsible">
@@ -96,7 +96,7 @@ def test_check_ldjson_price_autodetect(client, live_server, measure_memory_usage
assert b'ldjson-price-track-offer' in res.data
# Accept it
client.get(url_for('price_data_follower.accept', uuid=uuid, follow_redirects=True))
client.post(url_for('price_data_follower.accept', uuid=uuid), follow_redirects=True)
client.post(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
# Offer should be gone
@@ -493,7 +493,7 @@ def test_tag_mute_persists(client, live_server):
tag_uuid = datastore.add_tag('Test Tag')
# Mute the tag
response = client.get(url_for("tags.mute", uuid=tag_uuid))
response = client.post(url_for("tags.mute", uuid=tag_uuid))
assert response.status_code == 302 # Redirect
# Verify muted in memory
+1 -1
View File
@@ -33,7 +33,7 @@ def test_share_watch(client, live_server, measure_memory_usage, datastore_path):
assert bytes(include_filters.encode('utf-8')) in res.data
# click share the link
res = client.get(
res = client.post(
url_for("ui.form_share_put_watch", uuid=uuid),
follow_redirects=True
)
@@ -1567,7 +1567,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} sledování otagováno"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Sledujte tuto adresu URL!"
@@ -1588,7 +1588,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} Überwachungen wurde markiert"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Überwachung nicht gefunden"
@@ -1561,7 +1561,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr ""
@@ -1561,7 +1561,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr ""
@@ -1608,7 +1608,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "Se etiquetaron {} monitores"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Monitor no encontrado"
@@ -1570,7 +1570,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Surveillance non trouvée"
@@ -1563,7 +1563,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Monitoraggio non trovato"
@@ -1569,7 +1569,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} 件のウォッチにタグを付けました"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "ウォッチが見つかりません"
@@ -1571,7 +1571,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{}개 모니터링에 태그를 추가했습니다."
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "모니터링을 찾을 수 없습니다."
+2 -2
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.60.2\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-09-03 09:15+0200\n"
"POT-Creation-Date: 2026-09-03 21:31+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"
@@ -1560,7 +1560,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr ""
@@ -1691,7 +1691,7 @@ msgstr "Wybrano nieprawidłowy serwer proxy"
msgid "{} watches were tagged"
msgstr "Oznaczono {} obserwacji"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Nie znaleziono obserwacji"
@@ -1593,7 +1593,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} monitoramentos foram tagueados"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Monitoramento não encontrado"
@@ -1657,7 +1657,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} часы были помечены"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Часы не найдены"
@@ -1600,7 +1600,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} izleyici etiketlendi"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "İzleyici bulunamadı"
@@ -1581,7 +1581,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} завдань було позначено тегами"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "Завдання не знайдено"
@@ -1566,7 +1566,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "已为 {} 个监控项打标签"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "未找到监控项"
@@ -1565,7 +1565,7 @@ msgstr ""
msgid "{} watches were tagged"
msgstr "{} 個監測任務已加上標籤"
#: changedetectionio/blueprint/ui/__init__.py
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/watchlist/__init__.py
msgid "Watch not found"
msgstr "找不到監測任務"