diff --git a/hbd/server/http.py b/hbd/server/http.py index c972e63..8ad3a56 100644 --- a/hbd/server/http.py +++ b/hbd/server/http.py @@ -28,19 +28,25 @@ eventlog = notify_mod.eventlog def _build_threshold_configs_from_form(form_data: dict) -> dict: - """Convert form-submitted flat threshold data to nested threshold_configs YAML structure. + """Convert form-submitted threshold data to the nested threshold_configs structure. - Input: {config_name: {metric_path: {warning, critical, operator, hysteresis, enabled, count, display}}} - Output: {config_name: {thresholds: {plugin: {metric: {warning, critical, ...}}}}} + Input: {config_name: {owner?: str, metrics: {metric_path: {warning, critical, ...}}}} + Output: {config_name: {owner?: str, thresholds: {plugin: {metric: {...}}}}} """ result = {} - for config_name, metrics in form_data.items(): + for config_name, cfg in form_data.items(): + if not isinstance(cfg, dict): + continue + metrics = cfg.get("metrics") if not isinstance(metrics, dict): continue thresholds = {} for metric_path, values in metrics.items(): _insert_threshold_metric(thresholds, metric_path, values) - result[config_name] = {"thresholds": thresholds} + entry = {"thresholds": thresholds} + if cfg.get("owner"): + entry["owner"] = cfg["owner"] + result[config_name] = entry return result diff --git a/hbd/server/templates/settings.html b/hbd/server/templates/settings.html index fcd30af..384bd9c 100644 --- a/hbd/server/templates/settings.html +++ b/hbd/server/templates/settings.html @@ -1556,10 +1556,15 @@ const cfgsContainer = document.getElementById('thresh-cfgs-' + sectionId); cfgsContainer.querySelectorAll('.thresh-cfg-card').forEach(card => { + if (card.dataset.readonly === 'true') return; const configName = card.dataset.configName || (card.querySelector('.new-config-name')?.value || '').trim(); if (!configName) return; - configs[configName] = readMetrics(card); + const ownerInp = card.querySelector('.thresh-owner'); + configs[configName] = { + owner: ownerInp ? ownerInp.value.trim() : '', + metrics: readMetrics(card), + }; }); _staged['thresholds'] = configs; diff --git a/tests/test_http_config_api.py b/tests/test_http_config_api.py index 2a0d82c..83f5224 100644 --- a/tests/test_http_config_api.py +++ b/tests/test_http_config_api.py @@ -171,3 +171,28 @@ def test_write_path_preserves_oauth_client_secret(tmp_path): assert data2["oauth"]["gitea"]["client_secret"] == original_secret, ( f"Expected original secret preserved, got: {data2['oauth']['gitea']['client_secret']!r}" ) + + +# ---- threshold form payload shape ---- + +def test_build_threshold_configs_new_shape_with_owner(): + form = { + "servers": { + "owner": "alice", + "metrics": {"cpu_monitor.load_15min": {"operator": ">", "warning": 4.0, + "critical": 8.0, "enabled": True}}, + }, + } + result = http._build_threshold_configs_from_form(form) + assert result["servers"]["owner"] == "alice" + assert result["servers"]["thresholds"]["cpu_monitor"]["load_15min"]["warning"] == 4.0 + + +def test_build_threshold_configs_empty_owner_means_global(): + form = {"servers": {"owner": "", "metrics": {"rtt": {"warning": 100.0}}}} + result = http._build_threshold_configs_from_form(form) + assert "owner" not in result["servers"] + + +def test_build_threshold_configs_ignores_entries_without_metrics(): + assert http._build_threshold_configs_from_form({"bad": {"owner": "x"}}) == {}