def _gen_node(self, tree, height, show_host): leaves: List[Any] = [] for node in tree[3]: if not node[2].get("hidden"): leaves += self._gen_table(node, height - 1, show_host) with html.plugged(): html.open_div(class_="aggr_tree") with self._show_node(tree, show_host): html.write_text(tree[2]["title"]) html.close_div() content = HTML(html.drain()) if leaves: leaves[0][2].append((len(leaves), content)) return leaves
def page(self): with table_element("roles") as table: users = userdb.load_users() for rid, role in sorted(self._roles.items(), key=lambda a: (a[1]["alias"], a[0])): table.row() # Actions table.cell(_("Actions"), css="buttons") edit_url = watolib.folder_preserving_link([("mode", "edit_role"), ("edit", rid)]) clone_url = make_action_link([("mode", "roles"), ("_clone", rid)]) delete_url = make_confirm_link( url=make_action_link([("mode", "roles"), ("_delete", rid)]), message=_("Do you really want to delete the role %s?") % rid, ) html.icon_button(edit_url, _("Properties"), "edit") html.icon_button(clone_url, _("Clone"), "clone") if not role.get("builtin"): html.icon_button(delete_url, _("Delete this role"), "delete") # ID table.text_cell(_("Name"), rid) # Alias table.text_cell(_("Alias"), role["alias"]) # Type table.cell(_("Type"), _("builtin") if role.get("builtin") else _("custom")) # Modifications table.cell( _("Modifications"), "<span title='%s'>%s</span>" % (_("That many permissions do not use the factory defaults."), len(role["permissions"]))) # Users table.cell( _("Users"), HTML(", ").join([ html.render_a( user.get("alias", user_id), watolib.folder_preserving_link([("mode", "edit_user"), ("edit", user_id)])) for (user_id, user) in users.items() if rid in user["roles"] ]))
def render_graph_html(graph_artwork, graph_data_range, graph_render_options) -> HTML: graph_render_options = artwork.add_default_render_options( graph_render_options) with output_funnel.plugged(): _show_graph_html_content(graph_artwork, graph_data_range, graph_render_options) html_code = HTML(output_funnel.drain()) return html.render_javascript( 'cmk.graphs.create_graph(%s, %s, %s, %s);' % (json.dumps(html_code), json.dumps(graph_artwork), json.dumps(graph_render_options), json.dumps( graph_ajax_context(graph_artwork, graph_data_range, graph_render_options))))
def render_werk_description(werk) -> HTML: with output_funnel.plugged(): html.open_p() in_list = False in_code = False for line in werk["body"]: if line.startswith("LI:"): if not in_list: html.open_ul() in_list = True html.li(line[3:]) else: if in_list: html.close_ul() in_list = False if line.startswith("H2:"): html.h3(line[3:]) elif line.startswith("C+:"): html.open_pre(class_="code") in_code = True elif line.startswith("F+:"): file_name = line[3:] if file_name: html.div(file_name, class_="filename") html.open_pre(class_="file") in_code = True elif line.startswith("C-:") or line.startswith("F-:"): html.close_pre() in_code = False elif line.startswith("OM:"): html.write_text("OMD[mysite]:~$ ") html.b(line[3:]) elif line.startswith("RP:"): html.write_text("root@myhost:~# ") html.b(line[3:]) elif not line.strip() and not in_code: html.p("") else: html.write_text(line + "\n") if in_list: html.close_ul() html.close_p() return HTML(output_funnel.drain())
def _render_werk_options_form(werk_table_options: Dict[str, Any]) -> HTML: with output_funnel.plugged(): html.begin_form("werks") html.hidden_field("wo_set", "set") _show_werk_options_controls() html.open_div(class_="side_popup_content") for name, height, vs, _default_value in _werk_table_option_entries(): html.render_floating_option(name, height, "wo_", vs, werk_table_options[name]) html.close_div() html.hidden_fields() html.end_form() return HTML(output_funnel.drain())
def page(self): html.open_table(class_=["data", "headerleft"]) html.open_tr() html.th(_("Title")) html.open_td() html.b(self._manpage["header"]["title"]) html.close_td() html.close_tr() html.open_tr() html.th(_("Name of plugin")) html.open_td() html.tt(self._check_type) html.close_td() html.close_tr() html.open_tr() html.th(_("Description")) html.td(self._manpage_text(self._manpage["header"]["description"])) html.close_tr() if self._manpage["type"] == "check_mk": html.open_tr() html.th(_("Service name")) html.td( HTML(self._manpage["service_description"].replace( "%s", "☐"))) html.close_tr() check_ruleset_name = self._manpage.get("check_ruleset_name") if check_ruleset_name is not None: self._show_ruleset("checkgroup_parameters:%s" % check_ruleset_name) cluster = self._manpage["header"].get("cluster") if cluster: html.open_tr() html.th(_("Cluster behaviour")) html.td(self._manpage_text(cluster)) html.close_tr() else: self._show_ruleset("active_checks:%s" % self._check_type[6:]) html.close_table()
def _warn_about_local_files(self, crash_info): if crash_info["crash_type"] == "check": files = [] for filepath, _lineno, _func, _line in crash_info["exc_traceback"]: if "/local/" in filepath: files.append(filepath) if files: warn_text = _( "The following files located in the local hierarchy of your site are involved in this exception:" ) warn_text += html.render_ul( HTML("\n").join(map(html.render_li, files))) warn_text += _( "Maybe these files are not compatible with your current Checkmk " "version. Please verify and only report this crash when you think " "this should be working.") html.show_warning(warn_text)
def render_mobile_list(rows, view, group_cells, cells, num_columns, show_checkboxes): if not html.mobile: html.show_error(_("This view can only be used in mobile mode.")) return # Force relative timestamp always. This saves space. painter_options = PainterOptions.get_instance() painter_options.set("ts_format", "rel") html.open_ul(class_="mobilelist", **{"data-role": "listview"}) # Paint data rows for row in rows: html.open_li() rendered_cells = [cell.render(row) for cell in cells] if rendered_cells: # First cell (assumedly state) is left rendered_class, rendered_content = rendered_cells[0] html.open_p(class_=["ui-li-aside", "ui-li-desc", rendered_class]) html.write(rendered_content) html.close_p() if len(rendered_cells) > 1: content = HTML(" · ").join([ rendered_cell[1] for rendered_cell in rendered_cells[1:num_columns + 1] ]) html.h3(content) for rendered_cell, cell in zip( rendered_cells[num_columns + 1:], cells[num_columns + 1:]): rendered_class, rendered_content = rendered_cell html.open_p(class_="ui-li-desc") cell.paint_as_header() html.write_text(': ') html.open_span(class_=rendered_class) html.write(rendered_content) html.close_span() html.close_p() html.close_li() html.close_ul() html.javascript('$("ul.mobilelist a").attr("data-ajax", "false");')
def action(self) -> ActionResult: renaming_config = self._vs_renaming_config().from_html_vars("") self._vs_renaming_config().validate_value(renaming_config, "") renamings = self._collect_host_renamings(renaming_config) if not renamings: flash(_("No matching host names")) return None warning = self._renaming_collision_error(renamings) if warning: flash(warning) return None message = _( "<b>Do you really want to rename to following hosts? This involves a restart of the monitoring core!</b>" ) message += "<table>" for _folder, host_name, target_name in renamings: message += u"<tr><td>%s</td><td> → %s</td></tr>" % (host_name, target_name) message += "</table>" nr_rename = len(renamings) c = _confirm( _("Confirm renaming of %d %s") % (nr_rename, ungettext("host", "hosts", nr_rename)), HTML(message)) if c: title = _("Renaming of %s") % ", ".join(u"%s → %s" % x[1:] for x in renamings) host_renaming_job = RenameHostsBackgroundJob(title=title) host_renaming_job.set_function(rename_hosts_background_job, renamings) try: host_renaming_job.start() except background_job.BackgroundJobAlreadyRunning as e: raise MKGeneralException( _("Another host renaming job is already running: %s") % e) return redirect(host_renaming_job.detail_url()) if c is False: # not yet confirmed return FinalizeRequest(code=200) return None # browser reload
def query_limit_exceeded_warn(limit, user_config): # type: (Optional[int], LoggedInUser) -> None """Compare query reply against limits, warn in the GUI about incompleteness""" text = HTML(_("Your query produced more than %d results. ") % limit) if html.request.get_ascii_input( "limit", "soft") == "soft" and user_config.may("general.ignore_soft_limit"): text += html.render_a(_('Repeat query and allow more results.'), target="_self", href=html.makeuri([("limit", "hard")])) elif html.request.get_ascii_input("limit") == "hard" and user_config.may( "general.ignore_hard_limit"): text += html.render_a(_('Repeat query without limit.'), target="_self", href=html.makeuri([("limit", "none")])) text += " " + _( "<b>Note:</b> the shown results are incomplete and do not reflect the sort order.") html.show_warning(text)
def _render_tag_tree_level(self, tree_spec, path, cwd, title, tree): if (not self._is_tag_subdir(path=path, cwd=cwd) and not self._is_tag_subdir(path=cwd, cwd=path)): return if path != cwd and self._is_tag_subdir(path, cwd): bullet = self._tag_tree_bullet(self._tag_tree_worst_state(tree), path, False) if self._tag_tree_has_svc_problems(tree): bullet += html.render_icon_button( self._tag_tree_url(tree_spec, path, "svcproblems"), _("Show the service problems contained in this branch"), "svc_problems", target="main") if path: html.begin_foldable_container("tag-tree", ".".join(map(str, path)), False, HTML(bullet + title)) for (node_title, node_value), subtree in sorted(tree.get("_children", {}).items()): subpath = path + [node_value or ""] url = self._tag_tree_url(tree_spec, subpath, "allhosts") if "_num_hosts" in subtree: node_title += " (%d)" % subtree["_num_hosts"] node_title = html.render_a(node_title, href=url, target="main") if "_children" not in subtree: if self._is_tag_subdir(path, cwd): html.write(self._tag_tree_bullet(subtree.get("_state", 0), subpath, True)) if subtree.get("_svc_problems"): url = self._tag_tree_url(tree_spec, subpath, "svcproblems") html.icon_button(url, _("Show the service problems contained in this branch"), "svc_problems", target="main") html.write(node_title) html.br() else: self._render_tag_tree_level(tree_spec, subpath, cwd, node_title, subtree) if path and path != cwd and self._is_tag_subdir(path, cwd): html.end_foldable_container()
def insert_manpage_links(text: str) -> HTML: parts = text.replace(",", " ").split() new_parts: List[HTML] = [] check_regex = re.compile(r"[-_\.a-z0-9]") for part in parts: if check_regex.match(part) and os.path.exists( cmk.utils.paths.check_manpages_dir + "/" + part): url = makeuri_contextless( request, [ ("mode", "check_manpage"), ("check_type", part), ], filename="wato.py", ) new_parts.append(html.render_a(content=part, href=url)) else: new_parts.append(escape_to_html(part)) return HTML(" ").join(new_parts)
def render_list(ids, links): n = 0 for entry in links: n += 1 try: if isinstance(entry[1], type(True)): idss = ids + [str(n)] id_ = '/'.join(idss) html.begin_foldable_container("customlinks", id_, isopen=entry[1], title=entry[0], icon="foldable_sidebar") render_list(idss, entry[2]) html.end_foldable_container() elif isinstance(entry[1], str): frame = entry[3] if len(entry) > 3 else "main" if len(entry) > 2 and entry[2]: icon_file = entry[2] # Old configs used files named "link_<name>.gif". Those .gif files have # been removed from Checkmk. Replacing such images with the default icon if icon_file.endswith(".gif"): icon_name = "link" else: icon_name = icon_file.rsplit(".", 1)[0].replace( "icon_", "") else: icon_name = "link" linktext = HTML( html.render_icon(icon_name) + " " + entry[0]) simplelink(linktext, entry[1], frame) else: html.write_text( _("Second part of tuple must be list or string, not %s\n" ) % str(entry[1])) except Exception as e: html.write_text( _("invalid entry %s: %s<br>\n") % (entry, e))
def jqm_page_index_topic_renderer(topic: str, items: Items) -> None: has_items_for_topic = any(i for i in items if i[0] == topic) if not has_items_for_topic: return html.p(topic) html.open_ul(**{"data-role": "listview", "data-inset": "true"}) for top, href, title in items: if top == topic: html.open_li() html.open_a(href=href, **{ "data-ajax": "false", "data-transition": "flip" }) html.write_html(HTML(title)) html.close_a() html.close_li() html.close_ul()
def action(self): if html.request.var("_reset"): if not is_a_checkbox(self._valuespec): c = wato_confirm( _("Resetting configuration variable"), _("Do you really want to reset this configuration variable " "back to its default value?")) if c is False: return "" if c is None: return None elif not html.check_transaction(): return try: del self._current_settings[self._varname] except KeyError: pass msg: Union[ HTML, str] = _("Resetted configuration variable %s to its default." ) % self._varname else: new_value = self._valuespec.from_html_vars("ve") self._valuespec.validate_value(new_value, "ve") self._current_settings[self._varname] = new_value msg = _("Changed global configuration variable %s to %s.") \ % (self._varname, self._valuespec.value_to_text(new_value)) # FIXME: THIS HTML(...) is needed because we do not know what we get from value_to_text!! msg = HTML(msg) self._save() watolib.add_change("edit-configvar", msg, sites=self._affected_sites(), domains=[self._config_variable.domain()], need_restart=self._config_variable.need_restart()) page_menu = self.parent_mode() assert page_menu is not None return page_menu.name()
def _sidebar_foot(self, user_config): html.open_div(id_="side_footer") if config.user.may("general.configure_sidebar"): html.icon_button("sidebar_add_snapin.py", _("Add snapin to the sidebar"), "sidebar_addsnapin", target="main") # editing the profile is not possible on remote sites which are sync targets # of a central WATO system if config.wato_enabled and \ (config.user.may("general.edit_profile") or config.user.may("general.change_password")): html.icon_button( "user_profile.py", _("Edit your personal settings, change your password"), "sidebar_settings", target="main") if config.user.may( "general.logout") and not config.auth_by_http_header: html.icon_button("logout.py", _("Log out"), "sidebar_logout", target="_top") html.icon_button("return void();", _("You have pending messages."), "sidebar_messages", onclick='cmk.sidebar.read_message()', id_='msg_button', style='display:none') html.open_div(style="display:none;", id_="messages") self.render_messages() html.close_div() html.open_div(class_=["copyright"]) html.write( HTML("© ") + html.render_a( "tribe29 GmbH", target="_blank", href="https://checkmk.com")) html.close_div() html.close_div() if user_config.folded: html.final_javascript("cmk.sidebar.fold_sidebar();")
def mobile_html_head(title: str) -> None: html.write_html( HTML( """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">""" )) html.open_html() html.open_head() html.default_html_headers() html.meta(name="viewport", content="initial-scale=1.0") html.meta(name="apple-mobile-web-app-capable", content="yes") html.meta(name="apple-mobile-web-app-title", content="Check_MK") html.title(title) html.stylesheet(href="jquery/jquery.mobile-1.4.5.min.css") html.stylesheet(href="themes/facelift/theme.css") html.link(rel="apple-touch-icon", href="themes/facelift/images/ios_logo.png") html.javascript_file(src='js/mobile_min.js') html.close_head() html.open_body(class_="mobile")
def _add_cell( self, title: "HTMLContent" = "", text: "HTMLContent" = "", css: Optional["CSSSpec"] = None, help_txt: Optional[str] = None, colspan: Optional[int] = None, sortable: bool = True, ): if isinstance(text, HTML): content = text else: content = escape_to_html_permissive( str(text) if not isinstance(text, str) else text, escape_links=False) htmlcode: HTML = content + HTML(output_funnel.drain()) if isinstance(title, HTML): header_title = title else: if title is None: title = "" header_title = escape_to_html_permissive( str(title) if not isinstance(title, str) else title, escape_links=False) if self.options["collect_headers"] is True: # small helper to make sorting introducion easier. Cells which contain # buttons are never sortable if css and "buttons" in css and sortable: sortable = False self.headers.append( TableHeader(title=header_title, css=css, help_txt=help_txt, sortable=sortable)) current_row = self.rows[-1] assert isinstance(current_row, TableRow) current_row.cells.append(CellSpec(htmlcode, css, colspan))
def _render_tree(self, tree): for group, attrs in tree.items(): fetch_url = html.makeuri_contextless([ ("view_name", "aggr_all"), ("aggr_group_tree", "/".join(attrs["__path__"])), ], "view.py") if attrs.get('__children__'): html.begin_foldable_container( "bi_aggregation_group_trees", group, False, HTML(html.render_a( group, href=fetch_url, target="main", ))) self._render_tree(attrs['__children__']) html.end_foldable_container() else: html.open_ul() bulletlink(group, fetch_url) html.close_ul()
def page(self): html.open_table(class_=["data", "headerleft"]) html.open_tr() html.th(_("Title")) html.open_td() html.b(self._manpage["header"]["title"]) html.close_td() html.close_tr() html.open_tr() html.th(_("Name of plugin")) html.open_td() html.tt(self._check_type) html.close_td() html.close_tr() html.open_tr() html.th(_("Description")) html.td(self._manpage_text(self._manpage["header"]["description"])) html.close_tr() if self._manpage["type"] == "check_mk": html.open_tr() html.th(_("Service name")) html.td( HTML(self._manpage["service_description"].replace( "%s", "☐"))) html.close_tr() if self._manpage.get("group"): group = self._manpage["group"] varname = "checkgroup_parameters:" + group self._show_ruleset(varname) else: varname = "active_checks:" + self._check_type[6:] self._show_ruleset(varname) html.close_table()
def page_login() -> None: title = _("Checkmk Mobile") mobile_html_head(title) jqm_page_header(title, id_="login") html.div(_("Welcome to Checkmk Mobile."), id_="loginhead") html.begin_form("login", method="POST", add_transid=False) # Keep information about original target URL default_origtarget = ("index.py" if requested_file_name(request) in ["login", "logout"] else makeuri(request, [])) origtarget = request.get_url_input("_origtarget", default_origtarget) html.hidden_field("_origtarget", escaping.escape_attribute(origtarget)) html.text_input("_username", label=_("Username:"******"username", id_="input_user") html.password_input( "_password", size=None, label=_("Password:"******"current-password", id_="input_pass", ) html.br() html.button("_login", _("Login")) html.set_focus("_username") html.end_form() html.open_div(id_="loginfoot") html.img("themes/facelift/images/logo_cmk_small.png", class_="logomk") html.div( HTML( _('© <a target="_blank" href="https://checkmk.com">tribe29 GmbH</a>' )), class_="copyright", ) html.close_div() # close content-div html.close_div() html.close_div() # close page-div mobile_html_foot()
def render_graphs_from_definitions(graph_recipes, graph_data_range, graph_render_options, render_async=True) -> HTML: # Estimate step. Step is the number of seconds each fetched data point represents. # It does not make sense to fetch the data in *much* greater precision than our # display has. A *bit* more precision is useful for better optical zoom. graph_data_range.setdefault( "step", estimate_graph_step_for_html(graph_data_range["time_range"], graph_render_options)) output = HTML() for graph_recipe in graph_recipes: if render_async: output += render_graph_container_html(graph_recipe, graph_data_range, graph_render_options) else: output += render_graph_content_html(graph_recipe, graph_data_range, graph_render_options) return output
def render(self, row, cell): classes = ["perfometer"] if is_stale(row): classes.append("stale") try: title, h = Perfometer(row).render() if title is None and h is None: return "", "" except Exception as e: logger.exception("error rendering performeter") if config.debug: raise return " ".join(classes), _("Exception: %s") % e content = html.render_div(HTML(h), class_=["content"]) \ + html.render_div(title, class_=["title"]) \ + html.render_div("", class_=["glass"]) # pnpgraph_present: -1 means unknown (path not configured), 0: no, 1: yes if display_options.enabled(display_options.X) \ and row["service_pnpgraph_present"] != 0: if metrics.cmk_graphs_possible(): import cmk.gui.cee.plugins.views.graphs url = cmk.gui.cee.plugins.views.graphs.cmk_graph_url( row, "service") else: url = pnp_url(row, "service") disabled = False else: url = "javascript:void(0)" disabled = True return " ".join(classes), \ html.render_a(content=content, href=url, title=html.strip_tags(title), class_=["disabled" if disabled else None])
def render_tree_folder(tree_id, folder, js_func): subfolders = folder.get(".folders", {}).values() is_leaf = len(subfolders) == 0 # Suppress indentation for non-emtpy root folder if folder['.path'] == '' and is_leaf: html.open_ul() # empty root folder elif folder and folder['.path'] != '': html.open_ul(style="padding-left:0px;") title = html.render_a("%s (%d)" % (folder["title"], folder[".num_hosts"]), href="#", class_="link", onclick="%s(this, \'%s\');" % (js_func, folder[".path"])) if not is_leaf: html.begin_foldable_container(tree_id, "/" + folder[".path"], False, HTML(title)) for subfolder in sorted(subfolders, key=lambda x: x["title"].lower()): render_tree_folder(tree_id, subfolder, js_func) html.end_foldable_container() else: html.li(title) html.close_ul()
def _show_leaf(self, tree, show_host): site, host = tree[2]["host"] service = tree[2].get("service") # Four cases: # (1) zbghora17 . Host status (show_host == True, service is None) # (2) zbghora17 . CPU load (show_host == True, service is not None) # (3) Host Status (show_host == False, service is None) # (4) CPU load (show_host == False, service is not None) if show_host or not service: host_url = makeuri_contextless( request, [("view_name", "hoststatus"), ("site", site), ("host", host)], filename="view.py", ) if service: service_url = makeuri_contextless( request, [("view_name", "service"), ("site", site), ("host", host), ("service", service)], filename="view.py", ) with self._show_node(tree, show_host): self._assume_icon(site, host, service) if show_host: html.a(host.replace(" ", " "), href=host_url) html.b(HTML("♦"), class_="bullet") if not service: html.a(_("Host status"), href=host_url) else: html.a(service.replace(" ", " "), href=service_url)
def render(self, row: Row, cell: Cell) -> CellSpec: single_url = "view.py?" + urlencode_vars( [("view_name", "aggr_single"), ("aggr_name", row["aggr_name"])] ) avail_url = single_url + "&mode=availability" bi_map_url = "bi_map.py?" + urlencode_vars( [ ("aggr_name", row["aggr_name"]), ] ) with output_funnel.plugged(): html.icon_button(bi_map_url, _("Visualize this aggregation"), "aggr") html.icon_button(single_url, _("Show only this aggregation"), "showbi") html.icon_button( avail_url, _("Analyse availability of this aggregation"), "availability" ) if row["aggr_effective_state"]["in_downtime"] != 0: html.icon( "derived_downtime", _("A service or host in this aggregation is in downtime.") ) if row["aggr_effective_state"]["acknowledged"]: html.icon( "ack", _( "The critical problems that make this aggregation non-OK have been acknowledged." ), ) if not row["aggr_effective_state"]["in_service_period"]: html.icon( "outof_serviceperiod", _("This aggregation is currently out of its service period."), ) code = HTML(output_funnel.drain()) return "buttons", code
class TestRuleConditionRenderer: @pytest.mark.parametrize( "taggroup_id, tag_spec, rendered_condition", [ pytest.param( "tag_grp_1", "grp_1_tg_1", HTML("Host: Tag group 1 is <b>Tag 1.1</b>"), id="grouped tag", ), pytest.param( "tag_grp_1", {"$ne": "grp_1_tg_1"}, HTML("Host: Tag group 1 is <b>not</b> <b>Tag 1.1</b>"), id="negated grouped tag", ), pytest.param( "aux_tag_1", "aux_tag_1", HTML("Host has tag <b>Auxiliary tag 1</b>"), id="auxiliary tag", ), pytest.param( "aux_tag_1", {"$ne": "aux_tag_1"}, HTML("Host does not have tag <b>Auxiliary tag 1</b>"), id="negated auxiliary tag", ), pytest.param( "xyz", "a", HTML("Unknown tag: Host has the tag <tt>a</tt>"), id="unknown tag group", ), pytest.param( "xyz", "grp_1_tg_1", HTML("Unknown tag: Host has the tag <tt>grp_1_tg_1</tt>"), id="unknown tag", ), ], ) def test_single_tag_condition( self, taggroup_id: TaggroupID, tag_spec: Union[Optional[TagID], TagConditionNE], rendered_condition: HTML, ) -> None: assert RuleConditionRenderer()._single_tag_condition( taggroup_id, tag_spec, ) == rendered_condition def test_tag_condition(self) -> None: assert list(RuleConditionRenderer()._tag_conditions({ "tag_grp_1": { "$or": [ "grp_1_tg_1", "grp_1_tg_2", ] }, "tag_grp_2": { "$nor": [ "grp_2_tg_1", "grp_2_tg_2", ] }, "tag_grp_3": "grp_3_tg_1", "aux_tag_1": { "$ne": "aux_tag_1" }, })) == [ HTML( "Host: Tag group 1 is <b>Tag 1.1</b> <i>or</i> Host: Tag group 1 is <b>Tag 1.2</b>" ), HTML( "Neither Host: Tag group 2 is <b>Tag 2.1</b> <i>nor</i> Host: Tag group 2 is <b>Tag 2.2</b>" ), HTML("Host: Tag group 3 is <b>Tag 3.1</b>"), HTML("Host does not have tag <b>Auxiliary tag 1</b>"), ]
def render_job_row(cls, job_id, job_status, odd, job_details_back_url=None): html.open_tr(css="data %s0" % odd) # Actions html.open_td(css="job_actions") if job_status.get("may_stop"): html.icon_button( html.makeactionuri([(ActionHandler.stop_job_var, job_id)]), _("Stop this job"), "disable_test") if job_status.get("may_delete"): html.icon_button( html.makeactionuri([(ActionHandler.delete_job_var, job_id)]), _("Delete this job"), "delete") html.close_td() # Job ID html.open_td(css="job_id") uri = makeuri_contextless( request, [ ("mode", "background_job_details"), ("back_url", job_details_back_url), ("job_id", job_id), ], filename="wato.py", ) html.a(job_id, href=uri) html.close_td() # Title html.td(job_status.get("title", _("Background Job")), css="job_title") # State html.td(job_status["state"], css=cls.get_css_for_jobstate(job_status["state"])) # Started html.td(cmk.utils.render.date_and_time(job_status["started"]), css="job_started") # Owner html.td(job_status.get("user", _("Unknown user")), css="job_owner") # PID html.td(job_status["pid"] or "", css="job_pid") # Druation html.td(cmk.utils.render.timespan(job_status.get("duration", 0)), css="job_runtime") # Progress info loginfo = job_status.get("loginfo") if loginfo: if job_status.get( "state") == background_job.JobStatusStates.EXCEPTION: html.td(HTML("<br>".join(loginfo["JobException"])), css="job_last_progress") else: progress_text = "" if loginfo["JobProgressUpdate"]: progress_text += "%s" % loginfo["JobProgressUpdate"][-1] html.td(HTML(progress_text), css="job_last_progress") html.td(HTML("<br>".join(loginfo["JobResult"])), css="job_result") else: html.td("", css="job_last_progress") html.td("", css="job_result")
def show_job_details(cls, job_id, job_status): """Renders the complete job details in a single table with left headers""" html.open_table(class_=["data", "headerleft", "job_details"]) # Static info for left, right in [ (_("ID"), job_id), (_("Title"), job_status.get("title", "")), (_("Started"), cmk.utils.render.date_and_time(job_status["started"])), (_("Owner"), job_status.get("user", "")), ]: html.open_tr() html.th(left) html.td(right) html.close_tr() # Actions html.open_tr() html.th(_("Actions")) html.open_td() if job_status.get("may_stop"): html.icon_button( html.makeactionuri([(ActionHandler.stop_job_var, job_id)]), _("Stop this job"), "disable_test", ) if job_status.get("may_delete"): html.icon_button( html.makeactionuri([(ActionHandler.delete_job_var, job_id)]), _("Delete this job"), "delete", ) html.close_td() html.close_tr() # Job state html.open_tr() html.th(_("State")) html.td(job_status["state"], css=cls.get_css_for_jobstate(job_status["state"])) html.close_tr() if job_status["state"] == background_job.JobStatusStates.EXCEPTION: html.open_tr() html.th(_("Acknowledged by")) html.td(job_status.get("acknowledged_by", "")) html.close_tr() # Dynamic data loginfo = job_status.get("loginfo") runtime_info = ensure_str( cmk.utils.render.timespan(job_status.get("duration", 0))) if job_status["state"] == background_job.JobStatusStates.RUNNING \ and job_status.get("estimated_duration") is not None: runtime_info += u" (%s: %s)" % ( _("estimated duration"), ensure_str( cmk.utils.render.timespan( job_status["estimated_duration"]))) for left, right in [ (_("Runtime"), runtime_info), (_("PID"), job_status["pid"] or ""), (_("Result"), "<br>".join(loginfo["JobResult"])), ]: if right is None: continue html.open_tr() html.th(left) html.td(HTML(right)) html.close_tr() # Exceptions exceptions = loginfo["JobException"] if exceptions: html.open_tr() html.th(_("Exceptions")) html.open_td() if exceptions and "logfile_path" in job_status: exceptions.append( _("More information can be found in %s") % job_status["logfile_path"]) html.open_div(class_="log_output", id_="exception_log") html.pre("\n".join(exceptions)) html.close_div() html.close_td() html.close_tr() # Progress Update html.open_tr() html.th(_("Progress Info")) html.open_td() html.open_div(class_="log_output", style="height: 400px;", id_="progress_log") html.pre(HTML("\n").join(loginfo["JobProgressUpdate"])) html.close_div() html.close_td() html.close_tr() html.close_table() html.javascript( "var log = document.getElementById('progress_log'); log.scrollTop = log.scrollHeight;" )
def _render_filter_form(self) -> HTML: with output_funnel.plugged(): self._display_audit_log_options() return HTML(output_funnel.drain())