From 682b04923bac064e51f0a0fdd756796f2902af79 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Thu, 5 Mar 2026 22:03:45 +0100 Subject: [PATCH 1/7] install flask --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5c8bdb4..6be3e5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ FROM ${img_user}/${img_repo}:${img_tag} COPY . /usr/local/src_dragonfly WORKDIR /usr/local/src_dragonfly -RUN pip install docker pymodbus +RUN pip install docker pymodbus Flask RUN pip install . WORKDIR / From e32c93da3bb2a0c447d6e02779c05868411449f3 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 10 Mar 2026 21:47:01 +0100 Subject: [PATCH 2/7] Three updates: 1. load slack_hook from a separate file. Thus we do not have to expose the hook to the version control system. 2. The config file is loaded at each checkinterval, thus updates to the alarm system are automatic and do not need a container restart. 3. The check_endpoints support an additional optional enable filed. If enable is present AND false, the test is skipped. This allows to temporarly disable the checks without to fully deleting the configuration. --- dragonfly/watchdog.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/dragonfly/watchdog.py b/dragonfly/watchdog.py index 86091db..1cdfd16 100755 --- a/dragonfly/watchdog.py +++ b/dragonfly/watchdog.py @@ -17,6 +17,7 @@ class WatchDog(object): def __init__(self, config_path): self.config_path = config_path self.load_configuration() + self.load_slack_hook() self.setup_docker_client() self.setup_dripline_connection() signal.signal(signal.SIGINT, self.exit_gracefully) @@ -27,11 +28,21 @@ def load_configuration(self): with open(Path(args.config), "r") as open_file: self.config = yaml.safe_load( open_file.read() ) - if not "slack_hook" in self.config.keys(): - self.config["slack_hook"] = None + if not "slack_hook_file" in self.config.keys(): + self.config["slack_hook_file"] = None print("Configuration is:", flush=True) print(self.config, flush=True) + + def load_slack_hook(self): + if self.config["slack_hook_file"] is not None: + with open(Path(self.config["slack_hook_file"]), "r") as open_file: + self.hook_config = yaml.safe_load( open_file.read() ) + + if not "slack_hook" in self.hook_config.keys(): + self.hook_config["slack_hook"] = None + else: + self.hook_config = {"slack_hook": None} def setup_docker_client(self): self.client = docker.from_env() @@ -45,11 +56,11 @@ def exit_gracefully(self, signum, frame): self.send_slack_message("Stopping, received signal: %d"%signum) def send_slack_message(self, message): - if self.config["slack_hook"] is None: + if self.hook_config["slack_hook"] is None: print("Slack hook not configured. No message will be send!") return post = {"text": "{0}".format(message)} - response = requests.post(self.config["slack_hook"], headers={'Content-Type': 'application/json'}, data=json.dumps(post)) + response = requests.post(self.hook_config["slack_hook"], headers={'Content-Type': 'application/json'}, data=json.dumps(post)) if response.status_code != 200: print(f'Request to slack returned an error {response.status_code}, the response is:\n{response.text}') @@ -73,11 +84,13 @@ def compare(self, value, reference, method): raise ValueError(f"Comparison method {method} is not defined. You can use one of ['not_equal', 'equal', 'lower', 'greater'].") def run(self): - while not self.kill_now: + self.load_configuration() + if self.config["check_endpoints"] is not None: for entry in self.config["check_endpoints"]: if self.kill_now: break + if "enable" in entry.keys() and entry["enable"]==False: continue try: value = self.get_endpoint(entry["endpoint"]) print(entry["endpoint"], value, flush=True) @@ -86,7 +99,6 @@ def run(self): except Exception as e: self.send_slack_message("Could not get endpoint %s. Got error %s."%(entry["endpoint"], str(e) )) - for container in self.client.containers.list(all=True): if self.kill_now: break if any([container.name.startswith(black) for black in self.config["blacklist_containers"]]): From 0a5ce2f2553494ff64896c347b5d740f48f26def Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 10 Mar 2026 22:59:59 +0100 Subject: [PATCH 3/7] setting up a flask web app that allows to update the AlarmSystem config file from within a web form --- dragonfly/__init__.py | 15 ------ dragonfly/alarm_server.py | 72 ++++++++++++++++++++++++++++ dragonfly/templates/index.html | 88 ++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 dragonfly/alarm_server.py create mode 100644 dragonfly/templates/index.html diff --git a/dragonfly/__init__.py b/dragonfly/__init__.py index eb89dbb..0916a2e 100644 --- a/dragonfly/__init__.py +++ b/dragonfly/__init__.py @@ -1,19 +1,4 @@ import logging logger = logging.getLogger(__name__) -def __get_version(): - import scarab - import dragonfly - import pkg_resources - #TODO: this all needs to be populated from setup.py and gita - version = scarab.VersionSemantic() - logger.info('version should be: {}'.format(pkg_resources.get_distribution('dragonfly').version)) - version.parse(pkg_resources.get_distribution('dragonfly').version) - version.package = 'project8/dragonfly' - version.commit = 'na' - dragonfly.core.add_version('dragonfly', version) - return version -version = __get_version() -__version__ = version.version - from .watchdog import * diff --git a/dragonfly/alarm_server.py b/dragonfly/alarm_server.py new file mode 100644 index 0000000..bfe96cf --- /dev/null +++ b/dragonfly/alarm_server.py @@ -0,0 +1,72 @@ +from flask import Flask, redirect, request, render_template, url_for +import os +import yaml + +CONFIG_FILE = "/root/AlarmSystem.yaml" + +def load_data(): + if not os.path.exists(CONFIG_FILE): + return [] + with open(CONFIG_FILE, "r") as open_file: + config = yaml.safe_load( open_file ) + return config["check_endpoints"] + +def save_data(data): + with open(CONFIG_FILE, "r") as open_file: + config = yaml.safe_load( open_file ) + config["check_endpoints"] = data + print(config, flush=True) + with open(CONFIG_FILE, "w") as open_file: + yaml.safe_dump(config, open_file) + +app = Flask(__name__) + +@app.route('/') +def index(): + data = load_data() + + return render_template("index.html", data=data) + +@app.route('/add', methods=["POST"]) +def add_row(): + data = load_data() + + new_row = { + "enable": request.form.get("enable") == "on", + "endpoint": request.form.get("endpoint"), + "method": request.form.get("comparison"), + "reference": request.form.get("reference"), + "message": request.form.get("message"), + } + + data.append(new_row) + + save_data(data) + + return redirect(url_for("index")) + +@app.route("/delete/", methods=["POST"]) +def delete_row(index): + data = load_data() + + if 0 <= index < len(data): + data.pop(index) + + save_data(data) + + return redirect(url_for("index")) + +@app.route("/update", methods=["POST"]) +def update(): + data = load_data() + + for i, row in enumerate(data): + row["enable"] = request.form.get(f"enable_{i}") == "on" + row["endpoint"] = request.form.get(f"endpoint_{i}") + row["comparison"] = request.form.get(f"comparison_{i}") + row["reference"] = request.form.get(f"reference_{i}") + row["message"] = request.form.get(f"message_{i}") + + save_data(data) + + return redirect(url_for("index")) diff --git a/dragonfly/templates/index.html b/dragonfly/templates/index.html new file mode 100644 index 0000000..019ce47 --- /dev/null +++ b/dragonfly/templates/index.html @@ -0,0 +1,88 @@ + + + + YAML Monitor Table + + + + +

