UI - Real time - checkbox operations now realtime without reload

pull/3229/head
dgtlmoon 2025-06-03 14:54:13 +02:00
rodzic d5af91d8f7
commit 30e84f1030
4 zmienionych plików z 167 dodań i 88 usunięć

Wyświetl plik

@ -7,6 +7,105 @@ from changedetectionio.blueprint.ui.edit import construct_blueprint as construct
from changedetectionio.blueprint.ui.notification import construct_blueprint as construct_notification_blueprint
from changedetectionio.blueprint.ui.views import construct_blueprint as construct_views_blueprint
def _handle_operations(op, uuids, datastore, worker_handler, update_q, queuedWatchMetaData, watch_check_update, extra_data=None, emit_flash=True):
from flask import request, flash
if op == 'delete':
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.delete(uuid)
if emit_flash:
flash(f"{len(uuids)} watches deleted")
elif op == 'pause':
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['paused'] = True
if emit_flash:
flash(f"{len(uuids)} watches paused")
elif op == 'unpause':
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['paused'] = False
if emit_flash:
flash(f"{len(uuids)} watches unpaused")
elif (op == 'mark-viewed'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.set_last_viewed(uuid, int(time.time()))
if emit_flash:
flash(f"{len(uuids)} watches updated")
elif (op == 'mute'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_muted'] = True
if emit_flash:
flash(f"{len(uuids)} watches muted")
elif (op == 'unmute'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_muted'] = False
if emit_flash:
flash(f"{len(uuids)} watches un-muted")
elif (op == 'recheck'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
# Recheck and require a full reprocessing
worker_handler.queue_item_async_safe(update_q, queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))
if emit_flash:
flash(f"{len(uuids)} watches queued for rechecking")
elif (op == 'clear-errors'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]["last_error"] = False
if emit_flash:
flash(f"{len(uuids)} watches errors cleared")
elif (op == 'clear-history'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.clear_watch_history(uuid)
if emit_flash:
flash(f"{len(uuids)} watches cleared/reset.")
elif (op == 'notification-default'):
from changedetectionio.notification import (
default_notification_format_for_watch
)
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_title'] = None
datastore.data['watching'][uuid]['notification_body'] = None
datastore.data['watching'][uuid]['notification_urls'] = []
datastore.data['watching'][uuid]['notification_format'] = default_notification_format_for_watch
if emit_flash:
flash(f"{len(uuids)} watches set to use default notification settings")
elif (op == 'assign-tag'):
op_extradata = extra_data
if op_extradata:
tag_uuid = datastore.add_tag(title=op_extradata)
if op_extradata and tag_uuid:
for uuid in uuids:
if datastore.data['watching'].get(uuid):
# Bug in old versions caused by bad edit page/tag handler
if isinstance(datastore.data['watching'][uuid]['tags'], str):
datastore.data['watching'][uuid]['tags'] = []
datastore.data['watching'][uuid]['tags'].append(tag_uuid)
if emit_flash:
flash(f"{len(uuids)} watches were tagged")
if uuids:
for uuid in uuids:
watch_check_update.send(watch_uuid=uuid)
def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_handler, queuedWatchMetaData, watch_check_update):
ui_blueprint = Blueprint('ui', __name__, template_folder="templates")
@ -149,93 +248,17 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_handle
def form_watch_list_checkbox_operations():
op = request.form['op']
uuids = [u.strip() for u in request.form.getlist('uuids') if u]
if (op == 'delete'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.delete(uuid)
flash("{} watches deleted".format(len(uuids)))
elif (op == 'pause'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['paused'] = True
flash("{} watches paused".format(len(uuids)))
elif (op == 'unpause'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['paused'] = False
flash("{} watches unpaused".format(len(uuids)))
elif (op == 'mark-viewed'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.set_last_viewed(uuid, int(time.time()))
flash("{} watches updated".format(len(uuids)))
elif (op == 'mute'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_muted'] = True
flash("{} watches muted".format(len(uuids)))
elif (op == 'unmute'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_muted'] = False
flash("{} watches un-muted".format(len(uuids)))
elif (op == 'recheck'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
# Recheck and require a full reprocessing
worker_handler.queue_item_async_safe(update_q, queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))
flash("{} watches queued for rechecking".format(len(uuids)))
elif (op == 'clear-errors'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]["last_error"] = False
flash(f"{len(uuids)} watches errors cleared")
elif (op == 'clear-history'):
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.clear_watch_history(uuid)
flash("{} watches cleared/reset.".format(len(uuids)))
elif (op == 'notification-default'):
from changedetectionio.notification import (
default_notification_format_for_watch
)
for uuid in uuids:
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]['notification_title'] = None
datastore.data['watching'][uuid]['notification_body'] = None
datastore.data['watching'][uuid]['notification_urls'] = []
datastore.data['watching'][uuid]['notification_format'] = default_notification_format_for_watch
flash("{} watches set to use default notification settings".format(len(uuids)))
elif (op == 'assign-tag'):
op_extradata = request.form.get('op_extradata', '').strip()
if op_extradata:
tag_uuid = datastore.add_tag(title=op_extradata)
if op_extradata and tag_uuid:
for uuid in uuids:
if datastore.data['watching'].get(uuid):
# Bug in old versions caused by bad edit page/tag handler
if isinstance(datastore.data['watching'][uuid]['tags'], str):
datastore.data['watching'][uuid]['tags'] = []
datastore.data['watching'][uuid]['tags'].append(tag_uuid)
flash(f"{len(uuids)} watches were tagged")
if uuids:
for uuid in uuids:
# with app.app_context():
watch_check_update.send(watch_uuid=uuid)
extra_data = request.form.get('op_extradata', '').strip()
_handle_operations(
datastore=datastore,
extra_data=extra_data,
queuedWatchMetaData=queuedWatchMetaData,
uuids=uuids,
worker_handler=worker_handler,
update_q=update_q,
watch_check_update=watch_check_update,
op=op,
)
return redirect(url_for('watchlist.index'))

Wyświetl plik

@ -26,6 +26,9 @@ class SignalHandler:
queue_length_signal.connect(self.handle_queue_length, weak=False)
# logger.info("SignalHandler: Connected to queue_length signal")
watch_delete_signal = signal('watch_deleted')
watch_delete_signal.connect(self.handle_deleted_signal, weak=False)
# Create and start the queue update thread using standard threading
import threading
self.polling_emitter_thread = threading.Thread(
@ -61,6 +64,16 @@ class SignalHandler:
else:
logger.warning(f"Watch UUID {watch_uuid} not found in datastore")
def handle_deleted_signal(self, *args, **kwargs):
watch_uuid = kwargs.get('watch_uuid')
if watch_uuid:
# Emit the queue size to all connected clients
self.socketio_instance.emit("watch_deleted", {
"uuid": watch_uuid,
"event_timestamp": time.time()
})
logger.debug(f"Watch UUID {watch_uuid} was deleted")
def handle_queue_length(self, *args, **kwargs):
"""Handle queue_length signal and emit to all clients"""
try:
@ -167,7 +180,6 @@ def handle_watch_update(socketio, **kwargs):
if hasattr(q_item, 'item') and 'uuid' in q_item.item:
queue_list.append(q_item.item['uuid'])
error_texts = ""
# Get the error texts from the watch
error_texts = watch.compile_error_texts()
# Create a simplified watch data object to send to clients
@ -259,6 +271,29 @@ def init_socketio(app, datastore):
# Set up event handlers
logger.info("Socket.IO: Registering connect event handler")
@socketio.on('checkbox-operation')
def event_checkbox_operations(data):
from changedetectionio.blueprint.ui import _handle_operations
from changedetectionio import queuedWatchMetaData
from changedetectionio import worker_handler
from changedetectionio.flask_app import update_q, watch_check_update
logger.trace(f"Got checkbox operations event: {data}")
datastore = socketio.datastore
_handle_operations(
op=data.get('op'),
uuids=data.get('uuids'),
datastore=datastore,
extra_data=data.get('extra_data'),
worker_handler=worker_handler,
update_q=update_q,
queuedWatchMetaData=queuedWatchMetaData,
watch_check_update=watch_check_update,
emit_flash=False
)
@socketio.on('connect')
def handle_connect():
"""Handle client connection"""

Wyświetl plik

@ -18,6 +18,24 @@ $(document).ready(function () {
return false;
});
$('#checkbox-operations button').on('click.socketHandlerNamespace', function (e) {
e.preventDefault();
const op = $(this).val();
const checkedUuids = $('input[name="uuids"]:checked').map(function () {
return this.value.trim();
}).get();
console.log(`Socket.IO: Sending watch operation '${op}' for UUIDs:`, checkedUuids);
socket.emit('checkbox-operation', {
op: op,
uuids: checkedUuids,
extra_data: $('#op_extradata').val() // Set by the alert() handler
});
$('input[name="uuids"]:checked').prop('checked', false);
return false;
});
}

Wyświetl plik

@ -253,6 +253,9 @@ class ChangeDetectionStore:
del self.data['watching'][uuid]
self.needs_write_urgent = True
watch_delete_signal = signal('watch_deleted')
if watch_delete_signal:
watch_delete_signal.send(watch_uuid=uuid)
# Clone a watch by UUID
def clone(self, uuid):