diff --git a/changedetectionio/blueprint/backups/__init__.py b/changedetectionio/blueprint/backups/__init__.py index add44308c3e..fe793d1c4fe 100644 --- a/changedetectionio/blueprint/backups/__init__.py +++ b/changedetectionio/blueprint/backups/__init__.py @@ -138,7 +138,7 @@ def download_backup(filename): return send_from_directory(os.path.abspath(datastore.datastore_path), filename, as_attachment=True) @login_optionally_required - @backups_blueprint.route("/", methods=['GET']) + @backups_blueprint.route("", methods=['GET']) def index(): backups = find_backups() output = render_template("overview.html", diff --git a/changedetectionio/blueprint/imports/__init__.py b/changedetectionio/blueprint/imports/__init__.py index e0dd12bdc96..2e5fddf5b4e 100644 --- a/changedetectionio/blueprint/imports/__init__.py +++ b/changedetectionio/blueprint/imports/__init__.py @@ -27,7 +27,7 @@ def import_page(): update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid})) if len(importer_handler.remaining_data) == 0: - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) else: remaining_urls = importer_handler.remaining_data diff --git a/changedetectionio/blueprint/price_data_follower/__init__.py b/changedetectionio/blueprint/price_data_follower/__init__.py index 6011303a267..99841d71529 100644 --- a/changedetectionio/blueprint/price_data_follower/__init__.py +++ b/changedetectionio/blueprint/price_data_follower/__init__.py @@ -20,13 +20,13 @@ def accept(uuid): datastore.data['watching'][uuid]['processor'] = 'restock_diff' datastore.data['watching'][uuid].clear_watch() update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid})) - return redirect(url_for("index")) + return redirect(url_for("watchlist.index")) @login_required @price_data_follower_blueprint.route("//reject", methods=['GET']) def reject(uuid): datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_REJECT - return redirect(url_for("index")) + return redirect(url_for("watchlist.index")) return price_data_follower_blueprint diff --git a/changedetectionio/blueprint/rss/__init__.py b/changedetectionio/blueprint/rss/__init__.py index d9a87695c52..113e35ac5a9 100644 --- a/changedetectionio/blueprint/rss/__init__.py +++ b/changedetectionio/blueprint/rss/__init__.py @@ -13,8 +13,7 @@ def construct_blueprint(datastore: ChangeDetectionStore): # Import the login decorator if needed # from changedetectionio.auth_decorator import login_optionally_required - - @rss_blueprint.route("/", methods=['GET']) + @rss_blueprint.route("", methods=['GET']) def feed(): now = time.time() # Always requires token set diff --git a/changedetectionio/blueprint/settings/__init__.py b/changedetectionio/blueprint/settings/__init__.py index 5375b565cc0..015cc274950 100644 --- a/changedetectionio/blueprint/settings/__init__.py +++ b/changedetectionio/blueprint/settings/__init__.py @@ -13,7 +13,7 @@ def construct_blueprint(datastore: ChangeDetectionStore): settings_blueprint = Blueprint('settings', __name__, template_folder="templates") - @settings_blueprint.route("/", methods=['GET', "POST"]) + @settings_blueprint.route("", methods=['GET', "POST"]) @login_optionally_required def settings_page(): from changedetectionio import forms @@ -74,7 +74,7 @@ def settings_page(): datastore.needs_write_urgent = True flash("Password protection enabled.", 'notice') flask_login.logout_user() - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) datastore.needs_write_urgent = True flash("Settings updated.") diff --git a/changedetectionio/blueprint/settings/templates/settings.html b/changedetectionio/blueprint/settings/templates/settings.html index 2e651a0104e..65ce6c66026 100644 --- a/changedetectionio/blueprint/settings/templates/settings.html +++ b/changedetectionio/blueprint/settings/templates/settings.html @@ -299,7 +299,7 @@

Chrome Extension