Alarm System Configuration Table

+ +
+ + + + + + + + + + + {% for row in data %} + + + + + + + + + {% endfor %} +
EnableEndpointComparisonReferenceMessageDelete
+ + + + + + + + + + + + + +
+ +
+ + + +
+ +

Add New Row

+ +
+ +

+ + +

+ + +

+ + +

+ + +

+ + +
+ + + From f8dfbc69020a0759d20829fbcc9727dda7e9075a Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 10 Mar 2026 23:12:47 +0100 Subject: [PATCH 4/7] convert reference to float if possible, so that comparison works well --- dragonfly/alarm_server.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dragonfly/alarm_server.py b/dragonfly/alarm_server.py index bfe96cf..27fc02c 100644 --- a/dragonfly/alarm_server.py +++ b/dragonfly/alarm_server.py @@ -31,6 +31,7 @@ def index(): def add_row(): data = load_data() + new_row = { "enable": request.form.get("enable") == "on", "endpoint": request.form.get("endpoint"), @@ -38,6 +39,10 @@ def add_row(): "reference": request.form.get("reference"), "message": request.form.get("message"), } + try: + new_row["reference"] = float(new_row["reference"]) + except: + pass data.append(new_row) @@ -65,6 +70,10 @@ def update(): row["endpoint"] = request.form.get(f"endpoint_{i}") row["comparison"] = request.form.get(f"comparison_{i}") row["reference"] = request.form.get(f"reference_{i}") + try: + row["reference"] = float(row["reference"]) + except: + pass row["message"] = request.form.get(f"message_{i}") save_data(data) From a791423642e86edda1450d3858ee4331ff7c008b Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 10 Mar 2026 23:13:16 +0100 Subject: [PATCH 5/7] make add also in nice table format --- dragonfly/templates/index.html | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/dragonfly/templates/index.html b/dragonfly/templates/index.html index 019ce47..1eabce9 100644 --- a/dragonfly/templates/index.html +++ b/dragonfly/templates/index.html @@ -66,22 +66,24 @@

