def _write_table(self, rows, actions_enabled, actions_visible, search_term): headinfo = _("1 row") if len(rows) == 1 else _("%d rows") % len(rows) html.javascript("cmk.utils.update_header_info(%s);" % json.dumps(headinfo)) table_id = self.id num_cols = self._get_num_cols(rows) empty_columns = self._get_empty_columns(rows, num_cols) if self.options["omit_empty_columns"]: num_cols -= len([v for v in empty_columns if v]) html.open_table(class_=["data", "oddeven", self.css]) # If we have no group headers then paint the headers now if self.rows and self.rows[0][2] != "header": self._render_headers( actions_enabled, actions_visible, empty_columns, ) if actions_enabled and actions_visible: html.open_tr(class_=["data", "even0", "actions"]) html.open_td(colspan=num_cols) if not html.in_form(): html.begin_form("%s_actions" % table_id) if self.options["searchable"]: html.open_div(class_="search") html.text_input("_%s_search" % table_id) html.button("_%s_submit" % table_id, _("Search")) html.button("_%s_reset" % table_id, _("Reset search")) html.set_focus("_%s_search" % table_id) html.close_div() if html.request.has_var('_%s_sort' % table_id): html.open_div(class_=["sort"]) html.button("_%s_reset_sorting" % table_id, _("Reset sorting")) html.close_div() if not html.in_form(): html.begin_form("%s_actions" % table_id) html.hidden_fields() html.end_form() html.close_tr() for nr, (row_spec, css, state, _fixed, attrs) in enumerate(rows): if not css and "class_" in attrs: css = attrs.pop("class_") if not css and "class" in attrs: css = attrs.pop("class") # Intermediate header if state == "header": # Show the header only, if at least one (non-header) row follows if nr < len(rows) - 1 and rows[nr + 1][2] != "header": html.open_tr(class_="groupheader") html.open_td(colspan=num_cols) html.open_h3() html.write(row_spec) html.close_h3() html.close_td() html.close_tr() self._render_headers(actions_enabled, actions_visible, empty_columns) continue oddeven_name = "even" if (nr - 1) % 2 == 0 else "odd" html.open_tr(class_=[ "data", "%s%d" % (oddeven_name, state), css if css else None ], **attrs) for col_index, (cell_content, css_classes, colspan) in enumerate(row_spec): if self.options["omit_empty_columns"] and empty_columns[ col_index]: continue html.open_td(class_=css_classes if css_classes else None, colspan=colspan if colspan else None) html.write(cell_content) html.close_td() html.close_tr() if not rows and search_term: html.open_tr(class_=["data", "odd0", "no_match"]) html.td( _('Found no matching rows. Please try another search term.'), colspan=num_cols) html.close_tr() html.close_table()
def _write_table(self, rows, actions_enabled, actions_visible, search_term): # type: (TableRows, bool, bool, Optional[Text]) -> None headinfo = _("1 row") if len(rows) == 1 else _("%d rows") % len(rows) html.javascript("cmk.utils.update_header_info(%s);" % json.dumps(headinfo)) table_id = self.id num_cols = self._get_num_cols(rows) empty_columns = self._get_empty_columns(rows, num_cols) if self.options["omit_empty_columns"]: num_cols -= len([v for v in empty_columns if v]) html.open_table(class_=["data", "oddeven", self.css]) # If we have no group headers then paint the headers now if self.rows and not isinstance(self.rows[0], GroupHeader): self._render_headers( actions_enabled, actions_visible, empty_columns, ) if actions_enabled and actions_visible: html.open_tr(class_=["data", "even0", "actions"]) html.open_td(colspan=num_cols) if not html.in_form(): html.begin_form("%s_actions" % table_id) if self.options["searchable"]: html.open_div(class_="search") html.text_input("_%s_search" % table_id) html.button("_%s_submit" % table_id, _("Search")) html.button("_%s_reset" % table_id, _("Reset search")) html.set_focus("_%s_search" % table_id) html.close_div() if html.request.has_var('_%s_sort' % table_id): html.open_div(class_=["sort"]) html.button("_%s_reset_sorting" % table_id, _("Reset sorting")) html.close_div() if not html.in_form(): html.begin_form("%s_actions" % table_id) html.hidden_fields() html.end_form() html.close_tr() for nr, row in enumerate(rows): # Intermediate header if isinstance(row, GroupHeader): # Show the header only, if at least one (non-header) row follows if nr < len(rows) - 1 and not isinstance( rows[nr + 1], GroupHeader): html.open_tr(class_="groupheader") html.open_td(colspan=num_cols) html.open_h3() html.write(row.title) html.close_h3() html.close_td() html.close_tr() self._render_headers(actions_enabled, actions_visible, empty_columns) continue oddeven_name = "even" if (nr - 1) % 2 == 0 else "odd" class_ = ["data", "%s%d" % (oddeven_name, row.state)] if row.css: class_.append(row.css) else: for k in ["class_", "class"]: if k in row.row_attributes: cls_spec = cast(CSSSpec, row.row_attributes.pop(k)) if isinstance(cls_spec, list): class_.extend( [c for c in cls_spec if c is not None]) elif cls_spec is not None: class_.append(cls_spec) html.open_tr(class_=class_, **row.row_attributes) for col_index, cell in enumerate(row.cells): if self.options["omit_empty_columns"] and empty_columns[ col_index]: continue html.open_td(class_=cell.css, colspan=cell.colspan) html.write(cell.content) html.close_td() html.close_tr() if not rows and search_term: html.open_tr(class_=["data", "odd0", "no_match"]) html.td( _('Found no matching rows. Please try another search term.'), colspan=num_cols) html.close_tr() html.close_table()
def page(self): # Show outcome of host validation. Do not validate new hosts errors = None if self._mode == "edit": errors = watolib.validate_all_hosts([self._host.name()]).get( self._host.name(), []) + self._host.validation_errors() if errors: html.open_div(class_="info") html.open_table(class_="validationerror", boder="0", cellspacing="0", cellpadding="0") html.open_tr() html.open_td(class_="img") html.icon("validation_error") html.close_td() html.open_td() html.open_p() html.h3(_("Warning: This host has an invalid configuration!")) html.open_ul() for error in errors: html.li(error) html.close_ul() html.close_p() if html.form_submitted(): html.br() html.b(_("Your changes have been saved nevertheless.")) html.close_td() html.close_tr() html.close_table() html.close_div() lock_message = u"" locked_hosts = watolib.Folder.current().locked_hosts() if locked_hosts: if locked_hosts is True: lock_message = _( "Host attributes locked (You cannot edit this host)") elif isinstance(locked_hosts, str): lock_message = locked_hosts if lock_message: html.div(lock_message, class_="info") html.begin_form("edit_host", method="POST") html.prevent_password_auto_completion() basic_attributes = [ # attribute name, valuepec, default value ("host", self._vs_host_name(), self._host.name()), ] if self._is_cluster(): basic_attributes += [ # attribute name, valuepec, default value ("nodes", self._vs_cluster_nodes(), self._host.cluster_nodes() if self._host else []), ] configure_attributes( new=self._mode != "edit", hosts={self._host.name(): self._host} if self._mode != "new" else {}, for_what="host" if not self._is_cluster() else "cluster", parent=watolib.Folder.current(), basic_attributes=basic_attributes, ) if self._mode != "edit": html.set_focus("host") forms.end() html.hidden_fields() html.end_form()
def show_file(site, host_name, file_name): int_filename = form_file_to_int(file_name) title = _("Logfiles of Host %s: %s") % (host_name, int_filename) breadcrumb = _show_file_breadcrumb(host_name, title) html.header( title, breadcrumb, _show_file_page_menu(breadcrumb, site, host_name, int_filename)) if request.has_var('_ack') and not request.var("_do_actions") == _("No"): do_log_ack(site, host_name, file_name) return try: log_chunks = parse_file(site, host_name, int_filename, hidecontext=request.var('_hidecontext', 'no') == 'yes') except Exception as e: if config.debug: raise html.show_error(_("Unable to show logfile: <b>%s</b>") % e) html.footer() return if log_chunks is None: html.show_error(_("The logfile does not exist on site.")) html.footer() return if log_chunks == []: html.show_message( _("This logfile contains no unacknowledged messages.")) html.footer() return html.open_div(id_="logwatch") for log in log_chunks: html.open_table(class_="groupheader") html.open_tr() html.td(form_level(log['level']), class_=form_level(log['level'])) html.td(form_datetime(log['datetime']), class_="date") html.close_tr() html.close_table() html.open_table(class_=["section"]) for line in log['lines']: html.open_tr(class_=line['class']) html.open_td(class_='lines') html.icon_button( analyse_url(site, host_name, int_filename, line['line']), _("Analyze this line"), "analyze") html.write_text(line['line'].replace(" ", " ").replace( "\1", "<br>")) html.close_td() html.close_tr() html.close_table() html.close_div() html.footer()
def show_job_row_headers(cls): html.open_tr() for header in cls.get_headers(): html.th(header) html.close_tr()
def _show_graph_legend(graph_artwork, graph_render_options) -> None: """Render legend that describe the metrics""" graph_width = graph_render_options["size"][0] * html_size_per_ex font_size_style = "font-size: %dpt;" % graph_render_options["font_size"] scalars = get_scalars(graph_artwork, graph_render_options) if graph_render_options["show_vertical_axis"] or graph_render_options[ "show_controls"]: legend_margin_left = 49 else: legend_margin_left = 0 style = [] legend_width = graph_width - legend_margin_left # In case there is no margin show: Add some to the legend since it looks # ugly when there is no space between the outer graph border and the legend if not graph_render_options["show_margin"]: legend_width -= 5 * 2 style.append("margin: 8px 5px 5px 5px") style.append("width:%dpx" % legend_width) if legend_margin_left: style.append("margin-left:%dpx" % legend_margin_left) html.open_table(class_="legend", style=style) # Render the title row html.open_tr() html.th("") for scalar, title, inactive in scalars: classes = ["scalar", scalar] if inactive and graph_artwork["step"] != 60: descr = _( 'This graph is based on data consolidated with the function "%s". The ' 'values in this column are the "%s" values of the "%s" values ' "aggregated in %s steps. Assuming a check interval of 1 minute, the %s " "values here are based on the %s value out of %d raw values." ) % ( graph_artwork["definition"]["consolidation_function"], scalar, graph_artwork["definition"]["consolidation_function"], artwork.get_step_label(graph_artwork["step"]), scalar, graph_artwork["definition"]["consolidation_function"], (graph_artwork["step"] / 60), ) descr += ("\n\n" + _("Click here to change the graphs " 'consolidation function to "%s".') % scalar) classes.append("inactive") else: descr = "" html.th(title, class_=classes, style=font_size_style, title=descr) html.close_tr() # Render the curve related rows for curve in graph_curves(graph_artwork): html.open_tr() html.open_td(style=font_size_style) html.write_html(render_color_icon(curve["color"])) html.write_text(curve["title"]) html.close_td() for scalar, title, inactive in scalars: if scalar == "pin" and not show_pin_time(graph_artwork, graph_render_options): continue if inactive and graph_artwork["step"] != 60: inactive_cls: Optional[str] = "inactive" else: inactive_cls = None html.td(curve["scalars"][scalar][1], class_=inactive_cls, style=font_size_style) html.close_tr() # Render scalar values if graph_artwork["horizontal_rules"]: first = True for _value, readable, color, title in graph_artwork[ "horizontal_rules"]: html.open_tr(class_=["scalar", "first" if first else None]) html.open_td(style=font_size_style) html.write_html(render_color_icon(color)) html.write_text(title) html.close_td() # A colspan of 5 has to be used here, since the pin that is added by a click into # the graph introduces a new column. html.td(readable, colspan=5, class_="scalar", style=font_size_style) html.close_tr() first = False html.close_table()
def _show_service_info(self, all_rulesets): assert self._service is not None serviceinfo = analyse_service( self._host.site_id(), self._hostname, self._service, ).service_info if not serviceinfo: return forms.header(_("Check origin and parameters"), isopen=True, narrow=True, css="rulesettings") origin = serviceinfo["origin"] origin_txt = { "active": _("Active check"), "static": _("Manual check"), "auto": _("Inventorized check"), "classic": _("Classical check"), }[origin] self._render_rule_reason(_("Type of check"), None, "", "", False, origin_txt) # First case: discovered checks. They come from var/check_mk/autochecks/HOST. if origin == "auto": checkgroup = serviceinfo["checkgroup"] checktype = serviceinfo["checktype"] if not checkgroup: self._render_rule_reason( _("Parameters"), None, "", "", True, _("This check is not configurable via WATO"), ) # Logwatch needs a special handling, since it is not configured # via checkgroup_parameters but via "logwatch_rules" in a special # WATO module. elif checkgroup == "logwatch": rulespec = rulespec_registry["logwatch_rules"] self._output_analysed_ruleset( all_rulesets, rulespec, svc_desc_or_item=serviceinfo["item"], svc_desc=self._service, known_settings=serviceinfo["parameters"], ) else: # Note: some discovered checks have a check group but # *no* ruleset for discovered checks. One example is "ps". # That can be configured as a manual check or created by # inventory. But in the later case all parameters are set # by the inventory. This will be changed in a later version, # but we need to address it anyway. grouprule = "checkgroup_parameters:" + checkgroup if grouprule not in rulespec_registry: try: rulespec = rulespec_registry["static_checks:" + checkgroup] except KeyError: self._render_rule_reason( _("Parameters"), None, "", "", True, _("This check is not configurable via WATO"), ) return url = watolib.folder_preserving_link( [ ("mode", "edit_ruleset"), ("varname", "static_checks:" + checkgroup), ("host", self._hostname), ] ) assert isinstance(rulespec.valuespec, Tuple) self._render_rule_reason( _("Parameters"), url, _("Determined by discovery"), None, False, rulespec.valuespec._elements[2].value_to_text(serviceinfo["parameters"]), ) else: rulespec = rulespec_registry[grouprule] self._output_analysed_ruleset( all_rulesets, rulespec, svc_desc_or_item=serviceinfo["item"], svc_desc=self._service, known_settings=serviceinfo["parameters"], ) elif origin == "static": checkgroup = serviceinfo["checkgroup"] checktype = serviceinfo["checktype"] if not checkgroup: html.write_text(_("This check is not configurable via WATO")) else: rulespec = rulespec_registry["static_checks:" + checkgroup] itemspec = rulespec.item_spec if itemspec: item_text = itemspec.value_to_text(serviceinfo["item"]) assert rulespec.item_spec is not None title = rulespec.item_spec.title() else: item_text = serviceinfo["item"] title = _("Item") self._render_rule_reason(title, None, "", "", False, item_text) self._output_analysed_ruleset( all_rulesets, rulespec, svc_desc_or_item=serviceinfo["item"], svc_desc=self._service, known_settings=self._PARAMETERS_OMIT, ) assert isinstance(rulespec.valuespec, Tuple) html.write_text( rulespec.valuespec._elements[2].value_to_text(serviceinfo["parameters"]) ) html.close_td() html.close_tr() html.close_table() elif origin == "active": checktype = serviceinfo["checktype"] rulespec = rulespec_registry["active_checks:" + checktype] self._output_analysed_ruleset( all_rulesets, rulespec, svc_desc_or_item=None, svc_desc=None, known_settings=serviceinfo["parameters"], ) elif origin == "classic": ruleset = all_rulesets.get("custom_checks") origin_rule_result = self._get_custom_check_origin_rule( ruleset, self._hostname, self._service ) if origin_rule_result is None: raise MKUserError( None, _("Failed to determine origin rule of %s / %s") % (self._hostname, self._service), ) rule_folder, rule_index, rule = origin_rule_result url = watolib.folder_preserving_link( [("mode", "edit_ruleset"), ("varname", "custom_checks"), ("host", self._hostname)] ) forms.section(html.render_a(_("Command Line"), href=url)) url = watolib.folder_preserving_link( [ ("mode", "edit_rule"), ("varname", "custom_checks"), ("rule_folder", rule_folder.path()), ("rule_id", rule.id), ("host", self._hostname), ] ) html.open_table(class_="setting") html.open_tr() html.open_td(class_="reason") html.a( "%s %d %s %s" % (_("Rule"), rule_index + 1, _("in"), rule_folder.title()), href=url ) html.close_td() html.open_td(class_=["settingvalue", "used"]) if "command_line" in serviceinfo: html.tt(serviceinfo["command_line"]) else: html.write_text(_("(no command line, passive check)")) html.close_td() html.close_tr() html.close_table() self._show_labels( serviceinfo.get("labels", {}), "service", serviceinfo.get("label_sources", {}) )
def render(self, rows, view, group_cells, cells, num_columns, show_checkboxes): html.open_table(class_="data tiled") last_group = None group_open = False for row in rows: # Show group header if group_cells: this_group = group_value(row, group_cells) if this_group != last_group: # paint group header if group_open: html.close_td() html.close_tr() html.open_tr() html.open_td() html.open_table(class_="groupheader") html.open_tr(class_="groupheader") painted = False for cell in group_cells: if painted: html.td(', ') painted = cell.paint(row) html.close_tr() html.close_table() html.close_td() html.close_tr() html.open_tr() html.open_td(class_="tiles") group_open = True last_group = this_group # background color of tile according to item state state = row.get("service_state", -1) if state == -1: hbc = row.get("host_has_been_checked", 1) if hbc: state = row.get("host_state", 0) sclass = "hhstate%d" % state else: sclass = "hhstatep" else: hbc = row.get("service_has_been_checked", 1) if hbc: sclass = "sstate%d" % state else: sclass = "sstatep" if not group_open: html.open_tr() html.open_td(class_="tiles") group_open = True html.open_div(class_=["tile", sclass]) html.open_table() # We need at least five cells if len(cells) < 5: cells = cells + ([EmptyCell(view)] * (5 - len(cells))) rendered = [cell.render(row) for cell in cells] html.open_tr() html.open_td(class_=["tl", rendered[1][0]]) if show_checkboxes: render_checkbox(view, row, len(cells) - 1) html.write_text(rendered[1][1]) html.close_td() html.open_td(class_=["tr", rendered[2][0]]) html.write_text(rendered[2][1]) html.close_td() html.close_tr() html.open_tr() html.open_td(colspan=2, class_=["center", rendered[0][0]]) html.write_text(rendered[0][1]) html.close_td() html.close_tr() for css, cont in rendered[5:]: html.open_tr() html.open_td(colspan=2, class_=["cont", css]) html.write_text(cont) html.close_td() html.close_tr() html.open_tr() html.open_td(class_=["bl", rendered[3][0]]) html.write_text(rendered[3][1]) html.close_td() html.open_td(class_=["br", rendered[4][0]]) html.write_text(rendered[4][1]) html.close_td() html.close_tr() html.close_table() html.close_div() if group_open: html.close_td() html.close_tr() html.close_table() if not user.may("general.act"): return init_rowselect(_get_view_name(view))
def render(self, rows, view, group_cells, cells, num_columns, show_checkboxes): repeat_heading_every = 20 # in case column_headers is "repeat" html.open_table(class_='data table') last_group = None odd = "odd" column = 1 group_open = False num_cells = len(cells) if show_checkboxes: num_cells += 1 if not group_cells and view.get("column_headers") != "off": self._show_header_line(cells, num_columns, show_checkboxes) rows_with_ids = [(row_id(view, row), row) for row in rows] groups, rows_with_ids = calculate_view_grouping_of_services( rows_with_ids, row_group_cells=group_cells) visible_row_number = 0 group_hidden, num_grouped_rows = None, 0 for index, row in rows_with_ids: # Show group header, if a new group begins. But only if grouping # is activated if group_cells: this_group = group_value(row, group_cells) if this_group != last_group: if column != 1: # not a the beginning of a new line for _i in range(column - 1, num_columns): html.td('', class_="gap") html.td('', class_="fillup", colspan=num_cells) html.close_tr() column = 1 group_open = True visible_row_number = 0 # paint group header, but only if it is non-empty header_is_empty = True for cell in group_cells: _tdclass, content = cell.render(row) if content: header_is_empty = False break if not header_is_empty: html.open_tr(class_="groupheader") html.open_td(class_="groupheader", colspan=(num_cells * (num_columns + 2) + (num_columns - 1))) html.open_table(class_="groupheader", cellspacing="0", cellpadding="0", border="0") html.open_tr() painted = False for cell in group_cells: if painted: html.td(', ') painted = cell.paint(row) html.close_tr() html.close_table() html.close_td() html.close_tr() odd = "odd" # Table headers if view.get("column_headers") != "off": self._show_header_line(cells, num_columns, show_checkboxes) last_group = this_group # Should we wrap over to a new line? if column >= num_columns + 1: html.close_tr() column = 1 # At the beginning of the line? Beginn new line if column == 1: if view.get("column_headers") == "repeat": if visible_row_number > 0 and visible_row_number % repeat_heading_every == 0: self._show_header_line(cells, num_columns, show_checkboxes) visible_row_number += 1 # In one-column layout we use the state of the service # or host - if available - to color the complete line if num_columns == 1: # render state, if available through whole tr if not row.get('service_description'): state = row.get("host_state", 0) if state > 0: state += 1 # 1 is critical for hosts else: state = row.get("service_state", 0) else: state = 0 if index in groups: group_spec, num_grouped_rows = groups[index] group_hidden = grouped_row_title(index, group_spec, num_grouped_rows, odd, num_cells) odd = "even" if odd == "odd" else "odd" css_classes = [] hide = "" if num_grouped_rows > 0: num_grouped_rows -= 1 if group_hidden: hide = "display:none" if group_hidden is not None and num_grouped_rows == 0: # last row in group css_classes.append("group_end") group_hidden = None odd = "even" if odd == "odd" else "odd" if num_columns > 1: css_classes.append("multicolumn") css_classes += ["%s%d" % (odd, state)] html.open_tr(class_=["data"] + css_classes, style=hide) # Not first columns: Create one empty column as separator else: html.open_td(class_="gap") html.close_td() if show_checkboxes: render_checkbox_td(view, row, num_cells) for cell in cells: cell.paint(row) column += 1 if group_open: for _i in range(column - 1, num_columns): html.td('', class_="gap") html.td('', class_="fillup", colspan=num_cells) html.close_tr() html.close_table() if not user.may("general.act"): return init_rowselect(_get_view_name(view))
def render_bi_availability(view: "View", aggr_rows: 'Rows') -> None: config.user.need_permission("general.see_availability") av_mode = html.request.get_ascii_input_mandatory("av_mode", "availability") avoptions = get_availability_options_from_url("bi") title = view_title(view.spec) if av_mode == "timeline": title = _("Timeline of") + " " + title else: title = _("Availability of") + " " + title if html.output_format != "csv_export": html.body_start(title) breadcrumb = view.breadcrumb() breadcrumb.append( BreadcrumbItem( title=title, url=breadcrumb[-1].url + "&mode=availability", )) html.top_heading(title, breadcrumb) html.begin_context_buttons() html.toggle_button("avoptions", False, "painteroptions", _("Configure details of the report")) html.context_button(_("Status View"), html.makeuri([("mode", "status")]), "status") if config.reporting_available() and config.user.may("general.reporting"): html.context_button(_("Export as PDF"), html.makeuri([], filename="report_instant.py"), "report") if config.user.may("general.csv_export"): html.context_button(_("Export as CSV"), html.makeuri([("output_format", "csv_export")]), "download_csv") if av_mode == "timeline": html.context_button(_("Availability"), html.makeuri([("av_mode", "availability")]), "availability") elif len(aggr_rows) == 1: aggr_name = aggr_rows[0]["aggr_name"] aggr_group = aggr_rows[0]["aggr_group"] timeline_url = html.makeuri([("av_mode", "timeline"), ("av_aggr_name", aggr_name), ("av_aggr_group", aggr_group)]) html.context_button(_("Timeline"), timeline_url, "timeline") html.end_context_buttons() avoptions = render_availability_options("bi") if not html.has_user_errors(): logrow_limit = avoptions["logrow_limit"] if logrow_limit == 0: livestatus_limit = None else: livestatus_limit = (len(aggr_rows) * logrow_limit) spans: List[AVSpan] = [] # iterate all aggregation rows timewarpcode = HTML() timewarp = html.request.get_integer_input("timewarp") has_reached_logrow_limit = False timeline_containers, fetched_rows = availability.get_timeline_containers( aggr_rows, avoptions, timewarp, livestatus_limit + 1 if livestatus_limit is not None else None) if livestatus_limit and fetched_rows > livestatus_limit: has_reached_logrow_limit = True for timeline_container in timeline_containers: tree = timeline_container.aggr_tree these_spans = timeline_container.timeline timewarp_tree_state = timeline_container.timewarp_state spans += these_spans # render selected time warp for the corresponding aggregation row (should be matched by only one) if timewarp and timewarp_tree_state: state, assumed_state, node, _subtrees = timewarp_tree_state eff_state = state if assumed_state is not None: eff_state = assumed_state row = { "aggr_tree": tree, "aggr_treestate": timewarp_tree_state, "aggr_state": state, # state disregarding assumptions "aggr_assumed_state": assumed_state, # is None, if there are no assumptions "aggr_effective_state": eff_state, # is assumed_state, if there are assumptions, else real state "aggr_name": node["title"], "aggr_output": eff_state["output"], "aggr_hosts": node["reqhosts"], "aggr_function": node["func"], "aggr_group": html.request.var("aggr_group"), } renderer = bi.FoldableTreeRendererTree( row, omit_root=False, expansion_level=config.user.bi_expansion_level, only_problems=False, lazy=False) tdclass, htmlcode = renderer.css_class(), renderer.render() with html.plugged(): # TODO: SOMETHING IS WRONG IN HERE (used to be the same situation in original code!) # FIXME: WHAT is wrong in here?? html.open_h3() # render icons for back and forth button_back_shown = False button_forth_shown = False if int(these_spans[0]["from"]) == timewarp: html.disabled_icon_button("back_off") button_back_shown = True previous_span = None for span in these_spans: if not button_back_shown and int( span["from"]) == timewarp and previous_span is not None: html.icon_button( html.makeuri([("timewarp", str(int(previous_span["from"])))]), _("Jump one phase back"), "back") button_back_shown = True # Multiple followup spans can have the same "from" time # We only show one forth-arrow with an actual time difference elif not button_forth_shown and previous_span and int( previous_span["from"]) == timewarp and int( span["from"]) != timewarp: html.icon_button(html.makeuri([("timewarp", str(int(span["from"])))]), _("Jump one phase forth"), "forth") button_forth_shown = True previous_span = span if not button_forth_shown: html.disabled_icon_button("forth_off") html.write_text(" ") html.icon_button(html.makeuri([("timewarp", "")]), _("Close Timewarp"), "closetimewarp") html.write_text("%s %s" % (_("Timewarp to "), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timewarp)))) html.close_h3() html.open_table(class_=["data", "table", "timewarp"]) html.open_tr(class_=["data", "odd0"]) html.open_td(class_=tdclass) html.write_html(htmlcode) html.close_td() html.close_tr() html.close_table() timewarpcode += html.drain() # Note: 'spans_by_object' returns two arguments which are used by # all availability views but not by BI. There we have to take # only complete aggregations av_rawdata = availability.spans_by_object(spans, None)[0] av_data = availability.compute_availability("bi", av_rawdata, avoptions) # If we abolish the limit we have to fetch the data again # with changed logrow_limit = 0, which means no limit if has_reached_logrow_limit: text = _( "Your query matched more than %d log entries. " "<b>Note:</b> The shown data does not necessarily reflect the " "matched entries and the result might be incomplete. ") % avoptions["logrow_limit"] text += html.render_a(_('Repeat query without limit.'), html.makeuri([("_unset_logrow_limit", "1")])) html.show_warning(text) if html.output_format == "csv_export" and config.user.may("general.csv_export"): _output_csv("bi", av_mode, av_data, avoptions) return html.write(timewarpcode) do_render_availability("bi", av_rawdata, av_data, av_mode, None, avoptions) html.bottom_footer() html.body_end()
def _render_group(self, rows_with_ids, header, view, group_cells, cells, num_columns, show_checkboxes): repeat_heading_every = 20 # in case column_headers is "repeat" if group_cells: self._show_group_header_table(group_cells, rows_with_ids[0][1]) html.open_table(class_="data") odd = "odd" column_headers = view.get("column_headers") if column_headers != "off": self._show_header_line(cells, show_checkboxes) groups, rows_with_ids = calculate_view_grouping_of_services( rows_with_ids, row_group_cells=None) visible_row_number = 0 group_hidden, num_grouped_rows = None, 0 for index, row in rows_with_ids: if view.get("column_headers") == "repeat": if visible_row_number > 0 and visible_row_number % repeat_heading_every == 0: self._show_header_line(cells, show_checkboxes) visible_row_number += 1 odd = "even" if odd == "odd" else "odd" # state = row.get("service_state", row.get("aggr_state")) state = utils.saveint(row.get("service_state")) if state is None: state = utils.saveint(row.get("host_state", 0)) if state > 0: state += 1 # 1 is critical for hosts num_cells = len(cells) if index in groups: group_spec, num_grouped_rows = groups[index] group_hidden = grouped_row_title(index, group_spec, num_grouped_rows, odd, num_cells) odd = "even" if odd == "odd" else "odd" css_classes = [] if is_stale(row): css_classes.append("stale") hide = "" if num_grouped_rows > 0: num_grouped_rows -= 1 if group_hidden: hide = "display:none" if group_hidden is not None and num_grouped_rows == 0: # last row in group css_classes.append("group_end") group_hidden = None css_classes.append("%s%d" % (odd, state)) html.open_tr(class_=["data"] + css_classes, style=hide) if show_checkboxes: render_checkbox_td(view, row, num_cells) for cell in cells: cell.paint(row) html.close_tr() html.close_table() # Don't make rows selectable when no commands can be fired # Ignore "C" display option here. Otherwise the rows will not be selectable # after view reload. if not user.may("general.act"): return init_rowselect(_get_view_name(view))
def page(self): # Let exceptions from loading notification scripts happen now watolib.load_notification_scripts() html.begin_form("user", method="POST") html.prevent_password_auto_completion() forms.header(_("Identity")) # ID forms.section(_("Username"), simple=not self._is_new_user) if self._is_new_user: vs_user_id = UserID(allow_empty=False) else: vs_user_id = FixedValue(self._user_id) vs_user_id.render_input("user_id", self._user_id) def lockable_input(name, dflt): if not self._is_locked(name): html.text_input(name, self._user.get(name, dflt), size=50) else: html.write_text(self._user.get(name, dflt)) html.hidden_field(name, self._user.get(name, dflt)) # Full name forms.section(_("Full name")) lockable_input('alias', self._user_id) html.help(_("Full name or alias of the user")) # Email address forms.section(_("Email address")) email = self._user.get("email", "") if not self._is_locked("email"): EmailAddress().render_input("email", email) else: html.write_text(email) html.hidden_field("email", email) html.help( _("The email address is optional and is needed " "if the user is a monitoring contact and receives notifications " "via Email.")) forms.section(_("Pager address")) lockable_input('pager', '') html.help(_("The pager address is optional ")) if cmk_version.is_managed_edition(): forms.section(self._vs_customer.title()) self._vs_customer.render_input("customer", managed.get_customer_id(self._user)) html.help(self._vs_customer.help()) vs_sites = self._vs_sites() forms.section(vs_sites.title()) authorized_sites = self._user.get("authorized_sites", vs_sites.default_value()) if not self._is_locked("authorized_sites"): vs_sites.render_input("authorized_sites", authorized_sites) else: html.write_html(vs_sites.value_to_text(authorized_sites)) html.help(vs_sites.help()) self._show_custom_user_attributes('ident') forms.header(_("Security")) forms.section(_("Authentication")) is_automation = self._user.get("automation_secret", None) is not None html.radiobutton("authmethod", "password", not is_automation, _("Normal user login with password")) html.open_ul() html.open_table() html.open_tr() html.td(_("password:"******"_password_" + self._pw_suffix(), autocomplete="new-password") html.close_td() html.close_tr() html.open_tr() html.td(_("repeat:")) html.open_td() html.password_input("_password2_" + self._pw_suffix(), autocomplete="new-password") html.write_text(" (%s)" % _("optional")) html.close_td() html.close_tr() html.open_tr() html.td("%s:" % _("Enforce change")) html.open_td() # Only make password enforcement selection possible when user is allowed to change the PW uid = None if self._user_id is None else UserId(self._user_id) if (self._is_new_user or (config.user_may(uid, 'general.edit_profile') and config.user_may(uid, 'general.change_password'))): html.checkbox( "enforce_pw_change", self._user.get("enforce_pw_change", False), label=_("Change password at next login or access")) else: html.write_text( _("Not permitted to change the password. Change can not be enforced." )) else: html.i( _('The password can not be changed (It is locked by the user connector).' )) html.hidden_field('_password', '') html.hidden_field('_password2', '') html.close_td() html.close_tr() html.close_table() html.close_ul() html.radiobutton("authmethod", "secret", is_automation, _("Automation secret for machine accounts")) html.open_ul() html.text_input("_auth_secret", self._user.get("automation_secret", ""), size=30, id_="automation_secret") html.write_text(" ") html.open_b(style=["position: relative", "top: 4px;"]) html.write(" ") html.icon_button( "javascript:cmk.wato.randomize_secret('automation_secret', 20);", _("Create random secret"), "random") html.close_b() html.close_ul() html.help( _("If you want the user to be able to login " "then specify a password here. Users without a login make sense " "if they are monitoring contacts that are just used for " "notifications. The repetition of the password is optional. " "<br>For accounts used by automation processes (such as fetching " "data from views for further procession), set the method to " "<u>secret</u>. The secret will be stored in a local file. Processes " "with read access to that file will be able to use Multisite as " "a webservice without any further configuration.")) # Locking forms.section(_("Disable password"), simple=True) if not self._is_locked('locked'): html.checkbox("locked", self._user.get("locked", False), label=_("disable the login to this account")) else: html.write_text( _('Login disabled') if self._user. get("locked", False) else _('Login possible')) html.hidden_field('locked', '1' if self._user.get("locked", False) else '') html.help( _("Disabling the password will prevent a user from logging in while " "retaining the original password. Notifications are not affected " "by this setting.")) forms.section(_("Idle timeout")) idle_timeout = self._user.get("idle_timeout") if not self._is_locked("idle_timeout"): watolib.get_vs_user_idle_timeout().render_input( "idle_timeout", idle_timeout) else: html.write_text(idle_timeout) html.hidden_field("idle_timeout", idle_timeout) # Roles forms.section(_("Roles")) is_member_of_at_least_one = False for role_id, role in sorted(self._roles.items(), key=lambda x: (x[1]["alias"], x[0])): if not self._is_locked("roles"): html.checkbox("role_" + role_id, role_id in self._user.get("roles", [])) url = watolib.folder_preserving_link([("mode", "edit_role"), ("edit", role_id)]) html.a(role["alias"], href=url) html.br() else: is_member = role_id in self._user.get("roles", []) if is_member: is_member_of_at_least_one = True url = watolib.folder_preserving_link([("mode", "edit_role"), ("edit", role_id)]) html.a(role["alias"], href=url) html.br() html.hidden_field("role_" + role_id, '1' if is_member else '') if self._is_locked('roles') and not is_member_of_at_least_one: html.i(_('No roles assigned.')) self._show_custom_user_attributes('security') # Contact groups forms.header(_("Contact Groups"), isopen=False) forms.section() groups_page_url = watolib.folder_preserving_link([("mode", "contact_groups")]) group_assign_url = watolib.folder_preserving_link([ ("mode", "rulesets"), ("group", "grouping") ]) if not self._contact_groups: html.write( _("Please first create some <a href='%s'>contact groups</a>") % groups_page_url) else: entries = sorted([(group['alias'] or c, c) for c, group in self._contact_groups.items()]) is_member_of_at_least_one = False for alias, gid in entries: is_member = gid in self._user.get("contactgroups", []) if not self._is_locked('contactgroups'): html.checkbox("cg_" + gid, gid in self._user.get("contactgroups", [])) else: if is_member: is_member_of_at_least_one = True html.hidden_field("cg_" + gid, '1' if is_member else '') if not self._is_locked('contactgroups') or is_member: url = watolib.folder_preserving_link([ ("mode", "edit_contact_group"), ("edit", gid) ]) html.a(alias, href=url) html.br() if self._is_locked( 'contactgroups') and not is_member_of_at_least_one: html.i(_('No contact groups assigned.')) html.help( _("Contact groups are used to assign monitoring " "objects to users. If you haven't defined any contact groups yet, " "then first <a href='%s'>do so</a>. Hosts and services can be " "assigned to contact groups using <a href='%s'>rules</a>.<br><br>" "If you do not put the user into any contact group " "then no monitoring contact will be created for the user.") % (groups_page_url, group_assign_url)) forms.header(_("Notifications"), isopen=False) if not self._rbn_enabled(): forms.section(_("Enabling"), simple=True) html.checkbox("notifications_enabled", self._user.get("notifications_enabled", False), label=_("enable notifications")) html.help( _("Notifications are sent out " "when the status of a host or service changes.")) # Notification period forms.section(_("Notification time period")) user_np = self._user.get("notification_period") if not isinstance(user_np, str): raise Exception("invalid notification period %r" % (user_np, )) choices: Choices = [(id_, "%s" % (tp["alias"])) for (id_, tp) in self._timeperiods.items()] html.dropdown("notification_period", choices, deflt=user_np, ordered=True) html.help( _("Only during this time period the " "user will get notifications about host or service alerts.")) # Notification options notification_option_names = { # defined here: _() must be executed always! "host": { "d": _("Host goes down"), "u": _("Host gets unreachble"), "r": _("Host goes up again"), }, "service": { "w": _("Service goes into warning state"), "u": _("Service goes into unknown state"), "c": _("Service goes into critical state"), "r": _("Service recovers to OK"), }, "both": { "f": _("Start or end of flapping state"), "s": _("Start or end of a scheduled downtime"), } } forms.section(_("Notification Options")) for title, what, opts in [(_("Host events"), "host", "durfs"), (_("Service events"), "service", "wucrfs")]: html.write_text("%s:" % title) html.open_ul() user_opts = self._user.get(what + "_notification_options", opts) for opt in opts: opt_name = notification_option_names[what].get( opt, notification_option_names["both"].get(opt)) html.checkbox(what + "_" + opt, opt in user_opts, label=opt_name) html.br() html.close_ul() html.help( _("Here you specify which types of alerts " "will be notified to this contact. Note: these settings will only be saved " "and used if the user is member of a contact group.")) forms.section(_("Notification Method")) watolib.get_vs_flexible_notifications().render_input( "notification_method", self._user.get("notification_method")) else: forms.section(_("Fallback notifications"), simple=True) html.checkbox("fallback_contact", self._user.get("fallback_contact", False), label=_("Receive fallback notifications")) html.help( _("In case none of your notification rules handles a certain event a notification " "will be sent to this contact. This makes sure that in that case at least <i>someone</i> " "gets notified. Furthermore this contact will be used for notifications to any host or service " "that is not known to the monitoring. This can happen when you forward notifications " "from the Event Console.<br><br>Notification fallback can also configured in the global " "setting <a href=\"wato.py?mode=edit_configvar&varname=notification_fallback_email\">" "Fallback email address for notifications</a>.")) self._show_custom_user_attributes('notify') forms.header(_("Personal Settings"), isopen=False) select_language(self._user) self._show_custom_user_attributes('personal') # Later we could add custom macros here, which then could be used # for notifications. On the other hand, if we implement some check_mk # --notify, we could directly access the data in the account with the need # to store values in the monitoring core. We'll see what future brings. forms.end() if self._is_new_user: html.set_focus("user_id") else: html.set_focus("alias") html.hidden_fields() html.end_form()
def show(self): # pie_id, what, table, filter, dashlet): pie_id = "dashlet_%d" % self._dashlet_id pie_diameter = 130 pie_left_aspect = 0.5 pie_right_aspect = 0.8 what = self._livestatus_table() table = self._table() filter_ = self._filter() if what == 'hosts': info = 'host' infos = [info] else: info = 'service' infos = ['host', 'service'] use_filters = visuals.filters_of_visual(self._dashlet_spec, infos) for filt in use_filters: if filt.available() and not isinstance(filt, FilterCRESite): filter_ += filt.filter(info) query = "GET %s\n" % what for entry in table: query += entry[3] query += filter_ site = self._dashlet_spec['context'].get('siteopt', {}).get('site') if site: sites.live().set_only_sites([site]) result = sites.live().query_row(query) sites.live().set_only_sites() else: result = sites.live().query_summed_stats(query) pies = zip(table, result) total = sum([x[1] for x in pies]) html.open_div(class_="stats") html.canvas( '', class_="pie", id_="%s_stats" % pie_id, width=pie_diameter, height=pie_diameter, style="float: left") html.img(html.theme_url("images/globe.png"), class_="globe") html.open_table( class_=["hoststats"] + (["narrow"] if len(pies) > 0 else []), style="float:left") table_entries = pies while len(table_entries) < 6: table_entries = table_entries + [(("", None, "", ""), HTML(" "))] table_entries.append(((_("Total"), "", "all%s" % what, ""), total)) for (name, color, viewurl, query), count in table_entries: url = "view.py?view_name=" + viewurl + "&filled_in=filter&search=1" for filter_name, url_params in self._dashlet_spec['context'].items(): if filter_name == "wato_folder" and html.request.has_var("wato_folder"): url += "&wato_folder=" + html.request.var("wato_folder") elif filter_name == "svcstate": # The svcstate filter URL vars are controlled by dashlet continue else: url += '&' + html.urlencode_vars(url_params.items()) html.open_tr() html.th(html.render_a(name, href=url)) html.td('', class_="color", style="background-color: %s" % color if color else '') html.td(html.render_a(count, href=url)) html.close_tr() html.close_table() pie_parts = [] if total > 0: # Count number of non-empty classes num_nonzero = 0 for info, value in pies: if value > 0: num_nonzero += 1 # Each non-zero class gets at least a view pixels of visible thickness. # We reserve that space right now. All computations are done in percent # of the radius. separator = 0.02 # 3% of radius remaining_separatorspace = num_nonzero * separator # space for separators remaining_radius = 1 - remaining_separatorspace # remaining space remaining_part = 1.0 # keep track of remaining part, 1.0 = 100% # Loop over classes, begin with most outer sphere. Inner spheres show # worse states and appear larger to the user (which is the reason we # are doing all this stuff in the first place) for (name, color, viewurl, _q), value in pies[::1]: if value > 0 and remaining_part > 0: # skip empty classes # compute radius of this sphere *including all inner spheres!* The first # sphere always gets a radius of 1.0, of course. radius = remaining_separatorspace + remaining_radius * (remaining_part** (1 / 3.0)) pie_parts.append('chart_pie("%s", %f, %f, %r, true);' % (pie_id, pie_right_aspect, radius, color)) pie_parts.append('chart_pie("%s", %f, %f, %r, false);' % (pie_id, pie_left_aspect, radius, color)) # compute relative part of this class part = float(value) / total # ranges from 0 to 1 remaining_part -= part remaining_separatorspace -= separator html.close_div() html.javascript(""" function chart_pie(pie_id, x_scale, radius, color, right_side) { var context = document.getElementById(pie_id + "_stats").getContext('2d'); if (!context) return; var pie_x = %(x)f; var pie_y = %(y)f; var pie_d = %(d)f; context.fillStyle = color; context.save(); context.translate(pie_x, pie_y); context.scale(x_scale, 1); context.beginPath(); if(right_side) context.arc(0, 0, (pie_d / 2) * radius, 1.5 * Math.PI, 0.5 * Math.PI, false); else context.arc(0, 0, (pie_d / 2) * radius, 0.5 * Math.PI, 1.5 * Math.PI, false); context.closePath(); context.fill(); context.restore(); context = null; } if (cmk.dashboard.has_canvas_support()) { %(p)s } """ % { "x": pie_diameter / 2, "y": pie_diameter / 2, "d": pie_diameter, 'p': '\n'.join(pie_parts) })
def _render_headers(self, actions_enabled, actions_visible, empty_columns): if self.options["omit_headers"]: return table_id = self.id html.open_tr() first_col = True for nr, (header, css, help_txt, sortable) in enumerate(self.headers): if self.options["omit_empty_columns"] and empty_columns[nr]: continue text = header if help_txt: header = '<span title="%s">%s</span>' % ( html.attrencode(help_txt), header) css_class = "header_%s" % css if css else None if not self.options["sortable"] or not sortable: html.open_th(class_=css_class) else: reverse = 0 sort = html.request.var('_%s_sort' % table_id) if sort: sort_col, sort_reverse = map(int, sort.split(',', 1)) if sort_col == nr: reverse = 1 if sort_reverse == 0 else 0 action_uri = html.makeactionuri([('_%s_sort' % table_id, '%d,%d' % (nr, reverse))]) html.open_th(class_=["sort", css_class], title=_("Sort by %s") % text, onclick="location.href='%s'" % action_uri) # Add the table action link if first_col: first_col = False if actions_enabled: if not header: header = " " # Fixes layout problem with white triangle if actions_visible: state = '0' help_txt = _('Hide table actions') img = 'table_actions_on' else: state = '1' help_txt = _('Display table actions') img = 'table_actions_off' html.open_div(class_=["toggle_actions"]) html.icon_button(html.makeuri([('_%s_actions' % table_id, state)]), help_txt, img, cssclass='toggle_actions') html.open_span() html.write(header) html.close_span() html.close_div() else: html.write(header) else: html.write(header) html.close_th() html.close_tr()
def _write_table( self, rows: TableRows, num_rows_unlimited: int, actions_enabled: bool, actions_visible: bool, search_term: Optional[str], ) -> None: if not self.options["omit_update_header"]: row_info = _("1 row") if len( rows) == 1 else _("%d rows") % num_rows_unlimited html.javascript("cmk.utils.update_row_info(%s);" % json.dumps(row_info)) table_id = self.id num_cols = self._get_num_cols(rows) empty_columns = self._get_empty_columns(rows, num_cols) if self.options["omit_empty_columns"]: num_cols -= len([v for v in empty_columns if v]) html.open_table(class_=["data", "oddeven", self.css]) # If we have no group headers then paint the headers now if self.rows and not isinstance(self.rows[0], GroupHeader): self._render_headers( actions_enabled, actions_visible, empty_columns, ) if actions_enabled and actions_visible: html.open_tr(class_=["data", "even0", "actions"]) html.open_td(colspan=num_cols) if not html.in_form(): html.begin_form("%s_actions" % table_id) if request.has_var("_%s_sort" % table_id): html.open_div(class_=["sort"]) html.button("_%s_reset_sorting" % table_id, _("Reset sorting")) html.close_div() if not html.in_form(): html.begin_form("%s_actions" % table_id) html.hidden_fields() html.end_form() html.close_tr() for nr, row in enumerate(rows): # Intermediate header if isinstance(row, GroupHeader): # Show the header only, if at least one (non-header) row follows if nr < len(rows) - 1 and not isinstance( rows[nr + 1], GroupHeader): html.open_tr(class_="groupheader") html.open_td(colspan=num_cols) html.h3(row.title) html.close_td() html.close_tr() self._render_headers(actions_enabled, actions_visible, empty_columns) continue oddeven_name = "even" if nr % 2 == 0 else "odd" class_ = ["data", "%s%d" % (oddeven_name, row.state)] if isinstance(row.css, list): class_.extend([c for c in row.css if c is not None]) elif row.css is not None: class_.append(row.css) html.open_tr(class_=class_, id_=row.id_, onmouseover=row.onmouseover, onmouseout=row.onmouseout) for col_index, cell in enumerate(row.cells): if self.options["omit_empty_columns"] and empty_columns[ col_index]: continue html.td(cell.content, class_=cell.css, colspan=cell.colspan) html.close_tr() if not rows and search_term: html.open_tr(class_=["data", "odd0", "no_match"]) html.td( _("Found no matching rows. Please try another search term."), colspan=num_cols) html.close_tr() html.close_table()
def render(self, rows, view, group_cells, cells, num_columns, show_checkboxes): header_majorities = self._matrix_find_majorities_for_header( rows, group_cells) value_counts, row_majorities = self._matrix_find_majorities( rows, cells) painter_options = PainterOptions.get_instance() for groups, unique_row_ids, matrix_cells in \ create_matrices(rows, group_cells, cells, num_columns): # Paint the matrix. Begin with the group headers html.open_table(class_="data matrix") odd = "odd" for cell_nr, cell in enumerate(group_cells): odd = "even" if odd == "odd" else "odd" html.open_tr(class_="data %s0" % odd) html.open_td(class_="matrixhead") html.write_text(cell.title(use_short=False)) html.close_td() for _group, group_row in groups: tdclass, content = cell.render(group_row) if cell_nr > 0: gv = group_value(group_row, [cell]) majority_value = header_majorities.get( cell_nr - 1, None) if majority_value is not None and majority_value != gv: tdclass += " minority" html.open_td(class_=["left", tdclass]) html.write_text(content) html.close_td() html.close_tr() # Now for each unique service^H^H^H^H^H^H ID column paint one row for rid in unique_row_ids: # Omit rows where all cells have the same values if painter_options.get("matrix_omit_uniform"): at_least_one_different = False for counts in value_counts[rid].values(): if len(counts) > 1: at_least_one_different = True break if not at_least_one_different: continue odd = "even" if odd == "odd" else "odd" html.open_tr(class_="data %s0" % odd) tdclass, content = cells[0].render( list(matrix_cells[rid].values())[0]) html.open_td(class_=["left", tdclass]) html.write_text(content) html.close_td() # Now go through the groups and paint the rest of the # columns for group_id, group_row in groups: cell_row = matrix_cells[rid].get(group_id) if cell_row is None: html.td('') else: if len(cells) > 2: html.open_td(class_="cell") html.open_table() for cell_nr, cell in enumerate(cells[1:]): tdclass, content = cell.render(cell_row) gv = group_value(cell_row, [cell]) majority_value = row_majorities[rid].get( cell_nr, None) if majority_value is not None and majority_value != gv: tdclass += " minority" if len(cells) > 2: html.open_tr() html.td(content, class_=tdclass) if len(cells) > 2: html.close_tr() if len(cells) > 2: html.close_table() html.close_td() html.close_tr() html.close_table()
def _render_headers(self, actions_enabled: bool, actions_visible: bool, empty_columns: List[bool]) -> None: if self.options["omit_headers"]: return table_id = self.id html.open_tr() first_col = True for nr, header in enumerate(self.headers): if self.options["omit_empty_columns"] and empty_columns[nr]: continue if header.help_txt: header_title: HTML = html.render_span(header.title, title=header.help_txt) else: header_title = header.title if not isinstance(header.css, list): css_class: "CSSSpec" = [header.css] else: css_class = header.css assert isinstance(css_class, list) css_class = [("header_%s" % c) for c in css_class if c is not None] if not self.options["sortable"] or not header.sortable: html.open_th(class_=css_class) else: css_class.insert(0, "sort") reverse = 0 sort = request.get_ascii_input("_%s_sort" % table_id) if sort: sort_col, sort_reverse = map(int, sort.split(",", 1)) if sort_col == nr: reverse = 1 if sort_reverse == 0 else 0 action_uri = makeactionuri(request, transactions, [("_%s_sort" % table_id, "%d,%d" % (nr, reverse))]) html.open_th( class_=css_class, title=_("Sort by %s") % header.title, onclick="location.href='%s'" % action_uri, ) # Add the table action link if first_col: first_col = False if actions_enabled: if not header_title: header_title = HTML( " " ) # Fixes layout problem with white triangle if actions_visible: state = "0" help_txt = _("Hide table actions") img = "table_actions_on" else: state = "1" help_txt = _("Display table actions") img = "table_actions_off" html.open_div(class_=["toggle_actions"]) html.icon_button( makeuri(request, [("_%s_actions" % table_id, state)]), help_txt, img, cssclass="toggle_actions", ) html.span(header_title) html.close_div() else: html.write_text(header_title) else: html.write_text(header_title) html.close_th() html.close_tr()
def page(self): html.open_div(id_="ldap") html.open_table() html.open_tr() html.open_td() html.begin_form("connection", method="POST") html.prevent_password_auto_completion() vs = self._valuespec() vs.render_input("connection", self._connection_cfg) vs.set_focus("connection") html.hidden_fields() html.end_form() html.close_td() html.open_td(style="padding-left:10px;vertical-align:top") html.h2(_('Diagnostics')) if not html.request.var('_test') or not self._connection_id: html.show_message( HTML('<p>%s</p><p>%s</p>' % ( _('You can verify the single parts of your ldap configuration using this ' 'dialog. Simply make your configuration in the form on the left side and ' 'hit the "Save & Test" button to execute the tests. After ' 'the page reload, you should see the results of the test here.' ), _('If you need help during configuration or experience problems, please refer ' 'to the <a target="_blank" ' 'href="https://checkmk.com/checkmk_multisite_ldap_integration.html">' 'LDAP Documentation</a>.')))) else: connection = userdb.get_connection(self._connection_id) assert isinstance(connection, LDAPUserConnector) for address in connection.servers(): html.h3("%s: %s" % (_('Server'), address)) with table_element('test', searchable=False) as table: for title, test_func in self._tests(): table.row() try: state, msg = test_func(connection, address) except Exception as e: state = False msg = _('Exception: %s') % html.render_text( "%s" % e) logger.exception("error testing LDAP %s for %s", title, address) if state: img = html.render_icon("success", _('Success')) else: img = html.render_icon("failed", _("Failed")) table.cell(_("Test"), title) table.cell(_("State"), img) table.cell(_("Details"), msg) connection.disconnect() html.close_td() html.close_tr() html.close_table() html.close_div()
def _show_start_form(self): html.begin_form("parentscan", method="POST") html.hidden_fields() # Mode of action html.open_p() if not self._complete_folder: num_selected = len(get_hosts_from_checkboxes()) html.write_text( _("You have selected <b>%d</b> hosts for parent scan. ") % num_selected) html.p( _("The parent scan will try to detect the last gateway " "on layer 3 (IP) before a host. This will be done by " "calling <tt>traceroute</tt>. If a gateway is found by " "that way and its IP address belongs to one of your " "monitored hosts, that host will be used as the hosts " "parent. If no such host exists, an artifical ping-only " "gateway host will be created if you have not disabled " "this feature.")) forms.header(_("Settings for Parent Scan")) self._settings = ParentScanSettings(**user.load_file( "parentscan", { "where": "subfolder", "alias": _("Created by parent scan"), "recurse": True, "select": "noexplicit", "timeout": 8, "probes": 2, "ping_probes": 5, "max_ttl": 10, "force_explicit": False, })) # Selection forms.section(_("Selection")) if self._complete_folder: html.checkbox("recurse", self._settings.recurse, label=_("Include all subfolders")) html.br() html.radiobutton( "select", "noexplicit", self._settings.select == "noexplicit", _("Skip hosts with explicit parent definitions (even if empty)") + "<br>") html.radiobutton( "select", "no", self._settings.select == "no", _("Skip hosts hosts with non-empty parents (also if inherited)") + "<br>") html.radiobutton("select", "ignore", self._settings.select == "ignore", _("Scan all hosts") + "<br>") # Performance forms.section(_("Performance")) html.open_table() html.open_tr() html.open_td() html.write_text(_("Timeout for responses") + ":") html.close_td() html.open_td() html.text_input("timeout", str(self._settings.timeout), size=2, cssclass="number") html.write_text(_("sec")) html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Number of probes per hop") + ":") html.close_td() html.open_td() html.text_input("probes", str(self._settings.probes), size=2, cssclass="number") html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Maximum distance (TTL) to gateway") + ":") html.close_td() html.open_td() html.text_input("max_ttl", str(self._settings.max_ttl), size=2, cssclass="number") html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Number of PING probes") + ":") html.help( _("After a gateway has been found, Check_MK checks if it is reachable " "via PING. If not, it is skipped and the next gateway nearer to the " "monitoring core is being tried. You can disable this check by setting " "the number of PING probes to 0.")) html.close_td() html.open_td() html.text_input("ping_probes", str(self._settings.ping_probes), size=2, cssclass="number") html.close_td() html.close_tr() html.close_table() # Configuring parent forms.section(_("Configuration")) html.checkbox( "force_explicit", deflt=self._settings.force_explicit, label= _("Force explicit setting for parents even if setting matches that of the folder" )) # Gateway creation forms.section(_("Creation of gateway hosts")) html.write_text(_("Create gateway hosts in")) html.open_ul() html.radiobutton( "where", "subfolder", self._settings.where == "subfolder", _("in the subfolder <b>%s/Parents</b>") % watolib.Folder.current_disk_folder().title()) html.br() html.radiobutton( "where", "here", self._settings.where == "here", _("directly in the folder <b>%s</b>") % watolib.Folder.current_disk_folder().title()) html.br() html.radiobutton("where", "there", self._settings.where == "there", _("in the same folder as the host")) html.br() html.radiobutton("where", "nowhere", self._settings.where == "nowhere", _("do not create gateway hosts")) html.close_ul() html.write_text(_("Alias for created gateway hosts") + ": ") html.text_input("alias", default_value=self._settings.alias) forms.end() # Start button html.button("_start", _("Start")) html.hidden_fields() html.end_form()
def show(self): if not watolib.is_wato_slave_site(): if not config.wato_enabled: html.write_text(_("WATO is disabled.")) return False user_folders = compute_foldertree() # # Render link target selection # selected_topic, selected_target = config.user.load_file( "foldertree", (_('Hosts'), 'allhosts')) topic_views = visuals_by_topic( list(views.get_permitted_views().items()) + list(dashboard.get_permitted_dashboards().items())) topics = [(t, t) for t, _s in topic_views] html.open_table() html.open_tr() html.td(_('Topic:'), class_="label") html.open_td() html.dropdown("topic", topics, deflt=selected_topic, onchange='cmk.sidebar.wato_tree_topic_changed(this)') html.close_td() html.close_tr() html.open_tr() html.td(_("View:"), class_="label") html.open_td() for topic, view_list in topic_views: targets = [] for t, title, name, is_view in view_list: if config.visible_views and name not in config.visible_views: continue if config.hidden_views and name in config.hidden_views: continue if t == topic: if not is_view: name = 'dashboard|' + name targets.append((name, title)) if topic != selected_topic: default = '' style = 'display:none' # type: Optional[str] else: default = selected_target style = None html.dropdown( "target_%s" % topic, targets, deflt=default, onchange='cmk.sidebar.wato_tree_target_changed(this)', style=style) html.close_td() html.close_tr() html.close_table() # Now render the whole tree if user_folders: render_tree_folder("wato-hosts", list(user_folders.values())[0], 'cmk.sidebar.wato_tree_click')
def _output_analysed_ruleset( self, all_rulesets, rulespec, svc_desc_or_item, svc_desc, known_settings=None ): if known_settings is None: known_settings = self._PARAMETERS_UNKNOWN def rule_url(rule: Rule) -> str: return watolib.folder_preserving_link( [ ("mode", "edit_rule"), ("varname", varname), ("rule_folder", rule.folder.path()), ("rule_id", rule.id), ("host", self._hostname), ( "item", watolib.mk_repr(svc_desc_or_item).decode() if svc_desc_or_item else "", ), ("service", watolib.mk_repr(svc_desc).decode() if svc_desc else ""), ] ) varname = rulespec.name valuespec = rulespec.valuespec url = watolib.folder_preserving_link( [ ("mode", "edit_ruleset"), ("varname", varname), ("host", self._hostname), ("item", watolib.mk_repr(svc_desc_or_item).decode()), ("service", watolib.mk_repr(svc_desc).decode()), ] ) forms.section(html.render_a(rulespec.title, url)) ruleset = all_rulesets.get(varname) setting, rules = ruleset.analyse_ruleset(self._hostname, svc_desc_or_item, svc_desc) html.open_table(class_="setting") html.open_tr() html.open_td(class_="reason") # Show reason for the determined value if len(rules) == 1: rule_folder, rule_index, rule = rules[0] url = rule_url(rule) html.a(_("Rule %d in %s") % (rule_index + 1, rule_folder.title()), href=rule_url(rule)) elif len(rules) > 1: html.a("%d %s" % (len(rules), _("Rules")), href=url) else: html.span(_("Default value")) html.close_td() # Show the resulting value or factory setting html.open_td(class_=["settingvalue", "used" if len(rules) > 0 else "unused"]) if isinstance(known_settings, dict) and "tp_computed_params" in known_settings: computed_at = known_settings["tp_computed_params"]["computed_at"] html.write_text( _("Timespecific parameters computed at %s") % cmk.utils.render.date_and_time(computed_at) ) html.br() known_settings = known_settings["tp_computed_params"]["params"] # In some cases we now the settings from a check_mk automation if known_settings is self._PARAMETERS_OMIT: return # Special handling for logwatch: The check parameter is always None. The actual # patterns are configured in logwatch_rules. We do not have access to the actual # patterns here but just to the useless "None". In order not to complicate things # we simply display nothing here. if varname == "logwatch_rules": pass elif known_settings is not self._PARAMETERS_UNKNOWN: try: html.write_text(valuespec.value_to_text(known_settings)) except Exception as e: if config.debug: raise html.write_text(_("Invalid parameter %r: %s") % (known_settings, e)) else: # For match type "dict" it can be the case the rule define some of the keys # while other keys are taken from the factory defaults. We need to show the # complete outcoming value here. if rules and ruleset.match_type() == "dict": if ( rulespec.factory_default is not watolib.Rulespec.NO_FACTORY_DEFAULT and rulespec.factory_default is not watolib.Rulespec.FACTORY_DEFAULT_UNUSED ): fd = rulespec.factory_default.copy() fd.update(setting) setting = fd if valuespec and not rules: # show the default value if rulespec.factory_default is watolib.Rulespec.FACTORY_DEFAULT_UNUSED: # Some rulesets are ineffective if they are empty html.write_text(_("(unused)")) elif rulespec.factory_default is not watolib.Rulespec.NO_FACTORY_DEFAULT: # If there is a factory default then show that one setting = rulespec.factory_default html.write_text(valuespec.value_to_text(setting)) elif ruleset.match_type() in ("all", "list"): # Rulesets that build lists are empty if no rule matches html.write_text(_("(no entry)")) else: # Else we use the default value of the valuespec html.write_text(valuespec.value_to_text(valuespec.default_value())) # We have a setting elif valuespec: if ruleset.match_type() == "all": for s in setting: html.write_text(valuespec.value_to_text(s)) else: html.write_text(valuespec.value_to_text(setting)) # Binary rule, no valuespec, outcome is True or False else: icon_name = "rule_%s%s" % ("yes" if setting else "no", "_off" if not rules else "") html.icon(icon_name, title=_("yes") if setting else _("no")) html.close_td() html.close_tr() html.close_table()
def container() -> None: global g_section_open section_close() html.open_tr() html.open_td(colspan=2) g_section_open = True
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( make_confirm_link( url=makeactionuri(request, transactions, [(ActionHandler.stop_job_var, job_id)]), message=_("Stop job %s%s?") % (job_id, cls._get_extra_info(job_status)), ), _("Stop this job"), "disable_test", ) if job_status.get("may_delete"): html.icon_button( make_confirm_link( url=makeactionuri( request, transactions, [(ActionHandler.delete_job_var, job_id)]), message=_("Delete job %s%s?") % (job_id, cls._get_extra_info(job_status)), ), _("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 _show_rows(self): rows = self._get_rows() if bool([r for r in rows if r.stats is None]): html.center(_("No data from any site")) return html.open_table(class_=["content_center", "tacticaloverview"], cellspacing="2", cellpadding="0", border="0") show_stales = self.parameters()["show_stale"] and config.user.may( "general.see_stales_in_tactical_overview") has_stale_objects = bool( [r for r in rows if r.what != "events" and r.stats[-1]]) for row in rows: if row.what == "events": amount, problems, unhandled_problems = row.stats stales = 0 # no events open and disabled in local site: don't show events if amount == 0 and not config.mkeventd_enabled: continue else: amount, problems, unhandled_problems, stales = row.stats context_vars = get_context_url_variables(row.context) html.open_tr() html.th(row.title) html.th(_("Problems"), class_="show_more_mode") html.th(_("Unhandled")) if show_stales and has_stale_objects: html.th(_("Stale"), class_="show_more_mode") html.close_tr() td_class = 'col4' if has_stale_objects else 'col3' html.open_tr() url = makeuri_contextless(request, row.views.total + context_vars, filename="view.py") html.open_td(class_=["total", td_class]) html.a("%s" % amount, href=url, target="main") html.close_td() for value, ty in [(problems, "handled"), (unhandled_problems, "unhandled")]: url = makeuri_contextless( request, getattr(row.views, ty) + context_vars, filename="view.py", ) html.open_td(class_=[ td_class, "states prob" if value != 0 else None, "show_more_mode" if ty == "handled" else "basic" ]) link(str(value), url) html.close_td() if show_stales and has_stale_objects: if row.views.stale: url = makeuri_contextless( request, row.views.stale + context_vars, filename="view.py", ) html.open_td(class_=[ td_class, "states prob" if stales != 0 else None, "show_more_mode" ]) link(str(stales), url) html.close_td() else: html.td(html.render_span("0"), class_="show_more_mode") html.close_tr() html.close_table()
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( makeactionuri(request, transactions, [(ActionHandler.stop_job_var, job_id)]), _("Stop this job"), "disable_test") if job_status.get("may_delete"): html.icon_button( makeactionuri(request, transactions, [(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(html.render_span(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_file(site, host_name, file_name): int_filename = form_file_to_int(file_name) html.header(_("Logfiles of Host %s: %s") % (host_name, file_name)) html.begin_context_buttons() html.context_button(_("Services"), services_url(site, host_name), 'services') html.context_button(_("All Logfiles of Host"), html.makeuri([('file', '')])) button_all_logfiles() html.context_button(_("Analyze patterns"), analyse_url(site, host_name, file_name), 'analyze') if html.request.var('_hidecontext', 'no') == 'yes': hide_context_label = _('Show Context') hide_context_param = 'no' hide = True else: hide_context_label = _('Hide Context') hide_context_param = 'yes' hide = False try: log_chunks = parse_file(site, host_name, int_filename, hide) except Exception as e: if config.debug: raise html.end_context_buttons() html.show_error(_("Unable to show logfile: <b>%s</b>") % e) html.footer() return if log_chunks is None: html.end_context_buttons() html.show_error(_("The logfile does not exist.")) html.footer() return elif log_chunks == []: html.end_context_buttons() html.message(_("This logfile contains no unacknowledged messages.")) html.footer() return ack_button(site, host_name, int_filename) html.context_button(hide_context_label, html.makeuri([('_hidecontext', hide_context_param)])) html.end_context_buttons() html.open_div(id_="logwatch") for log in log_chunks: html.open_div(class_=["chunk"]) html.open_table(class_=["section"]) html.open_tr() html.td(form_level(log['level']), class_=form_level(log['level'])) html.td(form_datetime(log['datetime']), class_="date") html.close_tr() html.close_table() for line in log['lines']: html.open_p(class_=line['class']) html.icon_button( analyse_url(site, host_name, file_name, line['line']), _("Analyze this line"), "analyze") html.write_text(line['line'].replace(" ", " ").replace( "\1", "<br>")) html.close_p() html.close_div() html.close_div() html.footer()
def _render_headers(self, actions_enabled, actions_visible, empty_columns): # type: (bool, bool, List[bool]) -> None if self.options["omit_headers"]: return table_id = self.id html.open_tr() first_col = True for nr, header in enumerate(self.headers): if self.options["omit_empty_columns"] and empty_columns[nr]: continue if header.help_txt: header_title = html.render_span( header.title, title=header.help_txt) # type: Union[Text, str, HTML] else: header_title = header.title css_class = ("header_%s" % header.css) if header.css else None if not self.options["sortable"] or not header.sortable: html.open_th(class_=css_class) else: reverse = 0 sort = html.request.get_ascii_input('_%s_sort' % table_id) if sort: sort_col, sort_reverse = map(int, sort.split(',', 1)) if sort_col == nr: reverse = 1 if sort_reverse == 0 else 0 action_uri = html.makeactionuri([('_%s_sort' % table_id, '%d,%d' % (nr, reverse))]) html.open_th(class_=["sort", css_class], title=_("Sort by %s") % header.title, onclick="location.href='%s'" % action_uri) # Add the table action link if first_col: first_col = False if actions_enabled: if not header_title: header_title = " " # Fixes layout problem with white triangle if actions_visible: state = '0' help_txt = _('Hide table actions') img = 'table_actions_on' else: state = '1' help_txt = _('Display table actions') img = 'table_actions_off' html.open_div(class_=["toggle_actions"]) html.icon_button(html.makeuri([('_%s_actions' % table_id, state)]), help_txt, img, cssclass='toggle_actions') html.open_span() html.write(header_title) html.close_span() html.close_div() else: html.write(header_title) else: html.write(header_title) html.close_th() html.close_tr()
def show(self): if not watolib.is_wato_slave_site(): if not config.wato_enabled: html.write_text(_("WATO is disabled.")) return False user_folders = compute_foldertree() # # Render link target selection # selected_topic, selected_target = config.user.load_file("foldertree", (_('Hosts'), 'allhosts')) # Apply some view specific filters views_to_show = [(name, view) for name, view in views.get_permitted_views().items() if (not config.visible_views or name in config.visible_views) and (not config.hidden_views or name not in config.hidden_views)] visuals_to_show = [("views", e) for e in views_to_show] visuals_to_show += [("dashboards", e) for e in dashboard.get_permitted_dashboards().items()] topics = make_topic_menu(visuals_to_show) topic_choices: Choices = [(topic.title, topic.title) for topic in topics] html.open_table() html.open_tr() html.open_td() html.dropdown("topic", topic_choices, deflt=selected_topic, onchange='cmk.sidebar.wato_tree_topic_changed(this)') html.close_td() html.close_tr() html.open_tr() html.open_td() for topic in topics: targets: Choices = [] for item in topic.items: if item.url and item.url.startswith("dashboard.py"): name = 'dashboard|' + item.name else: name = item.name targets.append((name, item.title)) if topic.title != selected_topic: default = '' style: Optional[str] = 'display:none' else: default = selected_target style = None html.dropdown("target_%s" % topic.title, targets, deflt=default, onchange='cmk.sidebar.wato_tree_target_changed(this)', style=style) html.close_td() html.close_tr() html.close_table() # Now render the whole tree if user_folders: render_tree_folder("wato-hosts", list(user_folders.values())[0], 'cmk.sidebar.wato_tree_click')
def werk_table_row(caption, content, css=None): html.open_tr() html.th(caption) html.td(content, class_=css) html.close_tr()
def write_line(left, right): html.open_tr() html.td(left, class_="left") html.td(html.render_strong(right), class_="right") html.close_tr()