diff --git a/hbd/client/main.py b/hbd/client/main.py index 2418910..9587385 100644 --- a/hbd/client/main.py +++ b/hbd/client/main.py @@ -474,6 +474,7 @@ async def cleanup(connections: List[AsyncConnection]): logger.error(f"Error sending shutdown: {e}") conn.close() + break # Only send shutdown on first connection to avoid duplicates # Give messages time to send await asyncio.sleep(0.5) @@ -540,6 +541,7 @@ async def async_main(args, config): boot_msg["acks"] = 0 for conn in connections: await conn.sendto(boot_msg) + break # Only send message on first connection to avoid duplicates if args.message and not args.daemon: # Message-only mode @@ -739,7 +741,7 @@ def main(argv=None): # Daemonize if requested if args.daemon: - print("Daemonizing...") + logging.info("Daemonizing...") daemonize() _reconfigure_logging_for_daemon(log_level) logging.info(f"hbc starting, sending heartbeat to {', '.join(args.hosts)}") diff --git a/hbd/server/http.py b/hbd/server/http.py index c40e3fe..cdda8fd 100644 --- a/hbd/server/http.py +++ b/hbd/server/http.py @@ -890,7 +890,7 @@ async def start( tmpl = env.get_template("settings.html") body = tmpl.render( title="Settings - Heartbeat", - sections=settings_mod.get_settings_sections(config), + sections=settings_mod.get_settings_sections(config, threshold_checker=threshold_checker), current_user=current_user.to_dict() if current_user else None, active_page="settings", ) diff --git a/hbd/server/main.py b/hbd/server/main.py index 95701bd..4ed1230 100644 --- a/hbd/server/main.py +++ b/hbd/server/main.py @@ -255,6 +255,7 @@ async def _run_async(config, config_path=None): config=config, hbdclass=hbdclass, tcss=None, + threshold_checker=threshold_checker, verbose=config.get("verbose", False), get_now=lambda: time.time(), VER="", diff --git a/hbd/server/settings.py b/hbd/server/settings.py index 39648d0..b442890 100644 --- a/hbd/server/settings.py +++ b/hbd/server/settings.py @@ -88,7 +88,7 @@ def _sanitize_channel(name, cfg): # Public API # --------------------------------------------------------------------------- -def get_settings_sections(config: dict) -> list: +def get_settings_sections(config: dict, threshold_checker=None) -> list: """Return ordered list of setting sections for the settings page. Each section: @@ -182,46 +182,39 @@ def get_settings_sections(config: dict) -> list: }) # ---- Threshold configurations ----------------------------------------- - def _parse_metric_row(metric_path, metric_cfg): - if not isinstance(metric_cfg, dict): - return None + def _tc_to_row(tc): return { - "metric": metric_path, - "operator": metric_cfg.get("operator", ">"), - "warning": metric_cfg.get("warning"), - "critical": metric_cfg.get("critical"), - "hysteresis": metric_cfg.get("hysteresis"), - "count": metric_cfg.get("count", 1), - "enabled": metric_cfg.get("enabled", True), + "metric": tc.metric_path, + "operator": tc.operator.value, + "warning": tc.warning, + "critical": tc.critical, + "hysteresis": tc.hysteresis, + "count": tc.count, + "enabled": tc.enabled, } threshold_config_list = [] - raw_tconfigs = config.get("threshold_configs") or {} - if raw_tconfigs: - for cfg_name, cfg_data in sorted(raw_tconfigs.items()): - if not isinstance(cfg_data, dict): - continue - metrics = [ - r for r in ( - _parse_metric_row(mp, mc) - for mp, mc in (cfg_data.get("thresholds") or {}).items() - ) if r - ] - threshold_config_list.append({ - "name": cfg_name, - "metrics": sorted(metrics, key=lambda m: m["metric"]), - }) - elif config.get("thresholds"): - metrics = [ - r for r in ( - _parse_metric_row(mp, mc) - for mp, mc in config["thresholds"].items() - ) if r - ] - threshold_config_list.append({ - "name": "default", - "metrics": sorted(metrics, key=lambda m: m["metric"]), - }) + if threshold_checker is not None: + if threshold_checker.threshold_configs: + for cfg_name, cfg_metrics in sorted(threshold_checker.threshold_configs.items()): + # For the default config use the merged effective set; + # for named overrides use only the explicitly defined metrics + # (threshold_raw_configs) so inherited defaults are not repeated. + if cfg_name == "default": + display_metrics = cfg_metrics + else: + display_metrics = threshold_checker.threshold_raw_configs.get(cfg_name, cfg_metrics) + metrics = sorted( + [_tc_to_row(tc) for tc in display_metrics.values()], + key=lambda m: m["metric"], + ) + threshold_config_list.append({"name": cfg_name, "metrics": metrics}) + elif threshold_checker.thresholds: + metrics = sorted( + [_tc_to_row(tc) for tc in threshold_checker.thresholds.values()], + key=lambda m: m["metric"], + ) + threshold_config_list.append({"name": "default", "metrics": metrics}) # ---- Hosts summary ---------------------------------------------------- hosts_list = [] diff --git a/scripts/hbc_mini.py b/scripts/hbc_mini.py index 7a0ba7c..5dd6e47 100755 --- a/scripts/hbc_mini.py +++ b/scripts/hbc_mini.py @@ -1054,6 +1054,7 @@ async def _async_main(args, cfg: Dict[str, Any]) -> int: bmsg["msg"] = args.message for c in connections: await c.sendto(bmsg) + break if args.message and not args.daemon: await asyncio.sleep(0.3) for c in connections: @@ -1090,6 +1091,8 @@ async def _async_main(args, cfg: Dict[str, Any]) -> int: await conn.sendto({"shutdown": 1, "acks": conn.ackcount}) except Exception: pass + break + for conn in connections: conn.close() await asyncio.sleep(0.3) for plugin in plugins: