Fix typecheck idiom in cmk/gui/{plugins,wato}
[check_mk.git] / cmk / gui / wato / pages / check_catalog.py
blob912c91af67fdac0419060efc29199a340f646bd9
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 import re
29 import cmk.man_pages as man_pages
31 import cmk.gui.watolib as watolib
32 import cmk.gui.table as table
33 from cmk.gui.htmllib import HTML
34 from cmk.gui.exceptions import MKUserError
35 from cmk.gui.i18n import _
36 from cmk.gui.globals import html
38 from cmk.gui.valuespec import (
39 ID,)
41 from cmk.gui.plugins.wato.utils.main_menu import (
42 MainMenu,
43 MenuItem,
46 from cmk.gui.plugins.wato import (
47 WatoMode,
48 mode_registry,
49 search_form,
50 get_search_expression,
51 global_buttons,
56 # .--Check Plugins-------------------------------------------------------.
57 # | ____ _ _ ____ _ _ |
58 # | / ___| |__ ___ ___| | __ | _ \| |_ _ __ _(_)_ __ ___ |
59 # | | | | '_ \ / _ \/ __| |/ / | |_) | | | | |/ _` | | '_ \/ __| |
60 # | | |___| | | | __/ (__| < | __/| | |_| | (_| | | | | \__ \ |
61 # | \____|_| |_|\___|\___|_|\_\ |_| |_|\__,_|\__, |_|_| |_|___/ |
62 # | |___/ |
63 # +----------------------------------------------------------------------+
64 # | Catalog of check plugins |
65 # '----------------------------------------------------------------------'
68 @mode_registry.register
69 class ModeCheckPlugins(WatoMode):
70 @classmethod
71 def name(cls):
72 return "check_plugins"
74 @classmethod
75 def permissions(cls):
76 return []
78 def _from_vars(self):
79 self._search = get_search_expression()
80 self._topic = html.get_ascii_input("topic")
81 if self._topic and not self._search:
82 if not re.match("^[a-zA-Z0-9_./]+$", self._topic):
83 raise MKUserError("topic", _("Invalid topic"))
85 self._path = tuple(self._topic.split("/")) # e.g. [ "hw", "network" ]
86 else:
87 self._path = tuple()
89 for comp in self._path:
90 ID().validate_value(comp, None) # Beware against code injection!
92 self._manpages = self._get_check_catalog()
93 self._titles = man_pages.man_page_catalog_titles()
95 self._has_second_level = None
96 if self._topic and not self._search:
97 for t, has_second_level, title, _helptext in self._man_page_catalog_topics():
98 if t == self._path[0]:
99 self._has_second_level = has_second_level
100 self._topic_title = title
101 break
103 if len(self._path) == 2:
104 self._topic_title = self._titles.get(self._path[1], self._path[1])
106 def title(self):
107 if self._topic and not self._search:
108 heading = "%s - %s" % ( _("Catalog of Check Plugins"), self._topic_title )
109 elif self._search:
110 heading = html.render_text("%s: %s" % (_("Check plugins matching"), self._search))
111 else:
112 heading = _("Catalog of Check Plugins")
113 return heading
115 def buttons(self):
116 global_buttons()
117 if self._topic:
118 if len(self._path) == 2:
119 back_url = html.makeuri([("topic", self._path[0])])
120 else:
121 back_url = html.makeuri([("topic", "")])
122 html.context_button(_("Back"), back_url, "back")
124 def page(self):
125 html.help(_("This catalog of check plugins gives you a complete listing of all plugins "
126 "that are shipped with your Check_MK installation. It also allows you to "
127 "access the rule sets for configuring the parameters of the checks and to "
128 "manually create services in case you cannot or do not want to rely on the "
129 "automatic service discovery."))
131 search_form( "%s: " % _("Search for check plugins"), "check_plugins" )
133 # The maxium depth of the catalog paths is 3. The top level is being rendered
134 # like the WATO main menu. The second and third level are being rendered like
135 # the global settings.
137 if self._topic and not self._search:
138 self._render_manpage_topic()
140 elif self._search:
141 for path, manpages in self._get_manpages_after_search():
142 self._render_manpage_list(manpages, path, self._titles.get(path, path))
144 else:
145 menu = MainMenu()
146 for topic, _has_second_level, title, helptext in self._man_page_catalog_topics():
147 menu.add_item(
148 MenuItem(
149 mode_or_url=html.makeuri([("topic", topic)]),
150 title=title,
151 icon="plugins_" + topic,
152 permission=None,
153 description=helptext))
154 menu.show()
156 def _get_manpages_after_search(self):
157 collection = {}
158 handled_check_names = set([])
160 # searches in {"name" : "asd", "title" : "das", ...}
161 def get_matched_entry(entry):
162 if isinstance(entry, dict):
163 name = entry.get("name", "")
164 if isinstance(name, str):
165 name = name.decode("utf8")
167 title = entry.get("title", "")
168 if isinstance(title, str):
169 title = title.decode("utf8")
170 if self._search in name.lower() or self._search in title.lower():
171 return entry
173 return None
175 def check_entries(key, entries):
176 if isinstance(entries, list):
177 these_matches = []
178 for entry in entries:
179 match = get_matched_entry(entry)
180 if match:
181 these_matches.append(match)
183 if these_matches:
184 collection.setdefault(key, [])
185 # avoid duplicates due to the fact that a man page can have more than
186 # one places in the global tree of man pages.
187 for match in these_matches:
188 name = match.get("name")
189 if name and name in handled_check_names:
190 continue # avoid duplicate
191 else:
192 collection[key].append(match)
193 if name:
194 handled_check_names.add(name)
196 elif isinstance(entries, dict):
197 for k, subentries in entries.items():
198 check_entries(k, subentries)
200 for key, entries in self._manpages.items():
201 check_entries(key, entries)
203 return collection.items()
205 def _get_check_catalog(self):
206 def path_prefix_matches(p, op):
207 if op and not p:
208 return False
209 elif not op:
210 return True
211 return p[0] == op[0] and path_prefix_matches(p[1:], op[1:])
213 def strip_manpage_entry(entry):
214 return dict([(k, v) for (k, v) in entry.items() if k in ["name", "agents", "title"]])
216 tree = {}
217 if len(self._path) > 0:
218 only_path = tuple(self._path)
219 else:
220 only_path = ()
222 for path, entries in man_pages.load_man_page_catalog().items():
223 if not path_prefix_matches(path, only_path):
224 continue
225 subtree = tree
226 for component in path[:-1]:
227 subtree = subtree.setdefault(component, {})
228 subtree[path[-1]] = map(strip_manpage_entry, entries)
230 for p in only_path:
231 try:
232 tree = tree[p]
233 except KeyError:
234 pass
236 return tree
238 def _render_manpage_topic(self):
239 if isinstance(self._manpages, list):
240 self._render_manpage_list(self._manpages, self._path[-1], self._topic_title)
241 return
243 if len(self._path) == 1 and self._has_second_level:
244 # For some topics we render a second level in the same optic as the first level
245 menu = MainMenu()
246 for path_comp, subnode in self._manpages.items():
247 url = html.makeuri([("topic", "%s/%s" % (self._path[0], path_comp))])
248 title = self._titles.get(path_comp, path_comp)
249 helptext = self._get_check_plugin_stats(subnode)
251 menu.add_item(
252 MenuItem(
253 mode_or_url=url,
254 title=title,
255 icon="check_plugins",
256 permission=None,
257 description=helptext,
259 menu.show()
261 else:
262 # For the others we directly display the tables
263 entries = []
264 for path_comp, subnode in self._manpages.items():
265 title = self._titles.get(path_comp, path_comp)
266 entries.append((title, subnode, path_comp))
268 entries.sort(cmp=lambda a, b: cmp(a[0].lower(), b[0].lower()))
270 for title, subnode, path_comp in entries:
271 self._render_manpage_list(subnode, path_comp, title)
273 def _get_check_plugin_stats(self, subnode):
274 if isinstance(subnode, list):
275 num_cats = 1
276 num_plugins = len(subnode)
277 else:
278 num_cats = len(subnode)
279 num_plugins = 0
280 for subcat in subnode.values():
281 num_plugins += len(subcat)
283 text = ""
284 if num_cats > 1:
285 text += "%d %s<br>" % (num_cats, _("sub categories"))
286 text += "%d %s" % (num_plugins, _("check plugins"))
287 return text
289 def _render_manpage_list(self, manpage_list, path_comp, heading):
290 def translate(t):
291 return self._titles.get(t, t)
293 html.h2(heading)
294 table.begin(searchable=False, sortable=False, css="check_catalog")
295 for entry in sorted(manpage_list, cmp=lambda a, b: cmp(a["title"], b["title"])):
296 if not isinstance(entry, dict):
297 continue
298 table.row()
299 url = html.makeuri([("mode", "check_manpage"), ("check_type", entry["name"]),
300 ("back", html.makeuri([]))])
301 table.cell(_("Type of Check"), "<a href='%s'>%s</a>" % (url, entry["title"]), css="title")
302 table.cell(_("Plugin Name"), "<tt>%s</tt>" % entry["name"], css="name")
303 table.cell(_("Agents"), ", ".join(map(translate, sorted(entry["agents"]))), css="agents")
304 table.end()
306 def _man_page_catalog_topics(self):
307 # topic, has_second_level, title, description
308 return [
309 ("hw", True, _("Appliances, other dedicated hardware"),
310 _("Switches, load balancers, storage, UPSes, "
311 "environmental sensors, etc. ")),
313 ("os", True, _("Operating systems"),
314 _("Plugins for operating systems, things "
315 "like memory, CPU, filesystems, etc.")),
317 ("app", False, _("Applications"),
318 _("Monitoring of applications such as "
319 "processes, services or databases")),
321 ("agentless", False, _("Networking checks without agent"),
322 _("Plugins that directly check networking "
323 "protocols like HTTP or IMAP")),
325 ("generic", False, _("Generic check plugins"),
326 _("Plugins for local agent extensions or "
327 "communication with the agent in general")),
331 @mode_registry.register
332 class ModeCheckManPage(WatoMode):
333 @classmethod
334 def name(cls):
335 return "check_manpage"
337 @classmethod
338 def permissions(cls):
339 return []
341 def _from_vars(self):
342 self._check_type = html.get_ascii_input("check_type")
344 # TODO: There is one check "sap.value-groups" which will be renamed to "sap.value_groups".
345 # As long as the old one is available, allow a minus here.
346 if not re.match("^[-a-zA-Z0-9_.]+$", self._check_type):
347 raise MKUserError("check_type", _("Invalid check type"))
349 # TODO: remove call of automation and then the automation. This can be done once the check_info
350 # data is also available in the "cmk." module because the get-check-manpage automation not only
351 # fetches the man page. It also contains info from check_info. What a hack.
352 self._manpage = watolib.check_mk_local_automation("get-check-manpage", [self._check_type])
353 if self._manpage == None:
354 raise MKUserError(None, _("There is no manpage for this check."))
356 def title(self):
357 return _("Check plugin manual page") + " - " + self._manpage["header"]["title"]
359 def buttons(self):
360 global_buttons()
361 if html.has_var("back"):
362 back_url = html.get_url_input("back")
363 html.context_button(_("Back"), back_url, "back")
365 html.context_button(_("All Check Plugins"), html.makeuri_contextless([("mode", "check_plugins")]), "check_plugins")
367 if self._check_type.startswith("check_"):
368 command = "check_mk_active-" + self._check_type[6:]
369 else:
370 command = "check_mk-" + self._check_type
372 url = html.makeuri_contextless([("view_name", "searchsvc"), ("check_command", command),
373 ("filled_in", "filter")],
374 filename="view.py")
375 html.context_button(_("Find usage"), url, "status")
377 # TODO
378 # We could simply detect on how many hosts and services this plugin
379 # is currently in use (Livestatus query) and display this information
380 # together with a link for searching. Then we can remove the dumb context
381 # button, that will always be shown - even if the plugin is not in use.
382 def page(self):
383 html.open_table(class_=["data", "headerleft"])
385 html.open_tr()
386 html.th(_("Title"))
387 html.open_td()
388 html.b(self._manpage["header"]["title"])
389 html.close_td()
390 html.close_tr()
392 html.open_tr()
393 html.th(_("Name of plugin"))
394 html.open_td()
395 html.tt(self._check_type)
396 html.close_td()
397 html.close_tr()
399 html.open_tr()
400 html.th(_("Description"))
401 html.td(self._manpage_text(self._manpage["header"]["description"]))
402 html.close_tr()
404 def show_ruleset(varname):
405 if watolib.g_rulespecs.exists(varname):
406 rulespec = watolib.g_rulespecs.get(varname)
407 url = html.makeuri_contextless([("mode", "edit_ruleset"), ("varname", varname)])
408 param_ruleset = html.render_a(rulespec.title, url)
409 html.open_tr()
410 html.th(_("Parameter rule set"))
411 html.open_td()
412 html.icon_button(url, _("Edit parameter rule set for this check type"), "check_parameters")
413 html.write(param_ruleset)
414 html.close_td()
415 html.close_tr()
416 html.open_tr()
417 html.th(_("Example for Parameters"))
418 html.open_td()
419 vs = rulespec.valuespec
420 vs.render_input("dummy", vs.default_value())
421 html.close_td()
422 html.close_tr()
424 if self._manpage["type"] == "check_mk":
425 html.open_tr()
426 html.th(_("Service name"))
427 html.td(HTML(self._manpage["service_description"].replace("%s", "&#9744;")))
428 html.close_tr()
430 if self._manpage.get("group"):
431 group = self._manpage["group"]
432 varname = "checkgroup_parameters:" + group
433 show_ruleset(varname)
435 else:
436 varname = "active_checks:" + self._check_type[6:]
437 show_ruleset(varname)
439 html.close_table()
441 def _manpage_text(self, text):
442 html_code = text.replace("<br>", "\n")\
443 .replace("<", "&lt;")\
444 .replace(">", "&gt;")
445 html_code = re.sub("{(.*?)}", "<tt>\\1</tt>", html_code)
446 html_code = re.sub("\n\n+", "<p>", html_code)
447 return html_code