diff --git a/hbd/server/settings.py b/hbd/server/settings.py index 768a569..39648d0 100644 --- a/hbd/server/settings.py +++ b/hbd/server/settings.py @@ -181,6 +181,48 @@ def get_settings_sections(config: dict) -> list: "notification_channels": attrs.get("notification_channels", []), }) + # ---- Threshold configurations ----------------------------------------- + def _parse_metric_row(metric_path, metric_cfg): + if not isinstance(metric_cfg, dict): + return None + 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), + } + + 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"]), + }) + # ---- Hosts summary ---------------------------------------------------- hosts_list = [] for hname, hcfg in (config.get("hosts") or {}).items(): @@ -312,6 +354,16 @@ def get_settings_sections(config: dict) -> list: "hosts": hosts_list, "fields": [], }, + { + "id": "thresholds", + "title": "Threshold Configurations", + "description": "Named alert threshold sets. Each defines warning/critical levels per metric.", + "threshold_configs": threshold_config_list, + "fields": [ + field("default_threshold_config", "Default config", "text", + "Threshold config used for hosts with no explicit mapping."), + ], + }, { "id": "runtime", "title": "Runtime", diff --git a/hbd/server/templates/settings.html b/hbd/server/templates/settings.html index a867952..adf7104 100644 --- a/hbd/server/templates/settings.html +++ b/hbd/server/templates/settings.html @@ -254,6 +254,17 @@ .host-bool { text-align: center; } .dot-yes { color: #2e7d32; font-size: 1.1em; } .dot-no { color: #ddd; font-size: 1.1em; } + + /* ---- Threshold configurations ---- */ + .thresh-config { margin: 12px 20px 20px; } + .thresh-config-name { + font-weight: 600; font-size: 0.9em; color: #1a237e; + margin-bottom: 6px; + } + .mini-table .warn { color: #e65100; font-weight: 600; } + .mini-table .crit { color: #b71c1c; font-weight: 600; } + .mini-table .dim { color: #aaa; } + .mini-table .metric-path { font-family: monospace; font-size: 0.88em; } @@ -394,6 +405,49 @@ {% endif %} {% endif %} + {# ---- Threshold configurations section ---- #} + {% if section.id == "thresholds" %} + {% if section.threshold_configs %} + {% for tc in section.threshold_configs %} +
+
{{ tc.name }}
+ {% if tc.metrics %} +
+ + + + + + + + + + + + + {% for m in tc.metrics %} + + + + + + + + + {% endfor %} + +
MetricOpWarningCriticalHysteresisCount
{{ m.metric }}{{ m.operator or '>' }}{{ m.warning if m.warning is not none else '—' }}{{ m.critical if m.critical is not none else '—' }}{{ '%.0f%%' % (m.hysteresis * 100) if m.hysteresis else '—' }}{{ m.count }}
+
+ {% else %} + No thresholds defined. + {% endif %} +
+ {% endfor %} + {% else %} +
No threshold configurations defined.
+ {% endif %} + {% endif %} + {# ---- Hosts section ---- #} {% if section.id == "hosts" %} {% if section.hosts %}