def host_service_graph_popup_cmk(site, host_name, service_description): graph_render_options = { "size": (30, 10), "font_size": 6.0, # pt "resizable": False, "show_controls": False, "show_legend": False, "interaction": False, "show_time_range_previews": False, } end_time = time.time() start_time = end_time - 8 * 3600 graph_data_range = { "time_range": (start_time, end_time), } graph_identification = ("template", { "site": site, "host_name": host_name, "service_description": service_description, }) html.write_html( render_graphs_from_specification_html(graph_identification, graph_data_range, graph_render_options, render_async=False))
def _show_tree(self): td_style = None if self._wrap_texts == "wrap" else "white-space: nowrap;" tree = self._get_tree() depth = status_tree_depth(tree) leaves = self._gen_table(tree, depth, len(self._row["aggr_hosts"]) > 1) html.open_table(class_=["aggrtree", "ltr"]) odd = "odd" for code, colspan, parents in leaves: html.open_tr() leaf_td = html.render_td(code, class_=["leaf", odd], style=td_style, colspan=colspan) odd = "even" if odd == "odd" else "odd" tds = [leaf_td] for rowspan, c in parents: tds.append(html.render_td(c, class_=["node"], style=td_style, rowspan=rowspan)) if self._mirror: tds.reverse() html.write_html(HTML("").join(tds)) html.close_tr() html.close_table()
def _show_popup(self, entry: PageMenuEntry) -> None: assert isinstance(entry.item, PageMenuPopup) if entry.name is None: raise ValueError("Missing \"name\" attribute on entry \"%s\"" % entry.title) classes: List[Optional[str]] = ["page_menu_popup"] classes += entry.item.css_classes if isinstance(entry.item, PageMenuSidePopup): classes.append("side_popup") popup_id = "popup_%s" % entry.name html.open_div(id_=popup_id, class_=classes) html.open_div(class_="head") html.h3(entry.title) html.a(html.render_icon("close"), class_="close_popup", href="javascript:void(0)", onclick="cmk.page_menu.close_popup(this)") html.close_div() if (isinstance(entry.item, PageMenuSidePopup) and entry.item.content and "side_popup_content" not in entry.item.content): raise RuntimeError( "Add a div container with the class \"side_popup_content\" to the popup content") html.open_div(class_="content") html.write_html(entry.item.content) html.close_div() html.close_div() if isinstance(entry.item, PageMenuSidePopup): html.final_javascript("cmk.page_menu.side_popup_add_simplebar_scrollbar(%s);" % json.dumps(popup_id))
def _show_output_box(title, content): html.h3(title, class_="table") html.open_div(class_="log_output") html.write_html( HTML(escaping.escape_attribute(content).replace("\n", "<br>").replace(" ", " ")) ) html.close_div()
def _render_tag_tree_level(self, tree_spec, path, cwd, title, tree) -> None: if not self._is_tag_subdir( path=path, cwd=cwd) and not self._is_tag_subdir(path=cwd, cwd=path): return container: ContextManager[bool] = nullcontext(False) 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: container = foldable_container( treename="tag-tree", id_=".".join(map(str, path)), isopen=False, title=bullet + title, icon="foldable_sidebar", ) with container: 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_html( 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_html(node_title) html.br() else: self._render_tag_tree_level(tree_spec, subpath, cwd, node_title, subtree)
def _show_graph_html_content(graph_artwork, graph_data_range, graph_render_options) -> None: """Render the HTML code of a graph without its container That is a canvas object for drawing the actual graph and also legend, buttons, resize handle, etc. """ graph_render_options = artwork.add_default_render_options( graph_render_options) html.open_div( class_=[ "graph", "preview" if graph_render_options["preview"] else None ], style="font-size: %.1fpt;%s" % (graph_render_options["font_size"], _graph_padding_styles(graph_render_options)), ) if graph_render_options["show_controls"]: _show_graph_add_to_icon_for_popup(graph_artwork, graph_data_range, graph_render_options) v_axis_label = graph_artwork["vertical_axis"]["axis_label"] if v_axis_label: html.div(v_axis_label, class_="v_axis_label") # Add the floating elements if graph_render_options[ "show_graph_time"] and not graph_render_options["preview"]: html.div( graph_artwork["time_axis"]["title"] or "", css=[ "time", "inline" if graph_render_options["show_title"] == "inline" else None ], ) if graph_render_options["show_controls"] and graph_render_options[ "resizable"]: html.img(src=theme.url("images/resize_graph.png"), class_="resize") _show_html_graph_title(graph_artwork, graph_render_options) _show_graph_canvas(graph_render_options) # Note: due to "omit_zero_metrics" the graph might not have any curves if _graph_legend_enabled(graph_render_options, graph_artwork): _show_graph_legend(graph_artwork, graph_render_options) model_params_repr = graph_artwork["definition"].get("model_params_repr") model_params_display = (graph_artwork["definition"].get( "model_params", {}).get("display_model_parametrization")) if model_params_repr and model_params_display: html.open_div(align="center") html.h2(_("Forecast Parametrization")) html.write_html(model_params_repr) html.close_div() html.close_div()
def page(self): is_configured = self._varname in self._current_settings is_configured_globally = self._varname in self._global_settings default_values = watolib.ABCConfigDomain.get_all_default_globals() defvalue = default_values[self._varname] value = self._current_settings.get( self._varname, self._global_settings.get(self._varname, defvalue)) html.begin_form("value_editor", method="POST") forms.header(self._valuespec.title()) if not config.wato_hide_varnames: forms.section(_("Configuration variable:")) html.tt(self._varname) forms.section(_("Current setting")) self._valuespec.render_input("ve", value) self._valuespec.set_focus("ve") html.help(self._valuespec.help()) if is_configured_globally: self._show_global_setting() forms.section(_("Factory setting")) html.write_html(self._valuespec.value_to_text(defvalue)) forms.section(_("Current state")) if is_configured_globally: html.write_text( _("This variable is configured in <a href=\"%s\">global settings</a>." ) % ("wato.py?mode=edit_configvar&varname=%s" % self._varname)) elif not is_configured: html.write_text(_("This variable is at factory settings.")) else: curvalue = self._current_settings[self._varname] if is_configured_globally and curvalue == self._global_settings[ self._varname]: html.write_text( _("Site setting and global setting are identical.")) elif curvalue == defvalue: html.write_text( _("Your setting and factory settings are identical.")) else: html.write(self._valuespec.value_to_text(curvalue)) forms.end() html.button("save", _("Save")) if self._config_variable.allow_reset() and is_configured: curvalue = self._current_settings[self._varname] html.button( "_reset", _("Remove explicit setting") if curvalue == defvalue else _("Reset to default")) html.hidden_fields() html.end_form()
def robotmk_report_page() -> cmk.gui.pages.PageResult: """Renders the content of the RobotMK html log file""" site_id, host_name, service_description = _get_mandatory_request_vars() report_type: str = request.get_str_input_mandatory("report_type") content = _get_html_from_livestatus(report_type, site_id, host_name, service_description) html_content = _get_cleaned_html_content(content[0].decode("utf-8")) html.write_html(html_content)
def _show_row_cells(self, table, name, group): super(ModeContactgroups, self)._show_row_cells(table, name, group) table.cell(_("Members")) html.write_html( HTML(", ").join([ html.render_a(alias, href=watolib.folder_preserving_link([("mode", "edit_user"), ("edit", userid)])) for userid, alias in self._members.get(name, []) ]))
def _show_output_box(title: str, content: bytes) -> None: html.h3(title, class_="table") html.open_div(class_="log_output") html.write_html( HTML( escaping.escape_attribute(content.decode(errors="surrogateescape")) .replace("\n", "<br>") .replace(" ", " ") ) ) html.close_div()
def _show_row_cells(self, table: Table, name: GroupName, group: GroupSpec) -> None: super()._show_row_cells(table, name, group) table.cell(_("Members")) html.write_html( HTML(", ").join([ html.render_a( alias, href=watolib.folder_preserving_link([("mode", "edit_user"), ("edit", userid)]), ) for userid, alias in self._members.get(name, []) ]))
def _show_subtree(self, tree, path, show_host): if self._is_leaf(tree): self._show_leaf(tree, show_host) return html.open_span(class_="title") is_empty = len(tree[3]) == 0 if is_empty: mc = None else: mc = self._get_mousecode(path) css_class = "open" if self._is_open(path) else "closed" with self._show_node(tree, show_host, mousecode=mc, img_class=css_class): if tree[2].get("icon"): html.write_html(html.render_icon(tree[2]["icon"])) html.write_text(" ") if tree[2].get("docu_url"): html.icon_button( tree[2]["docu_url"], _("Context information about this rule"), "url", target="_blank", ) html.write_text(" ") html.write_text(tree[2]["title"]) if not is_empty: html.open_ul( id_="%d:%s" % (self._expansion_level or 0, self._path_id(path)), class_=["subtree", css_class], ) if not self._omit_content(path): for node in tree[3]: if not node[2].get("hidden"): new_path = path + [node[2]["title"]] html.open_li() self._show_subtree(node, new_path, show_host) html.close_li() html.close_ul() html.close_span()
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 display(self, value: FilterHTTPVariables) -> None: actual_value = value.get(self._filter_range_config.column, self._filter_range_config.default) html.add_form_var(self._filter_range_config.column) html.write_html( HTML(f""" <input id={self._filter_range_config.column} name={self._filter_range_config.column} style="pointer-events: all; width: 80%%;" oninput="{self._update_label()}" type="range" step="{self._filter_range_config.step}" min="{self._filter_range_config.min}" max="{self._filter_range_config.max}" value="{actual_value}"> <label id={self._filter_range_config.column}_label>{actual_value}</> """))
def page(self): if not config.user.may("wato.automation"): raise MKAuthException(_("This account has no permission for automation.")) html.set_output_format("python") if not html.request.has_var("_version"): # Be compatible to calls from sites using versions before 1.5.0p10. # Deprecate with 1.7 by throwing an exception in this situation. response = _get_login_secret(create_on_demand=True) else: response = { "version": cmk_version.__version__, "edition_short": cmk_version.edition_short(), "login_secret": _get_login_secret(create_on_demand=True), } html.write_html(repr(response))
def _change_table(self): with table_element( "changes", title=_("Pending changes (%s)") % self._get_amount_changes(), sortable=False, searchable=False, css="changes", limit=None, empty_text=_("Currently there are no changes to activate."), foldable=Foldable.FOLDABLE_STATELESS, ) as table: for _change_id, change in reversed(self._changes): css = [] if self._is_foreign(change): css.append("foreign") if not user.may("wato.activateforeign"): css.append("not_permitted") table.row(css=" ".join(css)) table.cell("", css="buttons") rendered = render_object_ref_as_icon(change["object"]) if rendered: html.write_html(rendered) table.cell(_("Time"), render.date_and_time(change["time"]), css="narrow nobr") table.cell(_("User"), css="narrow nobr") html.write_text(change["user_id"] if change["user_id"] else "") if self._is_foreign(change): html.icon("foreign_changes", _("This change has been made by another user")) # Text is already escaped (see ActivateChangesWriter._add_change_to_site). We have # to handle this in a special way because of the SiteChanges file format. Would be # cleaner to transport the text type (like AuditLogStore is doing it). table.cell(_("Change"), HTML(change["text"])) table.cell(_("Affected sites"), css="affected_sites") if self._affects_all_sites(change): html.write_text("<i>%s</i>" % _("All sites")) else: html.write_text(", ".join(sorted( change["affected_sites"])))
def _show_labels(self, labels, object_type, label_sources): forms.section(_("Effective labels")) html.open_table(class_="setting") html.open_tr() html.open_td(class_="reason") html.i(_("Explicit, ruleset, discovered")) html.close_td() html.open_td(class_=["settingvalue", "used"]) html.write_html( cmk.gui.view_utils.render_labels( labels, object_type, with_links=False, label_sources=label_sources ) ) html.close_td() html.close_tr() html.close_table()
def ajax_render_tree(): aggr_group = request.get_str_input("group") aggr_title = request.get_str_input("title") omit_root = bool(request.var("omit_root")) only_problems = bool(request.var("only_problems")) rows = [] bi_manager = BIManager() bi_manager.status_fetcher.set_assumed_states(user.bi_assumptions) aggregation_id = request.get_str_input_mandatory("aggregation_id") bi_aggregation_filter = BIAggregationFilter( [], [], [aggregation_id], [aggr_title] if aggr_title is not None else [], [aggr_group] if aggr_group is not None else [], [], ) rows = bi_manager.computer.compute_legacy_result_for_filter( bi_aggregation_filter) # TODO: Cleanup the renderer to use a class registry for lookup renderer_class_name = request.var("renderer") if renderer_class_name == "FoldableTreeRendererTree": renderer_cls: Type[ABCFoldableTreeRenderer] = FoldableTreeRendererTree elif renderer_class_name == "FoldableTreeRendererBoxes": renderer_cls = FoldableTreeRendererBoxes elif renderer_class_name == "FoldableTreeRendererBottomUp": renderer_cls = FoldableTreeRendererBottomUp elif renderer_class_name == "FoldableTreeRendererTopDown": renderer_cls = FoldableTreeRendererTopDown else: raise NotImplementedError() renderer = renderer_cls( rows[0], omit_root=omit_root, expansion_level=user.bi_expansion_level, only_problems=only_problems, lazy=False, ) html.write_html(renderer.render())
def show(self): html.open_table(class_="dashlet_overview") html.open_tr() html.open_td(valign="top") html.open_a(href="https://checkmk.com/") html.img(html.theme_url("images/check_mk.trans.120.png"), style="margin-right: 30px;") html.close_a() html.close_td() html.open_td() html.h2("CheckMK") html.write_html( 'Welcome to CheckMK. If you want to learn more about CheckMK, please visit ' 'our <a href="https://checkmk.com/" target="_blank">user manual</a>.' ) html.close_td() html.close_tr() html.close_table()
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 show(self): html.open_table(class_="dashlet_overview") html.open_tr() html.open_td(valign="top") html.open_a(href="https://mathias-kettner.com/check_mk.html") html.img(html.theme_url("images/check_mk.trans.120.png"), style="margin-right: 30px;") html.close_a() html.close_td() html.open_td() html.h2("Check_MK Multisite") html.write_html( 'Welcome to Check_MK Multisite. If you want to learn more about Multisite, please visit ' 'our <a href="https://mathias-kettner.com/checkmk_multisite.html">online documentation</a>. ' 'Multisite is part of <a href="https://mathias-kettner.com/check_mk.html">Check_MK</a> - an Open Source ' 'project by <a href="https://mathias-kettner.com">Mathias Kettner</a>.' ) html.close_td() html.close_tr() html.close_table()
def render_bi_availability(title, aggr_rows): # type: (Text, 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") if av_mode == "timeline": title = _("Timeline of") + " " + title else: title = _("Availability of") + " " + title if html.output_format != "csv_export": html.body_start(title) html.top_heading(title) 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 = [] # type: 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 _show_global_setting(self): forms.section(_("Global setting")) html.write_html(HTML(self._valuespec.value_to_text(self._global_settings[self._varname])))
def link(text: Union[str, HTML], url: str, target: str = "main", onclick: Optional[str] = None) -> None: html.write_html(render_link(text, url, target=target, onclick=onclick))
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"): EmailAddressUnicode().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.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 if self._is_new_user or config.user_may(self._user_id, 'general.edit_profile') and config.user_may( self._user_id, '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")) entries = self._roles.items() entries.sort(key=lambda x: (x[1]["alias"], x[0])) is_member_of_at_least_one = False for role_id, role in entries: 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")) choices = [(id_, "%s" % (tp["alias"])) for (id_, tp) in self._timeperiods.items()] html.dropdown("notification_period", choices, deflt=self._user.get("notification_period"), 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() html.button("save", _("Save")) if self._is_new_user: html.set_focus("user_id") else: html.set_focus("alias") html.hidden_fields() html.end_form()
def _show_user_list(self): visible_custom_attrs = [ (name, attr) for name, attr in userdb.get_user_attributes() if attr.show_in_table() ] users = userdb.load_users() entries = users.items() html.begin_form("bulk_delete_form", method="POST") roles = userdb.load_roles() timeperiods = watolib.timeperiods.load_timeperiods() contact_groups = load_contact_group_information() with table_element("users", None, empty_text=_("No users are defined yet.")) as table: online_threshold = time.time() - config.user_online_maxage for uid, user in sorted(entries, key=lambda x: x[1].get("alias", x[0]).lower()): table.row() # Checkboxes table.cell(html.render_input("_toggle_group", type_="button", class_="checkgroup", onclick="cmk.selection.toggle_all_rows();", value='X'), sortable=False, css="checkbox") if uid != config.user.id: html.checkbox("_c_user_%s" % base64.b64encode(uid.encode("utf-8"))) user_connection_id = userdb.cleanup_connection_id(user.get('connector')) connection = userdb.get_connection(user_connection_id) # Buttons table.cell(_("Actions"), css="buttons") if connection: # only show edit buttons when the connector is available and enabled edit_url = watolib.folder_preserving_link([("mode", "edit_user"), ("edit", uid)]) html.icon_button(edit_url, _("Properties"), "edit") clone_url = watolib.folder_preserving_link([("mode", "edit_user"), ("clone", uid)]) html.icon_button(clone_url, _("Create a copy of this user"), "clone") delete_url = make_action_link([("mode", "users"), ("_delete", uid)]) html.icon_button(delete_url, _("Delete"), "delete") notifications_url = watolib.folder_preserving_link([("mode", "user_notifications"), ("user", uid)]) if watolib.load_configuration_settings().get("enable_rulebased_notifications"): html.icon_button(notifications_url, _("Custom notification table of this user"), "notifications") # ID table.cell(_("ID"), uid) # Online/Offline if config.save_user_access_times: last_seen = user.get('last_seen', 0) if last_seen >= online_threshold: title = _('Online') img_txt = 'online' elif last_seen != 0: title = _('Offline') img_txt = 'offline' elif last_seen == 0: title = _('Never logged in') img_txt = 'inactive' title += ' (%s %s)' % (render.date(last_seen), render.time_of_day(last_seen)) table.cell(_("Act.")) html.icon(title, img_txt) table.cell(_("Last seen")) if last_seen != 0: html.write_text("%s %s" % (render.date(last_seen), render.time_of_day(last_seen))) else: html.write_text(_("Never logged in")) if cmk.is_managed_edition(): table.cell(_("Customer"), managed.get_customer_name(user)) # Connection if connection: table.cell(_("Connection"), '%s (%s)' % (connection.short_title(), user_connection_id)) locked_attributes = userdb.locked_attributes(user_connection_id) else: table.cell(_("Connection"), "%s (%s) (%s)" % (_("UNKNOWN"), user_connection_id, _("disabled")), css="error") locked_attributes = [] # Authentication if "automation_secret" in user: auth_method = _("Automation") elif user.get("password") or 'password' in locked_attributes: auth_method = _("Password") else: auth_method = "<i>%s</i>" % _("none") table.cell(_("Authentication"), auth_method) table.cell(_("State")) if user.get("locked", False): html.icon(_('The login is currently locked'), 'user_locked') if "disable_notifications" in user and isinstance(user["disable_notifications"], bool): disable_notifications_opts = {"disable": user["disable_notifications"]} else: disable_notifications_opts = user.get("disable_notifications", {}) if disable_notifications_opts.get("disable", False): html.icon(_('Notifications are disabled'), 'notif_disabled') # Full name / Alias table.text_cell(_("Alias"), user.get("alias", "")) # Email table.text_cell(_("Email"), user.get("email", "")) # Roles table.cell(_("Roles")) if user.get("roles", []): role_links = [(watolib.folder_preserving_link([("mode", "edit_role"), ("edit", role)]), roles[role].get("alias")) for role in user["roles"]] html.write_html( HTML(", ").join( html.render_a(alias, href=link) for (link, alias) in role_links)) # contact groups table.cell(_("Contact groups")) cgs = user.get("contactgroups", []) if cgs: cg_aliases = [ contact_groups[c]['alias'] if c in contact_groups else c for c in cgs ] cg_urls = [ watolib.folder_preserving_link([("mode", "edit_contact_group"), ("edit", c)]) for c in cgs ] html.write_html( HTML(", ").join( html.render_a(content, href=url) for (content, url) in zip(cg_aliases, cg_urls))) else: html.i(_("none")) #table.cell(_("Sites")) #html.write(vs_authorized_sites().value_to_text(user.get("authorized_sites", # vs_authorized_sites().default_value()))) # notifications if not watolib.load_configuration_settings().get("enable_rulebased_notifications"): table.cell(_("Notifications")) if not cgs: html.i(_("not a contact")) elif not user.get("notifications_enabled", True): html.write_text(_("disabled")) elif user.get("host_notification_options", "") == "" and \ user.get("service_notification_options", "") == "": html.write_text(_("all events disabled")) else: tp = user.get("notification_period", "24X7") if tp not in timeperiods: tp = tp + _(" (invalid)") elif tp not in watolib.timeperiods.builtin_timeperiods(): url = watolib.folder_preserving_link([("mode", "edit_timeperiod"), ("edit", tp)]) tp = html.render_a(timeperiods[tp].get("alias", tp), href=url) else: tp = timeperiods[tp].get("alias", tp) html.write(tp) # the visible custom attributes for name, attr in visible_custom_attrs: vs = attr.valuespec() table.cell(html.attrencode(_u(vs.title()))) html.write(vs.value_to_text(user.get(name, vs.default_value()))) html.button("_bulk_delete_users", _("Bulk Delete"), "submit", style="margin-top:10px") html.hidden_fields() html.end_form() if not load_contact_group_information(): url = "wato.py?mode=contact_groups" html.open_div(class_="info") html.write( _("Note: you haven't defined any contact groups yet. If you <a href='%s'>" "create some contact groups</a> you can assign users to them und thus " "make them monitoring contacts. Only monitoring contacts can receive " "notifications.") % url) html.write(" you can assign users to them und thus " "make them monitoring contacts. Only monitoring contacts can receive " "notifications.") html.close_div()
def _show_page_content(self, content: Optional["HTML"]): html.open_div(id_="content_area") if content is not None: html.write_html(content) html.close_div()
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 host_service_graph_dashlet_cmk( graph_identification: GraphIdentifier, custom_graph_render_options, ): graph_render_options = {**default_dashlet_graph_render_options} graph_render_options = artwork.add_default_render_options( graph_render_options) graph_render_options.update(custom_graph_render_options) width_var = request.get_float_input_mandatory("width", 0.0) width = int((width_var / html_size_per_ex)) height_var = request.get_float_input_mandatory("height", 0.0) height = int((height_var / html_size_per_ex)) bounds = _graph_margin_ex(graph_render_options) height -= _graph_title_height_ex(graph_render_options) height -= bounds.top + bounds.bottom width -= bounds.left + bounds.right graph_render_options["size"] = (width, height) # The timerange is specified in PNP like manner. range_secs = { "0": 4 * 3600, "1": 25 * 3600, "2": 7 * 86400, "3": 31 * 86400, "4": 366 * 86400, } secs_var = request.var("timerange") if secs_var not in range_secs: secs = 4 * 3600 else: secs = range_secs[secs_var] end_time = time.time() start_time = end_time - secs graph_data_range = { "time_range": (start_time, end_time), } graph_data_range["step"] = estimate_graph_step_for_html( graph_data_range["time_range"], graph_render_options) graph_recipes = resolve_graph_recipe_with_error_handling( graph_identification, destination=GraphDestinations.dashlet, ) if not isinstance(graph_recipes, list): return graph_recipes # This is to html.write the exception if graph_recipes: graph_recipe = graph_recipes[0] else: raise MKGeneralException(_("Failed to calculate a graph recipe.")) # When the legend is enabled, we need to reduce the height by the height of the legend to # make the graph fit into the dashlet area. if graph_render_options["show_legend"]: # TODO FIXME: This graph artwork is calulated twice. Once here and once in render_graphs_from_specification_html() graph_artwork = artwork.compute_graph_artwork(graph_recipe, graph_data_range, graph_render_options) if graph_artwork["curves"]: legend_height = graph_legend_height_ex(graph_render_options, graph_artwork) graph_render_options["size"] = (width, height - legend_height) html_code = render_graphs_from_definitions([graph_recipe], graph_data_range, graph_render_options, render_async=False) html.write_html(html_code)
def show(self): # type: () -> None filename = Path(cmk.utils.paths.omd_root).joinpath( 'var/dokuwiki/data/pages/sidebar.txt') html.open_form(id_="wiki_search", onsubmit="cmk.sidebar.wiki_search('%s');" % config.omd_site()) html.input(id_="wiki_search_field", type_="text", name="wikisearch") html.icon_button("#", _("Search"), "wikisearch", onclick="cmk.sidebar.wiki_search('%s');" % config.omd_site()) html.close_form() html.div('', id_="wiki_side_clear") start_ul = True ul_started = False try: title = None for line in filename.open(encoding="utf-8").readlines(): line = line.strip() if line == "": if ul_started: html.end_foldable_container() start_ul = True ul_started = False elif line.endswith(":"): title = line[:-1] elif line == "----": pass # html.br() elif line.startswith("*"): if start_ul: if title: html.begin_foldable_container("wikisnapin", title, True, title, indent=True) else: html.open_ul() start_ul = False ul_started = True erg = re.findall(r'\[\[(.*)\]\]', line) if len(erg) == 0: continue erg = erg[0].split('|') if len(erg) > 1: link = erg[0] name = erg[1] else: link = erg[0] name = erg[0] if link.startswith("http://") or link.startswith( "https://"): simplelink(name, link, "_blank") else: erg = name.split(':') if len(erg) > 0: name = erg[-1] else: name = erg[0] bulletlink( name, "/%s/wiki/doku.php?id=%s" % (config.omd_site(), link)) else: html.write_text(line) if ul_started: html.close_ul() except IOError: html.write_html( html.render_p( html.render_text( "To get a navigation menu, you have to create a ") + html.render_a("sidebar", href="/%s/wiki/doku.php?id=%s" % (config.omd_site(), _("sidebar")), target="main") + # html.render_text(" in your wiki first.")))