From 719ad392751e205a3599cbbc3dd24dad15e46e1e Mon Sep 17 00:00:00 2001 From: Lars Michelsen Date: Thu, 11 Apr 2019 12:13:07 +0200 Subject: [PATCH] Add specific visualization for labels depending on their source Labels can be created explicit by host config, rulesets and can be discovered automatically by Check_MK. Depending on the source of a label it is now colorized in different ways to make the origin of the label transparent to the user. We may need to work a bit on the colors. That will be the difficult part ;-). CMK-1817 Change-Id: I4c52509408159ffd97c61150126f9ea94f1863c0 --- cmk/gui/plugins/views/__init__.py | 1 + cmk/gui/plugins/views/inventory.py | 5 ++- cmk/gui/plugins/views/painters.py | 17 +++++++--- cmk/gui/plugins/views/utils.py | 8 ++++- cmk/gui/plugins/wato/builtin_attributes.py | 2 +- cmk/gui/plugins/wato/check_mk_configuration.py | 2 ++ cmk/gui/valuespec.py | 16 ++++++++-- cmk/gui/view_utils.py | 17 ++++++---- cmk/gui/wato/pages/folders.py | 7 +++- cmk/gui/wato/pages/object_parameters.py | 11 ++++--- cmk_base/automations/check_mk.py | 21 +++++++++--- cmk_base/config.py | 10 ++++++ tests/unit/cmk_base/test_config.py | 12 +++++++ tests/unit/cmk_base/test_unit_automations.py | 22 +++++++++++-- web/htdocs/themes/classic/scss/_pages.scss | 44 ++++++++++++++++++++------ web/htdocs/themes/facelift/scss/_pages.scss | 44 ++++++++++++++++++++------ 16 files changed, 193 insertions(+), 46 deletions(-) diff --git a/cmk/gui/plugins/views/__init__.py b/cmk/gui/plugins/views/__init__.py index 6c4fe54503..fcd7d0c6b2 100644 --- a/cmk/gui/plugins/views/__init__.py +++ b/cmk/gui/plugins/views/__init__.py @@ -40,6 +40,7 @@ from cmk.gui.plugins.views.utils import ( get_tag_groups, render_tag_groups, get_labels, + get_label_sources, render_labels, get_permitted_views, cmp_custom_variable, diff --git a/cmk/gui/plugins/views/inventory.py b/cmk/gui/plugins/views/inventory.py index 42625ea9e5..6620af041d 100644 --- a/cmk/gui/plugins/views/inventory.py +++ b/cmk/gui/plugins/views/inventory.py @@ -517,7 +517,10 @@ def inv_paint_csv_labels(csv_list): @decorate_inv_paint def inv_paint_cmk_label(label): - return "labels", render_labels({label[0]: label[1]}, object_type="host", with_links=True) + return "labels", render_labels({label[0]: label[1]}, + object_type="host", + with_links=True, + label_sources={label[0]: "discovered"}) @decorate_inv_paint diff --git a/cmk/gui/plugins/views/painters.py b/cmk/gui/plugins/views/painters.py index a061b0c714..640284ff0e 100644 --- a/cmk/gui/plugins/views/painters.py +++ b/cmk/gui/plugins/views/painters.py @@ -74,6 +74,7 @@ from cmk.gui.plugins.views import ( get_tag_groups, render_labels, get_labels, + get_label_sources, ) # .--Painter Options-----------------------------------------------------. @@ -5246,14 +5247,18 @@ class PainterHostLabels(Painter): @property def columns(self): - return ["host_labels"] + return ["host_labels", "host_sources"] @property def sorter(self): return "host_labels" def render(self, row, cell): - return "", render_labels(get_labels(row, "host"), "host", with_links=True) + return "", render_labels( + get_labels(row, "host"), + "host", + with_links=True, + label_sources=get_label_sources(row, "host")) @painter_registry.register @@ -5272,11 +5277,15 @@ class PainterServiceLabels(Painter): @property def columns(self): - return ["service_labels"] + return ["service_labels", "host_sources"] @property def sorter(self): return "service_labels" def render(self, row, cell): - return "", render_labels(get_labels(row, "service"), "service", with_links=True) + return "", render_labels( + get_labels(row, "service"), + "service", + with_links=True, + label_sources=get_label_sources(row, "service")) diff --git a/cmk/gui/plugins/views/utils.py b/cmk/gui/plugins/views/utils.py index 49032c7750..4c8304c8e9 100644 --- a/cmk/gui/plugins/views/utils.py +++ b/cmk/gui/plugins/views/utils.py @@ -997,11 +997,17 @@ def get_tag_groups(row, what): def get_labels(row, what): - # Sites with old versions that don't have the tag groups column return + # Sites with old versions that don't have the labels column return # None for this field. Convert this to the default value return row.get("%s_labels" % what, {}) or {} +def get_label_sources(row, what): + # Sites with old versions that don't have the sources column return + # None for this field. Convert this to the default value + return row.get("%s_sources" % what, {}) or {} + + def get_graph_timerange_from_painter_options(): painter_options = PainterOptions.get_instance() value = painter_options.get("pnp_timerange") diff --git a/cmk/gui/plugins/wato/builtin_attributes.py b/cmk/gui/plugins/wato/builtin_attributes.py index 47e18381cb..cc898f5cbc 100644 --- a/cmk/gui/plugins/wato/builtin_attributes.py +++ b/cmk/gui/plugins/wato/builtin_attributes.py @@ -918,7 +918,7 @@ class HostAttributeLabels(ABCHostAttributeValueSpec): return True def valuespec(self): - return Labels(world=Labels.World.CONFIG) + return Labels(world=Labels.World.CONFIG, label_source=Labels.Source.EXPLICIT) def filter_matches(self, crit, value, hostname): return set(value).issuperset(set(crit)) diff --git a/cmk/gui/plugins/wato/check_mk_configuration.py b/cmk/gui/plugins/wato/check_mk_configuration.py index bab398a83b..6cc8294fcf 100644 --- a/cmk/gui/plugins/wato/check_mk_configuration.py +++ b/cmk/gui/plugins/wato/check_mk_configuration.py @@ -1344,6 +1344,7 @@ class RulespecServiceLabels(ServiceRulespec): def valuespec(self): return Labels( world=Labels.World.CONFIG, + label_source=Labels.Source.RULESET, title=_("Service labels"), help=_("Use this ruleset to assign labels to service of your choice."), ) @@ -1367,6 +1368,7 @@ class RulespecHostLabels(HostRulespec): def valuespec(self): return Labels( world=Labels.World.CONFIG, + label_source=Labels.Source.RULESET, title=_("Host labels"), help=_("Use this ruleset to assign labels to hosts of your choice."), ) diff --git a/cmk/gui/valuespec.py b/cmk/gui/valuespec.py index 04032a7025..fef98e4b53 100644 --- a/cmk/gui/valuespec.py +++ b/cmk/gui/valuespec.py @@ -4238,12 +4238,19 @@ class Labels(ValueSpec): CONFIG = "config" CORE = "core" - def __init__(self, world, *args, **kwargs): + class Source(Enum): + EXPLICIT = "explicit" + RULESET = "ruleset" + DISCOVERED = "discovered" + + def __init__(self, world, label_source=None, **kwargs): self._world = world + # Set this source to mark the labels that have no explicit label source set + self._label_source = label_source kwargs.setdefault("help", "") kwargs["help"] += _("Labels need to be in the format [KEY]:[VALUE]. " "For example os:windows.") - super(Labels, self).__init__(*args, **kwargs) + super(Labels, self).__init__(**kwargs) def canonical_value(self): return {} @@ -4254,7 +4261,9 @@ class Labels(ValueSpec): def value_to_text(self, value): from cmk.gui.view_utils import render_labels - return render_labels(value, "host", with_links=False) + label_sources = {k: self._label_source.value for k in value.keys() + } if self._label_source else {} + return render_labels(value, "host", with_links=False, label_sources=label_sources) def render_input(self, varprefix, value): html.help(self.help()) @@ -4289,6 +4298,7 @@ class PageAutocompleteLabels(AjaxPage): def _get_labels_from_config(self, search_label): return [] # TODO: Implement me + # TODO: Provide information about the label source # Would be better to optimize this kind of query somehow. The best we can # do without extending livestatus is to use the Cache header for liveproxyd def _get_labels_from_core(self, search_label): diff --git a/cmk/gui/view_utils.py b/cmk/gui/view_utils.py index 69f234934c..c8de64ba4a 100644 --- a/cmk/gui/view_utils.py +++ b/cmk/gui/view_utils.py @@ -124,27 +124,30 @@ def check_limit(rows, limit, user): return True -def render_labels(labels, object_type, with_links): - return _render_tag_groups_or_labels(labels, object_type, with_links, label_type="label") +def render_labels(labels, object_type, with_links, label_sources): + return _render_tag_groups_or_labels( + labels, object_type, with_links, label_type="label", label_sources=label_sources) def render_tag_groups(tag_groups, object_type, with_links): - return _render_tag_groups_or_labels(tag_groups, object_type, with_links, label_type="tag_group") + return _render_tag_groups_or_labels( + tag_groups, object_type, with_links, label_type="tag_group", label_sources={}) -def _render_tag_groups_or_labels(entries, object_type, with_links, label_type): +def _render_tag_groups_or_labels(entries, object_type, with_links, label_type, label_sources): elements = [ - _render_tag_group(tg_id, tag, object_type, with_links, label_type) + _render_tag_group(tg_id, tag, object_type, with_links, label_type, + label_sources.get(tg_id, "unspecified")) for tg_id, tag in sorted(entries.items()) ] return html.render_tags( HTML("").join(elements), class_=["tagify", label_type, "display"], readonly="true") -def _render_tag_group(tg_id, tag, object_type, with_link, label_type): +def _render_tag_group(tg_id, tag, object_type, with_link, label_type, label_source): span = html.render_tag( html.render_div(html.render_span("%s:%s" % (tg_id, tag), class_=["tagify__tag-text"])), - class_=["tagify--noAnim"]) + class_=["tagify--noAnim", label_source]) if not with_link: return span diff --git a/cmk/gui/wato/pages/folders.py b/cmk/gui/wato/pages/folders.py index e4321c93a1..ac6ae96cbf 100644 --- a/cmk/gui/wato/pages/folders.py +++ b/cmk/gui/wato/pages/folders.py @@ -580,7 +580,12 @@ class ModeFolder(WatoMode): table.cell(_("Explicit labels"), css="tag-ellipsis") labels, show_all_code = self._limit_labels(host.labels()) - html.write(cmk.gui.view_utils.render_labels(labels, "host", with_links=False)) + html.write( + cmk.gui.view_utils.render_labels( + labels, + "host", + with_links=False, + label_sources={k: "explicit" for k in labels.keys()})) html.write(show_all_code) # Located in folder diff --git a/cmk/gui/wato/pages/object_parameters.py b/cmk/gui/wato/pages/object_parameters.py index de8252d910..cd23e0ec7d 100644 --- a/cmk/gui/wato/pages/object_parameters.py +++ b/cmk/gui/wato/pages/object_parameters.py @@ -146,7 +146,7 @@ class ModeObjectParameters(WatoMode): return forms.header(_("Host information"), isopen=True, narrow=True, css="rulesettings") - self._show_labels(host_info["labels"], "host") + self._show_labels(host_info["labels"], "host", host_info["label_sources"]) def _show_service_info(self, all_rulesets): serviceinfo = watolib.check_mk_automation(self._host.site_id(), "analyse-service", @@ -272,9 +272,10 @@ class ModeObjectParameters(WatoMode): html.close_tr() html.close_table() - self._show_labels(serviceinfo.get("labels", []), "service") + self._show_labels( + serviceinfo.get("labels", {}), "service", serviceinfo.get("label_sources", {})) - def _show_labels(self, labels, object_type): + def _show_labels(self, labels, object_type, label_sources): forms.section(_("Effective labels")) html.open_table(class_="setting") html.open_tr() @@ -283,7 +284,9 @@ class ModeObjectParameters(WatoMode): html.i(_("Explicit, ruleset, discovered")) html.close_td() html.open_td(class_=["settingvalue", "used"]) - html.write(cmk.gui.view_utils.render_labels(labels, object_type, with_links=False)) + html.write( + cmk.gui.view_utils.render_labels( + labels, object_type, with_links=False, label_sources=label_sources)) html.close_td() html.close_tr() diff --git a/cmk_base/automations/check_mk.py b/cmk_base/automations/check_mk.py index 2a7ea407ea..86cb6c20a4 100644 --- a/cmk_base/automations/check_mk.py +++ b/cmk_base/automations/check_mk.py @@ -576,7 +576,10 @@ class AutomationAnalyseServices(Automation): service_info = self._get_service_info(config_cache, hostname, servicedesc) if service_info: - service_info["labels"] = config_cache.labels_of_service(hostname, servicedesc) + service_info.update({ + "labels": config_cache.labels_of_service(hostname, servicedesc), + "label_sources": config_cache.label_sources_of_service(hostname, servicedesc), + }) return service_info # Determine the type of the check, and how the parameters are being @@ -718,7 +721,10 @@ class AutomationAnalyseHost(Automation): def execute(self, args): host_name = args[0] config_cache = config.get_config_cache() - return {"labels": config_cache.get_host_config(host_name).labels} + return { + "labels": config_cache.get_host_config(host_name).labels, + "label_sources": config_cache.get_host_config(host_name).label_sources, + } automations.register(AutomationAnalyseHost()) @@ -1531,11 +1537,18 @@ class AutomationGetLabelsOf(Automation): config_cache = config.get_config_cache() if object_type == "host": - return {"labels": config_cache.get_host_config(host_name).labels} + return { + "labels": config_cache.get_host_config(host_name).labels, + "label_sources": config_cache.get_host_config(host_name).label_sources, + } if object_type == "service": service_description = args[2].decode("utf-8") - return {"labels": config_cache.labels_of_service(host_name, service_description)} + return { + "labels": config_cache.labels_of_service(host_name, service_description), + "label_sources": config_cache.label_sources_of_service( + host_name, service_description), + } raise NotImplementedError() diff --git a/cmk_base/config.py b/cmk_base/config.py index ce6136da66..9fdc120d60 100644 --- a/cmk_base/config.py +++ b/cmk_base/config.py @@ -2966,6 +2966,16 @@ class ConfigCache(object): labels.update(self.service_extra_conf_merged(hostname, svc_desc, service_label_rules)) return labels + def label_sources_of_service(self, hostname, svc_desc): + """Returns the effective set of service label keys with their source identifier instead of the value + Order and merging logic is equal to labels_of_service()""" + labels = {} + labels.update({ + k: "ruleset" + for k in self.service_extra_conf_merged(hostname, svc_desc, service_label_rules) + }) + return labels + def set_all_processed_hosts(self, all_processed_hosts): self._all_processed_hosts = set(all_processed_hosts) diff --git a/tests/unit/cmk_base/test_config.py b/tests/unit/cmk_base/test_config.py index e996ccb4f5..a4792fbb6b 100644 --- a/tests/unit/cmk_base/test_config.py +++ b/tests/unit/cmk_base/test_config.py @@ -403,6 +403,11 @@ def test_host_config_labels(monkeypatch): "from-rule": "rule1", "from-rule2": "rule2", } + assert cfg.label_sources == { + "explicit": "explicit", + "from-rule": "ruleset", + "from-rule2": "ruleset", + } def test_host_labels_of_host_discovered_labels(monkeypatch, tmp_path): @@ -414,6 +419,7 @@ def test_host_labels_of_host_discovered_labels(monkeypatch, tmp_path): f.write(repr({u"äzzzz": u"eeeeez"}) + "\n") assert config_cache.get_host_config("test-host").labels == {u"äzzzz": u"eeeeez"} + assert config_cache.get_host_config("test-host").label_sources == {u"äzzzz": u"discovered"} def test_service_label_rules_default(): @@ -434,10 +440,16 @@ def test_labels_of_service(monkeypatch): config_cache = _setup_host(monkeypatch, "test-host", ["abc"]) assert config_cache.labels_of_service("xyz", "CPU load") == {} + assert config_cache.label_sources_of_service("xyz", "CPU load") == {} + assert config_cache.labels_of_service("test-host", "CPU load") == { "label1": "val1", "label2": "val2", } + assert config_cache.label_sources_of_service("test-host", "CPU load") == { + "label1": "ruleset", + "label2": "ruleset", + } def test_config_cache_get_host_config(): diff --git a/tests/unit/cmk_base/test_unit_automations.py b/tests/unit/cmk_base/test_unit_automations.py index d509d13b22..39f08145c1 100644 --- a/tests/unit/cmk_base/test_unit_automations.py +++ b/tests/unit/cmk_base/test_unit_automations.py @@ -90,7 +90,14 @@ def test_get_labels_of_host(monkeypatch): }) config.get_config_cache().initialize() - assert automation.execute(["host", "test-host"]) == {"labels": {"explicit": "ding"}} + assert automation.execute(["host", "test-host"]) == { + "labels": { + "explicit": "ding" + }, + "label_sources": { + "explicit": "explicit" + }, + } def test_get_labels_of_service(monkeypatch): @@ -115,6 +122,10 @@ def test_get_labels_of_service(monkeypatch): "labels": { "label1": "val1", "label2": "val2" + }, + "label_sources": { + "label1": "ruleset", + "label2": "ruleset" } } @@ -131,4 +142,11 @@ def test_analyse_host(monkeypatch): }) config.get_config_cache().initialize() - assert automation.execute(["test-host"]) == {"labels": {"explicit": "ding"}} + assert automation.execute(["test-host"]) == { + "labels": { + "explicit": "ding" + }, + "label_sources": { + "explicit": "explicit" + }, + } diff --git a/web/htdocs/themes/classic/scss/_pages.scss b/web/htdocs/themes/classic/scss/_pages.scss index 3c06bfe45f..5500f8cfb2 100644 --- a/web/htdocs/themes/classic/scss/_pages.scss +++ b/web/htdocs/themes/classic/scss/_pages.scss @@ -1535,22 +1535,48 @@ table.data.job_table> tbody > tr > td.job_pid{ text-align: right; } -.tagify { +.tagify, .tagify[readonly] { border: none; tag { margin: 0 0 0 5px; - } - a tag { - cursor: pointer; + > div { + color: #fff; + } + + x { + font-size: 11px; + } + + &:hover:not([readonly]) div::before { + opacity: 0.5; + box-shadow: none; + } + + > div::before { + background: #00a8b5; + box-shadow: none; + } + + &.explicit > div::before { + background: #774898; + } + + &.ruleset > div::before { + background: #de4383; + } + + &.discovered > div::before { + background: #f3ae4b; + } } - tag x { - font-size: 11px; + &:hover { + border-color: none; } -} -.tagify.display[readonly] tag > div::before { - background: #d6d6d6; + a tag { + cursor: pointer; + } } diff --git a/web/htdocs/themes/facelift/scss/_pages.scss b/web/htdocs/themes/facelift/scss/_pages.scss index 10c5d1071c..779bf7e716 100644 --- a/web/htdocs/themes/facelift/scss/_pages.scss +++ b/web/htdocs/themes/facelift/scss/_pages.scss @@ -30,22 +30,48 @@ Boston, MA 02110-1301 USA. styles are not used in the sidebar and in Mobile. */ -.tagify { +.tagify, .tagify[readonly] { border: none; tag { margin: 0 0 0 5px; - } - a tag { - cursor: pointer; + > div { + color: #fff; + } + + x { + font-size: 11px; + } + + &:hover:not([readonly]) div::before { + opacity: 0.5; + box-shadow: none; + } + + > div::before { + background: #00a8b5; + box-shadow: none; + } + + &.explicit > div::before { + background: #774898; + } + + &.ruleset > div::before { + background: #de4383; + } + + &.discovered > div::before { + background: #f3ae4b; + } } - tag x { - font-size: 11px; + &:hover { + border-color: none; } -} -.tagify.display[readonly] tag > div::before { - background: #d6d6d6; + a tag { + cursor: pointer; + } } -- 2.11.4.GIT