Alarm System Configuration Table

Add New Row

- -

- - -

- - -

- - -

- - -

- - + + + + + + + + + + + + + + + + + +
EnableEndpointComparisonReferenceMessageDelete
From 47c876ffc0d96c8cd11f4d9ff7ed757870348ec3 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Wed, 11 Mar 2026 10:16:59 +0100 Subject: [PATCH 6/7] adding notes and modifing table header --- dragonfly/templates/index.html | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dragonfly/templates/index.html b/dragonfly/templates/index.html index 1eabce9..2879902 100644 --- a/dragonfly/templates/index.html +++ b/dragonfly/templates/index.html @@ -28,7 +28,7 @@

Alarm System Configuration Table

Comparison Reference Message - Delete + Action {% for row in data %} @@ -73,7 +73,7 @@

Add New Row

Comparison Reference Message - Delete + Action @@ -85,6 +85,13 @@

Add New Row

- +

Notes:

+
    +
  • Enable: If checkbox is not checked, the check of the endpoint is skipped.
  • +
  • Endpoint: has to be a valid dripline endpoint name.
  • +
  • Comparison: Has to be one of ["equal", "not_equal", "lower", "greater"]
  • +
  • Reference: Will be interpreted as float if possible, otherwise as string. Has to match endpoint type.
  • +
  • Message: The message that is send to slack, it the condition is fullfilled.
  • +
From e69b079df2fe6edad863d1f8af13ccd1774e3e6c Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Mon, 16 Mar 2026 06:48:09 +0100 Subject: [PATCH 7/7] allowing to check calibratied values by attaching :value_cal to the endpoint name --- dragonfly/watchdog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dragonfly/watchdog.py b/dragonfly/watchdog.py index 1cdfd16..efb819b 100755 --- a/dragonfly/watchdog.py +++ b/dragonfly/watchdog.py @@ -92,7 +92,10 @@ def run(self): if self.kill_now: break if "enable" in entry.keys() and entry["enable"]==False: continue try: - value = self.get_endpoint(entry["endpoint"]) + if ":value_cal" in entry["endpoint"]: + value = self.get_endpoint(entry["endpoint"].replace(":value_cal", ""), calibrated=True) + else: + value = self.get_endpoint(entry["endpoint"]) print(entry["endpoint"], value, flush=True) if self.compare(value, entry["reference"], entry["method"]): self.send_slack_message(entry["message"].format(**locals()))