def _validate_general_host_attributes(host_attributes, new): # inventory_failed and site are no "real" host_attributes (TODO: Clean this up!) all_host_attribute_names = list( host_attribute_registry.keys()) + ["inventory_failed", "site"] for name, value in host_attributes.items(): if name not in all_host_attribute_names: raise MKUserError( None, _("Unknown attribute: %s") % escaping.escape_attribute(name)) # For real host attributes validate the values try: attr = watolib.host_attribute(name) except KeyError: attr = None if attr is not None: if attr.needs_validation("host", new): attr.validate_input(value, "") # The site attribute gets an extra check if name == "site" and value not in config.allsites().keys(): raise MKUserError( None, _("Unknown site %s") % escaping.escape_attribute(value))
def _render_warnings(self, configuration_warnings): html_code = "<div class=warning>" html_code += "<b>%s</b>" % _("Warnings:") html_code += "<ul>" for domain, warnings in sorted(configuration_warnings.items()): for warning in warnings: html_code += "<li>%s: %s</li>" % \ (escaping.escape_attribute(domain), escaping.escape_attribute(warning)) html_code += "</ul>" html_code += "</div>" return html_code
def _show_row_cells(self, table, name, group): table.cell(_("Actions"), css="buttons") edit_url = watolib.folder_preserving_link([("mode", "edit_%s_group" % self.type_name), ("edit", name)]) delete_url = html.makeactionuri([("_delete", name)]) clone_url = watolib.folder_preserving_link([("mode", "edit_%s_group" % self.type_name), ("clone", name)]) html.icon_button(edit_url, _("Properties"), "edit") html.icon_button(clone_url, _("Create a copy of this group"), "clone") html.icon_button(delete_url, _("Delete"), "delete") table.cell(_("Name"), escaping.escape_attribute(name)) table.cell(_("Alias"), escaping.escape_attribute(group['alias']))
def _validate_host_tags(host_tags): for tag_group_id, tag_id in host_tags.items(): for tag_group in config.tags.tag_groups: if tag_group.id == tag_group_id: for grouped_tag in tag_group.tags: if grouped_tag.id == tag_id: break else: raise MKUserError(None, _("Unknown tag %s") % escaping.escape_attribute(tag_id)) break else: raise MKUserError(None, _("Unknown tag group %s") % escaping.escape_attribute(tag_group_id))
def page_index(): html.write( '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' '<html><head>\n') html.default_html_headers() html.write("""<title>%s</title> </head> <frameset cols="280,*" frameborder="0" framespacing="0" border="0"> <frame src="side.py" name="side" noresize scrolling="no"> <frame src="%s" name="main" noresize> </frameset> </html> """ % (escaping.escape_attribute( config.get_page_heading()), escaping.escape_attribute(_get_start_url())))
def _show_crash_report(self, info): html.h2(_("Crash Report")) html.open_table(class_=["data", "crash_report"]) _crash_row(_("Exception"), "%s (%s)" % (info["exc_type"], info["exc_value"]), odd=True, pre=True) _crash_row(_("Traceback"), self._format_traceback(info["exc_traceback"]), odd=False, pre=True) _crash_row(_("Local Variables"), format_local_vars(info["local_vars"]) if "local_vars" in info else "", odd=True, pre=True) _crash_row(_("Crash Type"), info["crash_type"], odd=False, legend=True) _crash_row(_("Time"), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(info["time"])), odd=True) _crash_row(_("Operating System"), info["os"], False) _crash_row(_("Checkmk Version"), info["version"], True) _crash_row(_("Edition"), info.get("edition", ""), False) _crash_row(_("Core"), info.get("core", ""), True) _crash_row(_("Python Version"), info.get("python_version", _("Unknown")), False) joined_paths = "<br>".join( [escaping.escape_attribute(p) for p in info.get("python_paths", [_("Unknown")])]) _crash_row(_("Python Module Paths"), joined_paths, odd=False) html.close_table()
def _get_api_call(): action = global_request.var('action') for cls in api_call_collection_registry.values(): api_call = cls().get_api_calls().get(action) if api_call: return api_call raise MKUserError(None, "Unknown API action %s" % escaping.escape_attribute(action))
def _show_output_box(title, content): html.h3(title) html.open_div(class_="log_output") html.write( escaping.escape_attribute(content).replace("\n", "<br>").replace( ' ', ' ')) html.close_div()
def run(self): # type: () -> Iterator[ACResult] self._executed = True try: # Do not merge results that have been gathered on one site for different sites results = list(self.execute()) num_sites = len(set(r.site_id for r in results)) if num_sites > 1: for result in results: result.from_test(self) yield result return # Merge multiple results produced for a single site total_result = ACResult.merge(*list(self.execute())) total_result.from_test(self) yield total_result except Exception: logger.exception("error executing configuration test %s", self.__class__.__name__) result = ACResultCRIT( "<pre>%s</pre>" % _("Failed to execute the test %s: %s") % (escaping.escape_attribute( self.__class__.__name__), traceback.format_exc())) result.from_test(self) yield result
def page_login() -> None: title = _("Check_MK Mobile") mobile_html_head(title) jqm_page_header(title, id_="login") html.div(_("Welcome to Check_MK Mobile."), id_="loginhead") html.begin_form("login", method='POST', add_transid=False) # Keep information about original target URL default_origtarget = "index.py" if html.myfile in ["login", "logout" ] else html.makeuri([]) origtarget = html.get_url_input("_origtarget", default_origtarget) html.hidden_field('_origtarget', escaping.escape_attribute(origtarget)) html.text_input("_username", label=_("Username:"******"username") html.password_input("_password", size=None, label=_("Password:"******"current-password") html.br() html.button("_login", _('Login')) html.set_focus("_username") html.end_form() html.open_div(id_="loginfoot") html.img("themes/classic/images/logo_cmk_small.png", class_="logomk") html.div(HTML( _("© <a target=\"_blank\" href=\"https://checkmk.com\">tribe29 GmbH</a>" )), class_="copyright") html.close_div() # close content-div html.close_div() html.close_div() # close page-div mobile_html_foot()
def _from_vars(self): # type: () -> None config.user.need_permission("wato.download_agent_output") host_name = html.request.var("host") if not host_name: raise MKGeneralException(_("The host is missing.")) ty = html.request.var("type") if ty not in ["walk", "agent"]: raise MKGeneralException(_("Invalid type specified.")) self._back_url = html.get_url_input("back_url", deflt="") or None watolib.init_wato_datastructures(with_wato_lock=True) host = watolib.Folder.current().host(host_name) if not host: raise MKGeneralException( _("Host is not managed by WATO. " "Click <a href=\"%s\">here</a> to go back.") % escape_attribute(self._back_url)) host.need_permission("read") self._request = FetchAgentOutputRequest(host=host, agent_type=ty)
def compute_output_message(effective_state, rule): output = [] if effective_state["output"]: output.append(effective_state["output"]) str_state = str(effective_state["state"]) if str_state in rule.get("state_messages", {}): output.append(escaping.escape_attribute(rule["state_messages"][str_state])) return ", ".join(output)
def time_series_math(operator_id, operands_evaluated): operators = time_series_operators() if operator_id not in operators: raise MKGeneralException( _("Undefined operator '%s' in graph expression") % escaping.escape_attribute(operator_id)) _op_title, op_func = operators[operator_id] return [op_func_wrapper(op_func, tsp) for tsp in zip(*operands_evaluated)]
def action(self, cmdtag, spec, row, row_index, num_rows): for s in [0, 1, 2, 3]: statename = html.request.var("_fake_%d" % s) if statename: pluginoutput = html.request.get_unicode_input_mandatory("_fake_output").strip() if not pluginoutput: pluginoutput = _("Manually set to %s by %s") % ( escaping.escape_attribute(statename), config.user.id) perfdata = html.request.var("_fake_perfdata") if perfdata: pluginoutput += "|" + perfdata if cmdtag == "SVC": cmdtag = "SERVICE" command = "PROCESS_%s_CHECK_RESULT;%s;%s;%s" % (cmdtag, spec, s, livestatus.lqencode(pluginoutput)) title = _("<b>manually set check results to %s</b> for" ) % escaping.escape_attribute(statename) return command, title
def log_audit(linkinfo, action, message, user_id=None): if config.wato_use_git: if isinstance(message, HTML): message = escaping.strip_tags(message.value) cmk.gui.watolib.git.add_message(message) # Using escape_attribute here is against our regular rule to do the escaping # at the last possible time: When rendering. But this here is the last # place where we can distinguish between HTML() encapsulated (already) # escaped / allowed HTML and strings to be escaped. message = escaping.escape_attribute(message).strip() log_entry(linkinfo, action, message, user_id)
def time_series_math(operator_id: Literal["+", "*", "-", "/", "MAX", "MIN", "AVERAGE", "MERGE"], operands_evaluated: List[TimeSeries]) -> TimeSeries: operators = time_series_operators() if operator_id not in operators: raise MKGeneralException( _("Undefined operator '%s' in graph expression") % escaping.escape_attribute(operator_id)) _op_title, op_func = operators[operator_id] twindow = operands_evaluated[0].twindow return TimeSeries([op_func_wrapper(op_func, tsp) for tsp in zip(*operands_evaluated)], twindow)
def format_plugin_output(output: CellContent, row: 'Optional[Row]' = None, shall_escape: bool = True) -> str: assert not isinstance(output, dict) ok_marker = '<b class="stmark state0">OK</b>' warn_marker = '<b class="stmark state1">WARN</b>' crit_marker = '<b class="stmark state2">CRIT</b>' unknown_marker = '<b class="stmark state3">UNKN</b>' # In case we have a host or service row use the optional custom attribute # ESCAPE_PLUGIN_OUTPUT (set by host / service ruleset) to override the global # setting. if row: custom_vars = row.get("service_custom_variables", row.get("host_custom_variables", {})) if "ESCAPE_PLUGIN_OUTPUT" in custom_vars: shall_escape = custom_vars["ESCAPE_PLUGIN_OUTPUT"] == "1" if shall_escape: output = escaping.escape_attribute(output) else: output = "%s" % output output = output.replace("(!)", warn_marker) \ .replace("(!!)", crit_marker) \ .replace("(?)", unknown_marker) \ .replace("(.)", ok_marker) if row and "[running on" in output: a = output.index("[running on") e = output.index("]", a) hosts = output[a + 12:e].replace(" ", "").split(",") h = get_host_list_links(row["site"], hosts) output = output[:a] + "running on " + ", ".join(h) + output[e + 1:] prevent_url_icons = (row.get("service_check_command", "") == "check_mk-check_mk_agent_update" if row is not None else False) if shall_escape and not prevent_url_icons: http_url = r"(http[s]?://[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+)" # (?:<A HREF="), (?: target="_blank">)? and endswith(" </A>") is a special # handling for the HTML code produced by check_http when "clickable URL" option is active. output = re.sub( "(?:<A HREF=")?" + http_url + "(?: target="_blank">)?", lambda p: str( html.render_icon_button( p.group(1).replace('"', ''), p.group(1).replace('"', ''), "link")), output) if output.endswith(" </A>"): output = output[:-11] return output
def check_aggregation_title_uniqueness(aggregations): known_titles: Set[Any] = set() for attrs in aggregations.values(): title = attrs["title"] if title in known_titles: raise MKConfigError( _("Duplicate BI aggregation with the title \"<b>%s</b>\". " "Please check your BI configuration and make sure that within each group no aggregation has " "the same title as any other. Note: you can use arguments in the top level " "aggregation rule, like <tt>Host $HOST$</tt>.") % (escaping.escape_attribute(title))) known_titles.add(title)
def render_messages(self): for msg in notify.get_gui_messages(): if 'gui_hint' in msg['methods']: html.open_div(id_="message-%s" % msg['id'], class_=["popup_msg"]) html.a("x", href="javascript:void(0)", class_=["close"], onclick="cmk.sidebar.message_close(\'%s\')" % msg['id']) html.write_text(msg['text'].replace('\n', '<br>\n')) html.close_div() if 'gui_popup' in msg['methods']: html.javascript( 'alert(\'%s\'); cmk.sidebar.mark_message_read("%s")' % (escaping.escape_attribute(msg['text']).replace('\n', '\\n'), msg['id']))
def _activate_changes(self, request): mode = request.get("mode", "dirty") if request.get("allow_foreign_changes"): allow_foreign_changes = bool( int(request.get("allow_foreign_changes"))) else: allow_foreign_changes = False sites = request.get("sites") changes = watolib.ActivateChanges() changes.load() if changes.has_foreign_changes(): if not config.user.may("wato.activateforeign"): raise MKAuthException( _("You are not allowed to activate changes of other users." )) if not allow_foreign_changes: raise MKAuthException( _("There are changes from other users and foreign changes are not allowed in this API call." )) if mode == "specific": for site in sites: if site not in config.allsites().keys(): raise MKUserError( None, _("Unknown site %s") % escaping.escape_attribute(site)) manager = watolib.ActivateChangesManager() manager.load() if not manager.has_changes(): raise MKUserError(None, _("Currently there are no changes to activate.")) if not sites: sites = manager.dirty_and_active_activation_sites() comment = request.get("comment", "").strip() if comment == "": comment = None manager.start(sites, comment=comment, activate_foreign=allow_foreign_changes) manager.wait_for_completion() return manager.get_state()
def check_title_uniqueness(forest): # Legacy, will be removed any decade from now # One aggregation cannot be in mutliple groups. known_titles: Set[Any] = set() for aggrs in forest.values(): for aggr in aggrs: title = aggr["title"] if title in known_titles: raise MKConfigError( _("Duplicate BI aggregation with the title \"<b>%s</b>\". " "Please check your BI configuration and make sure that within each group no aggregation has " "the same title as any other. Note: you can use arguments in the top level " "aggregation rule, like <tt>Host $HOST$</tt>.") % (escaping.escape_attribute(title))) known_titles.add(title)
def _ajax_switch_masterstate(self): # type: () -> None html.set_output_format("text") if not config.user.may("sidesnap.master_control"): return if not html.check_transaction(): return site = html.request.get_ascii_input_mandatory("site") column = html.request.get_ascii_input_mandatory("switch") state = html.request.get_integer_input_mandatory("state") commands = { ("enable_notifications", 1): "ENABLE_NOTIFICATIONS", ("enable_notifications", 0): "DISABLE_NOTIFICATIONS", ("execute_service_checks", 1): "START_EXECUTING_SVC_CHECKS", ("execute_service_checks", 0): "STOP_EXECUTING_SVC_CHECKS", ("execute_host_checks", 1): "START_EXECUTING_HOST_CHECKS", ("execute_host_checks", 0): "STOP_EXECUTING_HOST_CHECKS", ("enable_flap_detection", 1): "ENABLE_FLAP_DETECTION", ("enable_flap_detection", 0): "DISABLE_FLAP_DETECTION", ("process_performance_data", 1): "ENABLE_PERFORMANCE_DATA", ("process_performance_data", 0): "DISABLE_PERFORMANCE_DATA", ("enable_event_handlers", 1): "ENABLE_EVENT_HANDLERS", ("enable_event_handlers", 0): "DISABLE_EVENT_HANDLERS", } command = commands.get((column, state)) if not command: html.write( _("Command %s/%d not found") % (escaping.escape_attribute(column), state)) return sites.live().command("[%d] %s" % (int(time.time()), command), site) sites.live().set_only_sites([site]) sites.live().query( "GET status\nWaitTrigger: program\nWaitTimeout: 10000\nWaitCondition: %s = %d\nColumns: %s\n" % (column, state, column)) sites.live().set_only_sites() self.show()
def page_view() -> None: view_name = html.request.var("view_name") if not view_name: return page_index() view_spec = views.get_permitted_views().get(view_name) if not view_spec: raise MKUserError("view_name", "No view defined with the name '%s'." % view_name) datasource = data_source_registry[view_spec["datasource"]]() context = visuals.get_merged_context( visuals.get_context_from_uri_vars( datasource.infos, single_infos=view_spec["single_infos"]), view_spec["context"], ) view = views.View(view_name, view_spec, context) view.row_limit = views.get_limit() view.only_sites = visuals.get_only_sites_from_context(context) view.user_sorters = views.get_user_sorters() view.want_checkboxes = views.get_want_checkboxes() title = views.view_title(view.spec, view.context) mobile_html_head(title) # Need to be loaded before processing the painter_options below. # TODO: Make this dependency explicit display_options.load_from_html(html) painter_options = PainterOptions.get_instance() painter_options.load(view_name) try: views.process_view(MobileViewRenderer(view)) except Exception as e: logger.exception("error showing mobile view") if config.debug: raise html.write("ERROR showing view: %s" % escaping.escape_attribute(str(e))) mobile_html_foot()
def _add_change_to_site(self, site_id, change_id, action_name, text, obj, add_user, need_sync, need_restart, domains): # Individual changes may override the domain restart default value if need_restart is None: need_restart = any([d.needs_activation for d in domains]) if need_sync is None: need_sync = any([d.needs_sync for d in domains]) def serialize_object(obj): if obj is None: return None return obj.__class__.__name__, obj.ident() # Using attrencode here is against our regular rule to do the escaping # at the last possible time: When rendering. But this here is the last # place where we can distinguish between HTML() encapsulated (already) # escaped / allowed HTML and strings to be escaped. text = escaping.escape_attribute(text) SiteChanges(site_id).save_change({ "id": change_id, "action_name": action_name, "text": "%s" % text, "object": serialize_object(obj), "user_id": config.user.id if add_user else None, "domains": [d.ident for d in domains], "time": time.time(), "need_sync": need_sync, "need_restart": need_restart, })
def _push_snapshot_to_site(self): """Calls a remote automation call push-snapshot which is handled by AutomationPushSnapshot()""" site = config.site(self._site_id) url = html.makeuri_contextless( [ ("command", "push-snapshot"), ("secret", site["secret"]), ("siteid", site["id"]), ("debug", config.debug and "1" or ""), ], filename=site["multisiteurl"] + "automation.py", ) response_text = self._upload_file(url, site.get('insecure', False)) try: return ast.literal_eval(response_text) except SyntaxError: raise cmk.gui.watolib.automations.MKAutomationException( _("Garbled automation response: <pre>%s</pre>") % (escaping.escape_attribute(response_text)))
def page_view() -> None: view_name = html.request.var("view_name") if not view_name: return page_index() view_spec = views.get_permitted_views().get(view_name) if not view_spec: raise MKUserError("view_name", "No view defined with the name '%s'." % view_name) datasource = data_source_registry[view_spec["datasource"]]() context = visuals.get_merged_context( visuals.get_context_from_uri_vars( datasource.infos, single_infos=view_spec["single_infos"]), view_spec["context"], ) view = views.View(view_name, view_spec, context) view.row_limit = views.get_limit() view.only_sites = views.get_only_sites() view.user_sorters = views.get_user_sorters() title = views.view_title(view_spec) mobile_html_head(title) painter_options = PainterOptions.get_instance() painter_options.load(view_name) try: view_renderer = MobileViewRenderer(view) views.show_view(view, view_renderer) except Exception as e: logger.exception("error showing mobile view") if config.debug: raise html.write("ERROR showing view: %s" % escaping.escape_attribute(str(e))) mobile_html_foot()
def time_series_math( operator_id: Literal["+", "*", "-", "/", "MAX", "MIN", "AVERAGE", "MERGE"], operands_evaluated: List[TimeSeries]) -> Optional[TimeSeries]: operators = time_series_operators() if operator_id not in operators: raise MKGeneralException( _("Undefined operator '%s' in graph expression") % escaping.escape_attribute(operator_id)) # Test for correct arity on FOUND[evaluated] data if any(( operator_id in ["-", "/"] and len(operands_evaluated) != 2, len(operands_evaluated) < 1, )): #raise MKGeneralException(_("Incorrect amount of data to correctly evaluate expression")) # Silently return so to get an empty graph slot return None _op_title, op_func = operators[operator_id] twindow = operands_evaluated[0].twindow return TimeSeries( [op_func_wrapper(op_func, tsp) for tsp in zip(*operands_evaluated)], twindow)
def _show_test_row(self, table, test_id, test_results_by_site, site_ids): table.row() table.cell(_("Actions"), css="buttons", sortable=False) html.icon_button( None, _("Toggle result details"), "toggle_details", onclick="cmk.wato.toggle_container('test_result_details_%s')" % test_id) worst_result = sorted(test_results_by_site["site_results"].values(), key=lambda result: result.status)[0] # Disabling of test in total is_test_disabled = self._is_test_disabled(test_id) if is_test_disabled: html.icon_button( html.makeactionuri([ ("_do", "enable"), ("_test_id", worst_result.test_id), ]), _("Reenable this test"), "enable_test", ) else: html.icon_button( html.makeactionuri([ ("_do", "disable"), ("_test_id", worst_result.test_id), ]), _("Disable this test"), "disable_test", ) # assume all have the same test meta information (title, help, ...) table.cell(_("Title"), css="title " + "stale" if is_test_disabled else "") html.write_text(test_results_by_site["test"]["title"]) # Now loop all sites to display their results for site_id in site_ids: if is_test_disabled: table.cell(site_id, "") table.cell("", "") continue result = test_results_by_site["site_results"].get(site_id) if result is None: table.cell(site_id, css="state state-1") table.cell("", css="buttons") continue is_acknowledged = self._is_acknowledged(result) if is_acknowledged or result.status == -1: css = "state stale" else: css = "state state%d" % result.status table.cell(site_id, css=css) html.open_div(title=result.text) html.write_text(result.status_name()) html.close_div() table.cell("", css="buttons") if result.status != 0: if is_acknowledged: html.icon_button( html.makeactionuri([ ("_do", "unack"), ("_site_id", result.site_id), ("_status_id", result.status), ("_test_id", result.test_id), ]), _("Unacknowledge this test result for site %s") % site_id, "unacknowledge_test", ) else: html.icon_button( html.makeactionuri([ ("_do", "ack"), ("_site_id", result.site_id), ("_status_id", result.status), ("_test_id", result.test_id), ]), _("Acknowledge this test result for site %s") % site_id, "acknowledge_test", ) else: html.write("") # Add toggleable notitication context table.row(class_="ac_test_details hidden", id_="test_result_details_%s" % test_id) table.cell(colspan=2 + 2 * len(site_ids)) html.write_text(test_results_by_site["test"]["help"]) if not is_test_disabled: html.open_table() for site_id in site_ids: result = test_results_by_site["site_results"].get(site_id) if result is None: continue html.open_tr() html.td(escaping.escape_attribute(site_id)) html.td("%s: %s" % (result.status_name(), result.text)) html.close_tr() html.close_table() # This dummy row is needed for not destroying the odd/even row highlighting table.row(class_="hidden")
def heading(text): html.write("<h3>%s</h3>\n" % escaping.escape_attribute(text))
def _show_host_row(self, rendered_hosts, table, hostname, search_text, show_checkboxes, colspan, host_errors, contact_group_names): if search_text and (search_text.lower() not in hostname.lower()): return host = self._folder.host(hostname) rendered_hosts.append(hostname) effective = host.effective_attributes() table.row() # Column with actions (buttons) if show_checkboxes: table.cell(html.render_input( "_toggle_group", type_="button", class_="checkgroup", onclick="cmk.selection.toggle_all_rows();", value='X'), sortable=False, css="checkbox") # Use CSS class "failed" in order to provide information about # selective toggling inventory-failed hosts for Javascript html.input(name="_c_%s" % hostname, type_="checkbox", value=colspan, class_="failed" if host.discovery_failed() else None) html.label("", "_c_%s" % hostname) table.cell(_("Actions"), css="buttons", sortable=False) self._show_host_actions(host) # Hostname with link to details page (edit host) table.cell(_("Hostname")) errors = host_errors.get(hostname, []) + host.validation_errors() if errors: msg = _("Warning: This host has an invalid configuration: ") msg += ", ".join(errors) html.icon(msg, "validation_error") html.nbsp() if host.is_offline(): html.icon(_("This host is disabled"), "disabled") html.nbsp() if host.is_cluster(): html.icon( _("This host is a cluster of %s") % ", ".join(host.cluster_nodes()), "cluster") html.nbsp() html.a(hostname, href=host.edit_url()) # Show attributes for attr in host_attribute_registry.attributes(): if attr.show_in_table(): attrname = attr.name() if attrname in host.attributes(): tdclass, tdcontent = attr.paint( host.attributes()[attrname], hostname) else: tdclass, tdcontent = attr.paint(effective.get(attrname), hostname) tdclass += " inherited" table.cell(attr.title(), escaping.escape_attribute(tdcontent), css=tdclass) # Am I authorized? reason = host.reason_why_may_not("read") if not reason: icon = "authok" title = _("You have permission to this host.") else: icon = "autherr" title = escaping.strip_tags(reason) table.cell(_('Auth'), html.render_icon(icon, title), css="buttons", sortable=False) # Permissions and Contact groups - through complete recursion and inhertance permitted_groups, host_contact_groups, _use_for_services = host.groups( ) table.cell( _("Permissions"), HTML(", ").join([ self._render_contact_group(contact_group_names, g) for g in permitted_groups ])) table.cell( _("Contact Groups"), HTML(", ").join([ self._render_contact_group(contact_group_names, g) for g in host_contact_groups ])) if not config.wato_hide_hosttags: table.cell(_("Tags"), css="tag-ellipsis") tag_groups, show_all_code = self._limit_labels(host.tag_groups()) html.write( cmk.gui.view_utils.render_tag_groups(tag_groups, "host", with_links=False)) html.write(show_all_code) table.cell(_("Explicit labels"), css="tag-ellipsis") labels, show_all_code = self._limit_labels(host.labels()) html.write( cmk.gui.view_utils.render_labels( labels, "host", with_links=False, label_sources={k: "explicit" for k in labels.keys()})) html.write(show_all_code) # Located in folder if self._folder.is_search_folder(): table.cell(_("Folder")) html.a(host.folder().alias_path(), href=host.folder().url())