{{ render_button(form.save_button) }} - Back + Back Clear Snapshot History
diff --git a/changedetectionio/blueprint/tags/templates/groups-overview.html b/changedetectionio/blueprint/tags/templates/groups-overview.html index 30ddfe181b3..b75762f57d4 100644 --- a/changedetectionio/blueprint/tags/templates/groups-overview.html +++ b/changedetectionio/blueprint/tags/templates/groups-overview.html @@ -47,7 +47,7 @@ Mute notifications {{ "{:,}".format(tag_count[uuid]) if uuid in tag_count else 0 }} - {{ tag.title }} + {{ tag.title }} Edit  Delete diff --git a/changedetectionio/blueprint/ui/__init__.py b/changedetectionio/blueprint/ui/__init__.py index 5dfffac52db..973b95b785a 100644 --- a/changedetectionio/blueprint/ui/__init__.py +++ b/changedetectionio/blueprint/ui/__init__.py @@ -36,7 +36,7 @@ def clear_watch_history(uuid): else: flash("Cleared snapshot history for watch {}".format(uuid)) - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/clear_history", methods=['GET', 'POST']) @login_optionally_required @@ -52,7 +52,7 @@ def clear_all_history(): else: flash('Incorrect confirmation text.', 'error') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) output = render_template("clear_all_history.html") return output @@ -68,7 +68,7 @@ def mark_all_viewed(): continue datastore.set_last_viewed(watch_uuid, int(time.time())) - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/delete", methods=['GET']) @login_optionally_required @@ -77,7 +77,7 @@ def form_delete(): if uuid != 'all' and not uuid in datastore.data['watching'].keys(): flash('The watch by UUID {} does not exist.'.format(uuid), 'error') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) # More for testing, possible to return the first/only if uuid == 'first': @@ -85,7 +85,7 @@ def form_delete(): datastore.delete(uuid) flash('Deleted.') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/clone", methods=['GET']) @login_optionally_required @@ -101,7 +101,7 @@ def form_clone(): update_q.put(queuedWatchMetaData.PrioritizedItem(priority=5, item={'uuid': new_uuid})) flash('Cloned.') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/checknow", methods=['GET']) @login_optionally_required @@ -143,7 +143,7 @@ def form_watch_checknow(): if i == 0: flash("No watches available to recheck.") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/form/checkbox-operations", methods=['POST']) @login_optionally_required @@ -244,7 +244,7 @@ def form_watch_list_checkbox_operations(): flash(f"{len(uuids)} watches were tagged") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) @ui_blueprint.route("/share-url/", methods=['GET']) @@ -296,6 +296,6 @@ def form_share_put_watch(uuid): logger.error(f"Error sharing -{str(e)}") flash(f"Could not share, something went wrong while communicating with the share server - {str(e)}", 'error') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) return ui_blueprint \ No newline at end of file diff --git a/changedetectionio/blueprint/ui/edit.py b/changedetectionio/blueprint/ui/edit.py index db1e8e5564d..d4bd51ab859 100644 --- a/changedetectionio/blueprint/ui/edit.py +++ b/changedetectionio/blueprint/ui/edit.py @@ -32,14 +32,14 @@ def edit_page(uuid): # More for testing, possible to return the first/only if not datastore.data['watching'].keys(): flash("No watches to edit", "error") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) if uuid == 'first': uuid = list(datastore.data['watching'].keys()).pop() if not uuid in datastore.data['watching']: flash("No watch with the UUID %s found." % (uuid), "error") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) switch_processor = request.args.get('switch_processor') if switch_processor: @@ -66,7 +66,7 @@ def edit_page(uuid): processor_classes = next((tpl for tpl in processors.find_processors() if tpl[1] == processor_name), None) if not processor_classes: flash(f"Cannot load the edit form for processor/plugin '{processor_classes[1]}', plugin missing?", 'error') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) parent_module = processors.get_parent_module(processor_classes[0]) @@ -207,7 +207,7 @@ def edit_page(uuid): if request.args.get("next") and request.args.get("next") == 'diff': return redirect(url_for('ui.ui_views.diff_history_page', uuid=uuid)) - return redirect(url_for('index', tag=request.args.get("tag",''))) + return redirect(url_for('watchlist.index', tag=request.args.get("tag",''))) else: if request.method == 'POST' and not form.validate(): diff --git a/changedetectionio/blueprint/ui/templates/clear_all_history.html b/changedetectionio/blueprint/ui/templates/clear_all_history.html index fbbaa34f9f2..bcb57190f13 100644 --- a/changedetectionio/blueprint/ui/templates/clear_all_history.html +++ b/changedetectionio/blueprint/ui/templates/clear_all_history.html @@ -37,7 +37,7 @@
- Cancel
diff --git a/changedetectionio/blueprint/ui/views.py b/changedetectionio/blueprint/ui/views.py index 903a4c7778f..15524e58ffa 100644 --- a/changedetectionio/blueprint/ui/views.py +++ b/changedetectionio/blueprint/ui/views.py @@ -26,7 +26,7 @@ def preview_page(uuid): watch = datastore.data['watching'][uuid] except KeyError: flash("No history found for the specified link, bad link?", "error") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver' extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')] @@ -91,7 +91,7 @@ def diff_history_page(uuid): watch = datastore.data['watching'][uuid] except KeyError: flash("No history found for the specified link, bad link?", "error") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) # For submission of requesting an extract extract_form = forms.extractDataForm(request.form) @@ -119,7 +119,7 @@ def diff_history_page(uuid): if len(dates) < 2: flash("Not enough saved change detection snapshots to produce a report.", "error") - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) # Save the current newest history as the most recently viewed datastore.set_last_viewed(uuid, time.time()) @@ -196,7 +196,7 @@ def form_quick_watch_add(): if not form.validate(): for widget, l in form.errors.items(): flash(','.join(l), 'error') - return redirect(url_for('index')) + return redirect(url_for('watchlist.index')) url = request.form.get('url').strip() if datastore.url_exists(url): @@ -215,6 +215,6 @@ def form_quick_watch_add(): update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': new_uuid})) flash("Watch added.") - return redirect(url_for('index', tag=request.args.get('tag',''))) + return redirect(url_for('watchlist.index', tag=request.args.get('tag',''))) return views_blueprint \ No newline at end of file diff --git a/changedetectionio/blueprint/watchlist/__init__.py b/changedetectionio/blueprint/watchlist/__init__.py new file mode 100644 index 00000000000..fb57b2ee427 --- /dev/null +++ b/changedetectionio/blueprint/watchlist/__init__.py @@ -0,0 +1,114 @@ +import flask_login +import os +import time +import timeago + +from flask import Blueprint, request, make_response, render_template, redirect, url_for, flash, session +from flask_login import current_user +from flask_paginate import Pagination, get_page_parameter + +from changedetectionio import forms +from changedetectionio.store import ChangeDetectionStore +from changedetectionio.auth_decorator import login_optionally_required +from changedetectionio.strtobool import strtobool + +def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMetaData): + watchlist_blueprint = Blueprint('watchlist', __name__, template_folder="templates") + + @watchlist_blueprint.route("/", methods=['GET']) + @login_optionally_required + def index(): + active_tag_req = request.args.get('tag', '').lower().strip() + active_tag_uuid = active_tag = None + + # Be sure limit_tag is a uuid + if active_tag_req: + for uuid, tag in datastore.data['settings']['application'].get('tags', {}).items(): + if active_tag_req == tag.get('title', '').lower().strip() or active_tag_req == uuid: + active_tag = tag + active_tag_uuid = uuid + break + + # Redirect for the old rss path which used the /?rss=true + 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.needs_write = True + 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 = [] + with_errors = request.args.get('with_errors') == "1" + errored_count = 0 + search_q = request.args.get('q').strip().lower() if request.args.get('q') else False + for uuid, watch in datastore.data['watching'].items(): + if with_errors and not watch.get('last_error'): + continue + + if active_tag_uuid and not active_tag_uuid in watch['tags']: + continue + if watch.get('last_error'): + errored_count += 1 + + if search_q: + if (watch.get('title') and search_q in watch.get('title').lower()) or search_q in watch.get('url', '').lower(): + sorted_watches.append(watch) + elif watch.get('last_error') and search_q in watch.get('last_error').lower(): + sorted_watches.append(watch) + else: + sorted_watches.append(watch) + + form = forms.quickWatchForm(request.form) + page = request.args.get(get_page_parameter(), type=int, default=1) + total_count = len(sorted_watches) + + pagination = Pagination(page=page, + total=total_count, + per_page=datastore.data['settings']['application'].get('pager_size', 50), css_framework="semantic") + + sorted_tags = sorted(datastore.data['settings']['application'].get('tags').items(), key=lambda x: x[1]['title']) + output = render_template( + "watch-overview.html", + # Don't link to hosting when we're on the hosting environment + active_tag=active_tag, + active_tag_uuid=active_tag_uuid, + app_rss_token=datastore.data['settings']['application'].get('rss_access_token'), + datastore=datastore, + errored_count=errored_count, + form=form, + guid=datastore.data['app_guid'], + has_proxies=datastore.proxy_list, + has_unviewed=datastore.has_unviewed, + hosted_sticky=os.getenv("SALTED_PASS", False) == False, + pagination=pagination, + queued_uuids=[q_uuid.item['uuid'] for q_uuid in update_q.queue], + search_q=request.args.get('q','').strip(), + sort_attribute=request.args.get('sort') if request.args.get('sort') else request.cookies.get('sort'), + sort_order=request.args.get('order') if request.args.get('order') else request.cookies.get('order'), + system_default_fetcher=datastore.data['settings']['application'].get('fetch_backend'), + tags=sorted_tags, + watches=sorted_watches + ) + + if session.get('share-link'): + del(session['share-link']) + + resp = make_response(output) + + # The template can run on cookie or url query info + if request.args.get('sort'): + resp.set_cookie('sort', request.args.get('sort')) + if request.args.get('order'): + resp.set_cookie('order', request.args.get('order')) + + return resp + + return watchlist_blueprint \ No newline at end of file diff --git a/changedetectionio/templates/watch-overview.html b/changedetectionio/blueprint/watchlist/templates/watch-overview.html similarity index 87% rename from changedetectionio/templates/watch-overview.html rename to changedetectionio/blueprint/watchlist/templates/watch-overview.html index 0be142bab4c..2af9e61fde3 100644 --- a/changedetectionio/templates/watch-overview.html +++ b/changedetectionio/blueprint/watchlist/templates/watch-overview.html @@ -46,12 +46,12 @@ {% endif %} {% if search_q %}
Searching "{{search_q}}"
{% endif %}
- All + All {% for uuid, tag in tags %} {% if tag != "" %} - {{ tag.title }} + {{ tag.title }} {% endif %} {% endfor %}
@@ -72,14 +72,14 @@ {% set link_order = "desc" if sort_order == 'asc' else "asc" %} {% set arrow_span = "" %} - # + # - Website + Website {% if any_has_restock_price_processor %} Restock & Price {% endif %} - Last Checked - Last Changed + Last Checked + Last Changed @@ -104,12 +104,12 @@ {{ loop.index+pagination.skip }} {% if not watch.paused %} - Pause checks + Pause checks {% else %} - UnPause checks + UnPause checks {% endif %} {% set mute_label = 'UnMute notification' if watch.notification_muted else 'Mute notification' %} - {{ mute_label }} + {{ mute_label }} {{watch.title if watch.title is not none and watch.title|length > 0 else watch.url}} @@ -210,7 +210,7 @@