Module: check_mk
Branch: master
Commit: 96d6caf43039dedaed41e9929dc5754fa14254fe
URL:
http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=96d6caf43039de…
Author: Lars Michelsen <lm(a)mathias-kettner.de>
Date: Sat Apr 6 16:42:10 2019 +0200
Add completion suggestion for label input fields
* When typing into the label input fields dropdowns will show
suggestions based on the already existing labels and the already
inserted text.
* Depending on the "world" the input field is operating on different
sources need to be used for completion.
* When working with a label filter in a status view the labels need to
be fetched from livestatus. It may only suggest labels of the activated
configuration.
* When working in WATO the labels need to be based on the confiuration
that is currently being edited. It needs to work with the labels of
all sources (explicit, rulesets and discovered lables). This needs
to be implemented in the next steps.
CMK-1934
Change-Id: Ic5870c6159a4a83b61731ed73f9cfcb7ac8271f6
---
cmk/gui/plugins/visuals/filters.py | 2 +-
cmk/gui/plugins/wato/builtin_attributes.py | 2 +-
cmk/gui/plugins/wato/check_mk_configuration.py | 2 +
cmk/gui/valuespec.py | 53 ++++++++++++++++++++++++--
tests/unit/cmk/gui/test_pages.py | 1 +
web/htdocs/js/modules/forms.js | 39 ++++++++++++++++++-
6 files changed, 93 insertions(+), 6 deletions(-)
diff --git a/cmk/gui/plugins/visuals/filters.py b/cmk/gui/plugins/visuals/filters.py
index 152c56a..e9cc5e5 100644
--- a/cmk/gui/plugins/visuals/filters.py
+++ b/cmk/gui/plugins/visuals/filters.py
@@ -2625,7 +2625,7 @@ class ABCLabelFilter(Filter):
return [(self.htmlvars[0], row[self._column])]
def _valuespec(self):
- return Labels()
+ return Labels(world=Labels.World.CORE)
def display(self):
self._valuespec().render_input(self._var_prefix, self._current_value())
diff --git a/cmk/gui/plugins/wato/builtin_attributes.py
b/cmk/gui/plugins/wato/builtin_attributes.py
index db1ebb2..47e1838 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()
+ return Labels(world=Labels.World.CONFIG)
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 f99d9a6..1ce8637 100644
--- a/cmk/gui/plugins/wato/check_mk_configuration.py
+++ b/cmk/gui/plugins/wato/check_mk_configuration.py
@@ -1343,6 +1343,7 @@ class RulespecServiceLabels(ServiceRulespec):
@property
def valuespec(self):
return Labels(
+ world=Labels.World.CONFIG,
title=_("Service labels"),
help=_("Use this ruleset to assign labels to service of your
choice."),
)
@@ -1365,6 +1366,7 @@ class RulespecHostLabels(HostRulespec):
@property
def valuespec(self):
return Labels(
+ world=Labels.World.CONFIG,
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 399ad45..856d607 100644
--- a/cmk/gui/valuespec.py
+++ b/cmk/gui/valuespec.py
@@ -63,7 +63,7 @@ import cmk.utils.defines as defines
import cmk.gui.forms as forms
import cmk.gui.utils as utils
from cmk.gui.i18n import _
-from cmk.gui.pages import page_registry, Page
+from cmk.gui.pages import page_registry, Page, AjaxPage
from cmk.gui.globals import html
from cmk.gui.htmllib import HTML
from cmk.gui.exceptions import MKUserError, MKGeneralException
@@ -4234,7 +4234,12 @@ class TextOrRegExpUnicode(TextOrRegExp):
class Labels(ValueSpec):
"""Valuespec to render and input a collection of object
labels"""
- def __init__(self, *args, **kwargs):
+ class World(Enum):
+ CONFIG = "config"
+ CORE = "core"
+
+ def __init__(self, world, *args, **kwargs):
+ self._world = world
kwargs.setdefault("help", "")
kwargs["help"] += _("Labels need to be in the format
<tt>[KEY]:[VALUE]</tt>. "
"For example <tt>os:windows</tt>.")
@@ -4255,13 +4260,55 @@ class Labels(ValueSpec):
html.help(self.help())
html.text_input(
varprefix,
- default_value=json.dumps(["%s:%s" % e for e in
value.items()]).decode("utf-8"),
+
default_value=json.dumps(_encode_labels_for_tagify(value.items())).decode("utf-8"),
cssclass="labels",
attrs={
"placeholder": _("Add some label"),
+ "data-world": self._world.value,
})
+(a)page_registry.register_page("ajax_autocomplete_labels")
+class PageAutocompleteLabels(AjaxPage):
+ """Return all known labels to support tagify label input dropdown
completion"""
+
+ def page(self):
+ request = html.get_request()
+ return _encode_labels_for_tagify(
+ self._get_labels(Labels.World(request["world"]),
request["search_label"]))
+
+ def _get_labels(self, world, search_label):
+ if world == Labels.World.CONFIG:
+ return self._get_labels_from_config(search_label)
+
+ if world == Labels.World.CORE:
+ return self._get_labels_from_core(search_label)
+
+ raise NotImplementedError()
+
+ def _get_labels_from_config(self, search_label):
+ return [] # TODO: Implement me
+
+ # 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):
+ import cmk.gui.sites as sites
+ query = ("GET services\n" \
+ "Cache: reload\n" \
+ "Columns: host_labels labels\n")
+
+ labels = set()
+ for row in sites.live().query(query):
+ labels.update(row[0].items())
+ labels.update(row[1].items())
+
+ return list(labels)
+
+
+def _encode_labels_for_tagify(labels):
+ return [{"value": "%s:%s" % e} for e in labels]
+
+
class IconSelector(ValueSpec):
def __init__(self, **kwargs):
ValueSpec.__init__(self, **kwargs)
diff --git a/tests/unit/cmk/gui/test_pages.py b/tests/unit/cmk/gui/test_pages.py
index adb6e4a..f3da112 100644
--- a/tests/unit/cmk/gui/test_pages.py
+++ b/tests/unit/cmk/gui/test_pages.py
@@ -10,6 +10,7 @@ def test_registered_pages():
'add_bookmark',
'ajax_activation_state',
'ajax_add_visual',
+ 'ajax_autocomplete_labels',
'ajax_backup_job_state',
'ajax_dashlet_pos',
'ajax_delete_user_notification',
diff --git a/web/htdocs/js/modules/forms.js b/web/htdocs/js/modules/forms.js
index 68d68d2..9ad2c97 100644
--- a/web/htdocs/js/modules/forms.js
+++ b/web/htdocs/js/modules/forms.js
@@ -27,6 +27,7 @@ import "select2";
import Tagify from "@yaireo/tagify";
import * as utils from "utils";
+import * as ajax from "ajax";
export function enable_dynamic_form_elements(container=null) {
enable_select2_dropdowns(container);
@@ -54,9 +55,45 @@ function enable_label_input_fields(container) {
let elements = container.querySelectorAll("input.labels");
elements.forEach(element => {
- new Tagify(element, {
+ let ajax_obj;
+ let tagify = new Tagify(element, {
pattern: /^[^:]+:[^:]+$/,
});
+
+ let world = element.getAttribute("data-world");
+
+ // Realize the auto completion dropdown field by using an ajax call
+ tagify.on("input", function(e) {
+ var value = e.detail;
+ tagify.settings.whitelist.length = 0; // reset the whitelist
+
+ var post_data = "request=" + encodeURIComponent(JSON.stringify({
+ "search_label": value,
+ "world": world,
+ }));
+
+ if (ajax_obj)
+ ajax_obj.abort();
+
+ ajax_obj = ajax.call_ajax("ajax_autocomplete_labels.py", {
+ method: "POST",
+ post_data: post_data,
+ response_handler: function(handler_data, ajax_response) {
+ var response = JSON.parse(ajax_response);
+ if (response.result_code != 0) {
+ console.log("Error [" + response.result_code + "]:
" + response.result); // eslint-disable-line
+ return;
+ }
+
+ handler_data.tagify.settings.whitelist = response.result;
+ handler_data.tagify.dropdown.show.call(handler_data.tagify,
handler_data.value);
+ },
+ handler_data: {
+ value: value,
+ tagify: tagify,
+ },
+ });
+ });
});
}