From e99003ae6f118daff429f30364e8ac8c5a21ce41 Mon Sep 17 00:00:00 2001 From: "openai-code-agent[bot]" <242516109+Codex@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:22:16 +0000 Subject: [PATCH] Add config validation and CLI DNS controls Co-authored-by: nexuspcs <69493073+nexuspcs@users.noreply.github.com> --- python/README.md | 7 +++ python/connectivity_monitor/__main__.py | 30 ++++++++++- python/connectivity_monitor/config.py | 70 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/python/README.md b/python/README.md index 5b9b263..80f1f25 100644 --- a/python/README.md +++ b/python/README.md @@ -47,6 +47,10 @@ Options: --targets TARGETS Comma-separated ping targets (e.g. 1.1.1.1,8.8.8.8) --poll SECONDS Poll interval in seconds (default: 2) --threshold N Consecutive failures to declare an outage (default: 4) + --lat-warn MS High-latency warning threshold in milliseconds (default: 100) + --enable-dns Force-enable DNS health checks (overrides saved config) + --disable-dns Disable DNS health checks + --dns-target HOST Hostname to resolve for DNS checks (default: google.com) --web-port PORT Web dashboard port (default: 8080) ``` @@ -64,6 +68,9 @@ python3 -m connectivity_monitor --headless --targets 1.1.1.1,8.8.8.8 --web-port # Adjust poll interval and threshold python3 -m connectivity_monitor --headless --poll 5 --threshold 3 + +# Tune latency alerts and DNS checks +python3 -m connectivity_monitor --headless --lat-warn 80 --disable-dns --targets 1.1.1.1,9.9.9.9 ``` ## Web Dashboard & API diff --git a/python/connectivity_monitor/__main__.py b/python/connectivity_monitor/__main__.py index ce620a1..702472c 100644 --- a/python/connectivity_monitor/__main__.py +++ b/python/connectivity_monitor/__main__.py @@ -3,7 +3,13 @@ import argparse import sys -from .config import load_config, interactive_setup, headless_config, ensure_dirs +from .config import ( + load_config, + interactive_setup, + headless_config, + ensure_dirs, + validate_config, +) from .monitor import run_monitor @@ -35,6 +41,23 @@ def main(): "--threshold", type=int, default=None, help="Consecutive failures to declare an outage (default: 4)", ) + parser.add_argument( + "--lat-warn", type=int, dest="lat_warn", default=None, + help="Latency threshold in ms for warning/high-latency events (default: 100)", + ) + dns_group = parser.add_mutually_exclusive_group() + dns_group.add_argument( + "--enable-dns", dest="enable_dns", action="store_true", default=None, + help="Force-enable DNS health checks (overrides saved config)", + ) + dns_group.add_argument( + "--disable-dns", dest="enable_dns", action="store_false", + help="Disable DNS health checks", + ) + parser.add_argument( + "--dns-target", type=str, dest="dns_target", default=None, + help="Hostname to resolve for DNS checks (default: google.com)", + ) parser.add_argument( "--web-port", type=int, default=None, dest="web_port", help="Web dashboard port (default: 8080)", @@ -52,6 +75,11 @@ def main(): cfg = interactive_setup(saved) cfg["headless"] = False + try: + cfg = validate_config(cfg) + except ValueError as exc: + parser.error(str(exc)) + run_monitor(cfg) diff --git a/python/connectivity_monitor/config.py b/python/connectivity_monitor/config.py index c6b5aed..c0d0bc5 100644 --- a/python/connectivity_monitor/config.py +++ b/python/connectivity_monitor/config.py @@ -17,6 +17,26 @@ } +def _normalize_targets(targets): + """Split and clean a comma-separated targets string.""" + if not targets: + return [] + return [t.strip() for t in str(targets).split(",") if t.strip()] + + +def _require_positive_int(value, label, minimum=1, maximum=None): + """Validate integer bounds and return the coerced value.""" + try: + num = int(value) + except (TypeError, ValueError): + raise ValueError("{} must be an integer.".format(label)) + if num < minimum: + raise ValueError("{} must be at least {}.".format(label, minimum)) + if maximum is not None and num > maximum: + raise ValueError("{} must be at most {}.".format(label, maximum)) + return num + + def get_base_dir(): """Return base directory for logs/reports/config.""" home = os.path.expanduser("~") @@ -134,7 +154,57 @@ def headless_config(args): cfg["poll"] = args.poll if args.threshold: cfg["threshold"] = args.threshold + if args.lat_warn is not None: + cfg["lat_warn"] = args.lat_warn + if args.enable_dns is not None: + cfg["enable_dns"] = args.enable_dns + if args.dns_target: + cfg["dns_target"] = args.dns_target if args.web_port is not None: cfg["web_port"] = args.web_port return cfg + + +def validate_config(cfg): + """ + Validate and normalize configuration. + + Ensures numeric fields are positive, port is within range, targets are + present, and DNS settings are consistent. Returns the validated config + (mutating the original dict). + """ + cfg["poll"] = _require_positive_int( + cfg.get("poll", DEFAULTS["poll"]), "Poll interval (seconds)" + ) + cfg["threshold"] = _require_positive_int( + cfg.get("threshold", DEFAULTS["threshold"]), + "Failure threshold", + ) + cfg["lat_warn"] = _require_positive_int( + cfg.get("lat_warn", DEFAULTS["lat_warn"]), + "Latency warning threshold (ms)", + ) + cfg["web_port"] = _require_positive_int( + cfg.get("web_port", DEFAULTS["web_port"]), + "Web dashboard port", + maximum=65535, + ) + + targets = _normalize_targets(cfg.get("targets", DEFAULTS["targets"])) + if not targets: + raise ValueError("At least one ping target is required (e.g. 1.1.1.1).") + cfg["targets"] = ",".join(targets) + + enable_dns = cfg.get("enable_dns", True) + if isinstance(enable_dns, str): + enable_dns = enable_dns.lower() not in ("false", "0", "no", "off") + cfg["enable_dns"] = bool(enable_dns) + + if cfg["enable_dns"]: + dns_target = cfg.get("dns_target", DEFAULTS["dns_target"]) + if not dns_target or not str(dns_target).strip(): + raise ValueError("DNS health check is enabled but no DNS hostname is set.") + cfg["dns_target"] = str(dns_target).strip() + + return cfg