mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-28 12:23:22 +00:00
WIP
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
from json_logic.builtins import BUILTINS
|
||||
|
||||
from .exceptions import EmptyConditionRuleRowNotUsable
|
||||
from .pluggy_interface import plugin_manager # Import the pluggy plugin manager
|
||||
from . import default_plugin
|
||||
|
||||
@@ -46,6 +48,7 @@ CUSTOM_OPERATIONS = {
|
||||
"!contains_regex": not_contains_regex
|
||||
}
|
||||
|
||||
|
||||
def convert_to_jsonlogic(rule_dict):
|
||||
"""
|
||||
Convert a structured rule dict into a JSON Logic rule.
|
||||
@@ -66,6 +69,9 @@ def convert_to_jsonlogic(rule_dict):
|
||||
field = condition["field"]
|
||||
value = condition["value"]
|
||||
|
||||
if not operator or operator == 'None' or not value or not field:
|
||||
raise EmptyConditionRuleRowNotUsable()
|
||||
|
||||
# Convert value to int/float if possible
|
||||
try:
|
||||
if isinstance(value, str) and "." in value:
|
||||
@@ -98,6 +104,7 @@ def execute_ruleset_against_all_plugins(current_watch_uuid: str, application_dat
|
||||
"""
|
||||
from json_logic import jsonLogic
|
||||
|
||||
|
||||
# Give all plugins a chance to update the data dict again (that we will test the conditions against)
|
||||
for plugin in plugin_manager.get_plugins():
|
||||
new_execute_data = plugin.add_data(current_watch_uuid=current_watch_uuid,
|
||||
|
||||
6
changedetectionio/conditions/exceptions.py
Normal file
6
changedetectionio/conditions/exceptions.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class EmptyConditionRuleRowNotUsable(Exception):
|
||||
def __init__(self):
|
||||
super().__init__("One of the 'conditions' rulesets is incomplete, cannot run.")
|
||||
|
||||
def __str__(self):
|
||||
return self.args[0]
|
||||
41
changedetectionio/conditions/form.py
Normal file
41
changedetectionio/conditions/form.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Condition Rule Form (for each rule row)
|
||||
from wtforms import Form, SelectField, StringField, validators
|
||||
from wtforms import validators
|
||||
|
||||
class ConditionFormRow(Form):
|
||||
|
||||
# ✅ Ensure Plugins Are Loaded BEFORE Importing Choices
|
||||
from changedetectionio.conditions import plugin_manager
|
||||
from changedetectionio.conditions import operator_choices, field_choices
|
||||
|
||||
operator = SelectField(
|
||||
"Operator",
|
||||
choices=operator_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
field = SelectField(
|
||||
"Field",
|
||||
choices=field_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
value = StringField("Value", validators=[validators.Optional()])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
# First, run the default validators
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
# Custom validation logic
|
||||
if not self.operator.data or self.operator.data == 'None':
|
||||
self.operator.errors.append("Operator is required.")
|
||||
return False
|
||||
|
||||
if not self.field.data or self.field.data == 'None':
|
||||
self.field.errors.append("Field is required.")
|
||||
return False
|
||||
|
||||
if not self.value.data:
|
||||
self.value.errors.append("Value is required.")
|
||||
return False
|
||||
|
||||
return True # Only return True if all conditions pass
|
||||
@@ -3,8 +3,8 @@ import re
|
||||
from loguru import logger
|
||||
from wtforms.widgets.core import TimeInput
|
||||
|
||||
from changedetectionio.conditions.form import ConditionFormRow
|
||||
from changedetectionio.strtobool import strtobool
|
||||
from flask_wtf import FlaskForm
|
||||
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
@@ -513,25 +513,6 @@ class quickWatchForm(Form):
|
||||
|
||||
|
||||
|
||||
# Condition Rule Form (for each rule row)
|
||||
class ConditionForm(FlaskForm):
|
||||
from changedetectionio.conditions import plugin_manager
|
||||
|
||||
# ✅ Ensure Plugins Are Loaded BEFORE Importing Choices
|
||||
from changedetectionio.conditions import operator_choices, field_choices
|
||||
|
||||
operator = SelectField(
|
||||
"Operator",
|
||||
choices=operator_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
field = SelectField(
|
||||
"Field",
|
||||
choices=field_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
value = StringField("Value", validators=[validators.Optional()])
|
||||
|
||||
# Common to a single watch and the global settings
|
||||
class commonSettingsForm(Form):
|
||||
from . import processors
|
||||
@@ -620,7 +601,7 @@ class processor_text_json_diff_form(commonSettingsForm):
|
||||
notification_screenshot = BooleanField('Attach screenshot to notification (where possible)', default=False)
|
||||
|
||||
conditions_match_logic = RadioField(u'Match', choices=[('ALL', 'Match all of the following'),('ANY', 'Match any of the following')], default='ALL')
|
||||
conditions = FieldList(FormField(ConditionForm), min_entries=1) # Add rule logic here
|
||||
conditions = FieldList(FormField(ConditionFormRow), min_entries=1) # Add rule logic here
|
||||
|
||||
|
||||
def extra_tab_content(self):
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import re
|
||||
import urllib3
|
||||
|
||||
from changedetectionio.conditions import execute_ruleset_against_all_plugins
|
||||
from changedetectionio.processors import difference_detection_processor
|
||||
from changedetectionio.html_tools import PERL_STYLE_REGEX, cdata_in_document_to_text, TRANSLATE_WHITESPACE_TABLE
|
||||
from changedetectionio import html_tools, content_fetchers
|
||||
@@ -331,6 +332,15 @@ class perform_site_check(difference_detection_processor):
|
||||
if result:
|
||||
blocked = True
|
||||
|
||||
# And check if 'conditions' will let this pass through
|
||||
if not execute_ruleset_against_all_plugins(current_watch_uuid=watch.get('uuid'),
|
||||
application_datastruct=self.datastore.data,
|
||||
ephemeral_data={
|
||||
'text': stripped_text_from_html
|
||||
}
|
||||
):
|
||||
# Conditions say "Condition not met" so we block it.
|
||||
blocked = True
|
||||
|
||||
# Looks like something changed, but did it match all the rules?
|
||||
if blocked:
|
||||
|
||||
@@ -26,7 +26,6 @@ function set_active_tab() {
|
||||
if (tab.length) {
|
||||
tab[0].parentElement.className = "active";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function focus_error_tab() {
|
||||
|
||||
@@ -61,6 +61,41 @@
|
||||
{{ field(**kwargs)|safe }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_fieldlist_of_formfields_as_table(fieldlist) %}
|
||||
<table class="fieldlist_formfields pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for subfield in fieldlist[0] %}
|
||||
<th>{{ subfield.label }}</th>
|
||||
{% endfor %}
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form_row in fieldlist %}
|
||||
<tr {% if form_row.errors %} class="error-row" {% endif %}>
|
||||
{% for subfield in form_row %}
|
||||
<td>
|
||||
{{ subfield()|safe }}
|
||||
{% if subfield.errors %}
|
||||
<ul class="errors">
|
||||
{% for error in subfield.errors %}
|
||||
<li class="error">{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td>
|
||||
<button type="button" onclick="addRuleRow()">ADD +</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro playwright_warning() %}
|
||||
<p><strong>Error - Playwright support for Chrome based fetching is not enabled.</strong> Alternatively try our <a href="https://changedetection.io">very affordable subscription based service which has all this setup for you</a>.</p>
|
||||
<p>You may need to <a href="https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31">Enable playwright environment variable</a> and uncomment the <strong>sockpuppetbrowser</strong> in the <a href="https://github.com/dgtlmoon/changedetection.io/blob/master/docker-compose.yml">docker-compose.yml</a> file.</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
{% from '_helpers.html' import render_field, render_checkbox_field, render_button, render_time_schedule_form, playwright_warning, only_webdriver_type_watches_warning %}
|
||||
{% from '_helpers.html' import render_field, render_checkbox_field, render_button, render_time_schedule_form, playwright_warning, only_webdriver_type_watches_warning, render_fieldlist_of_formfields_as_table %}
|
||||
{% from '_common_fields.html' import render_common_settings_form %}
|
||||
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='vis.js')}}" defer></script>
|
||||
@@ -323,28 +323,8 @@ Math: {{ 1 + 1 }}") }}
|
||||
<div class="pure-control-group">
|
||||
<!-- Grid Header -->
|
||||
{{ render_field(form.conditions_match_logic) }}
|
||||
<table class="pure-table">
|
||||
<thead>
|
||||
<td>In Value</td>
|
||||
<td>Operator</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
{{ render_fieldlist_of_formfields_as_table(form.conditions) }}
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Rule Rows (Dynamic Content) -->
|
||||
{% for rule in form.conditions %}
|
||||
<tr>
|
||||
<td>{{ rule.field() }}</td>
|
||||
<td>{{ rule.operator() }}</td>
|
||||
<td>{{ rule.value() }}</td>
|
||||
<td>
|
||||
<button type="button" onclick="addRuleRow()">ADD +</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane-inner" id="filters-and-triggers">
|
||||
|
||||
Reference in New Issue
Block a user