def action(self) -> ActionResult: if not transactions.check_transaction(): return redirect(self.mode_url()) connections = load_connection_config(lock=True) if request.has_var("_delete"): index = request.get_integer_input_mandatory("_delete") connection = connections[index] self._add_change( "delete-ldap-connection", _("Deleted LDAP connection %s") % (connection["id"])) del connections[index] save_connection_config(connections) elif request.has_var("_move"): from_pos = request.get_integer_input_mandatory("_move") to_pos = request.get_integer_input_mandatory("_index") connection = connections[from_pos] self._add_change( "move-ldap-connection", _("Changed position of LDAP connection %s to %d") % (connection["id"], to_pos), ) del connections[from_pos] # make to_pos now match! connections[to_pos:to_pos] = [connection] save_connection_config(connections) return redirect(self.mode_url())
def _confirm(html_title, message): if not request.has_var("_do_confirm") and not request.has_var( "_do_actions"): # TODO: get the breadcrumb from all call sites wato_html_head(title=html_title, breadcrumb=Breadcrumb()) confirm_options = [(_("Confirm"), "_do_confirm")] return confirm_with_preview(message, confirm_options)
def action(self) -> ActionResult: if not transactions.check_transaction(): return redirect(mode_url("tags")) if request.has_var("_delete"): return self._delete_tag_group() if request.has_var("_del_aux"): return self._delete_aux_tag() if request.var("_move"): return self._move_tag_group() return redirect(mode_url("tags"))
def _get_search_vars(self) -> HTTPVariables: search_vars = {} if request.has_var("host_search_host"): search_vars[ "host_search_host"] = request.get_ascii_input_mandatory( "host_search_host") for varname, value in request.itervars(prefix="host_search_change_"): if html.get_checkbox(varname) is False: continue search_vars[varname] = value attr_ident = varname.split("host_search_change_", 1)[1] # The URL variable naming scheme is not clear. Try to match with "attr_" prefix # and without. We should investigate and clean this up. attr_prefix = "host_search_attr_%s" % attr_ident search_vars.update(request.itervars(prefix=attr_prefix)) attr_prefix = "host_search_%s" % attr_ident search_vars.update(request.itervars(prefix=attr_prefix)) for varname, value in request.itervars(): if varname.startswith(("_", "host_search_")) or varname == "mode": continue search_vars[varname] = value search_vars["mode"] = "folder" return list(search_vars.items())
def _action( self, cmdtag: str, spec: str, row: dict, row_index: int, num_rows: int ) -> CommandActionResult: if request.has_var("_delete_crash_reports"): commands = [("DEL_CRASH_REPORT;%s" % row["crash_id"])] return commands, _("remove") return None
def show_log_list(): title = _("All problematic logfiles") breadcrumb = make_simple_page_breadcrumb( mega_menu_registry.menu_monitoring(), title) make_header(html, title, breadcrumb, _log_list_page_menu(breadcrumb)) if request.has_var("_ack") and not request.var("_do_actions") == _("No"): do_log_ack(site=None, host_name=None, file_name=None) return for site, host_name, logs in all_logs(): if not logs: continue all_logs_empty = not any( parse_file(site, host_name, file_name) for file_name in logs) if all_logs_empty: continue # Logfile vanished html.h3( HTMLWriter.render_a( host_name, href=makeuri( request, [("site", site), ("host", host_name)], ), ), class_="table", ) list_logs(site, host_name, logs) html.footer()
def _page_not_found() -> Response: # TODO: This is a page handler. It should not be located in generic application # object. Move it to another place if request.has_var("_plain_error"): html.write_text(_("Page not found")) else: title = _("Page not found") make_header( html, title, Breadcrumb([ BreadcrumbItem( title="Nowhere", url=None, ), BreadcrumbItem( title=title, url="javascript:document.location.reload(false)", ), ]), ) html.show_error(_("This page was not found. Sorry.")) html.footer() return response
def page(self) -> PageResult: """Determines the hosts to be shown""" user.need_permission("general.parent_child_topology") topology_settings = TopologySettings() if request.var("filled_in"): # Parameters from the check_mk filters self._update_topology_settings_with_context(topology_settings) elif request.var("host_name"): # Explicit host_name. Used by icon linking to Topology topology_settings.growth_root_nodes = { HostName(html.request.get_str_input_mandatory("host_name")) } else: # Default page without further context topology_settings.growth_root_nodes = self._get_default_view_hostnames( topology_settings.growth_auto_max_nodes ) if request.has_var("topology_settings"): # These parameters are usually generated within javascript through user interactions try: settings_from_var = json.loads(request.get_str_input_mandatory("topology_settings")) for key, value in settings_from_var.items(): setattr(topology_settings, key, value) except (TypeError, ValueError): raise MKGeneralException(_("Invalid topology_settings %r") % topology_settings) self.show_topology(topology_settings)
def _delete_tag_group(self) -> ActionResult: del_id = request.get_item_input( "_delete", dict(self._tag_config.get_tag_group_choices()))[1] if not request.has_var( "_repair") and self._is_cleaning_up_user_tag_group_to_builtin( del_id): message: Union[bool, str] = ( _('Transformed the user tag group "%s" to builtin.') % del_id) else: message = _rename_tags_after_confirmation( self.breadcrumb(), OperationRemoveTagGroup(del_id)) if message is False: return FinalizeRequest(code=200) if message: self._tag_config.remove_tag_group(del_id) try: self._tag_config.validate_config() except MKGeneralException as e: raise MKUserError(None, "%s" % e) self._save_tags_and_update_hosts( self._tag_config.get_dict_format()) _changes.add_change( "edit-tags", _("Removed tag group %s (%s)") % (message, del_id)) if isinstance(message, str): flash(message) return redirect(mode_url("tags"))
class UserTwoFactorOverview(ABCUserProfilePage): def _page_title(self) -> str: return _("Two-factor authentication") def __init__(self) -> None: super().__init__("general.manage_2fa") def _action(self) -> None: assert user.id is not None credentials = load_two_factor_credentials(user.id) if credential_id := request.get_ascii_input("_delete"): if credential_id not in credentials["webauthn_credentials"]: return del credentials["webauthn_credentials"][credential_id] save_two_factor_credentials(user.id, credentials) flash(_("Credential has been deleted")) if request.has_var("_backup_codes"): codes = make_two_factor_backup_codes() credentials["backup_codes"] = [ pwhashed for _password, pwhashed in codes ] save_two_factor_credentials(user.id, credentials) flash( _("The following backup codes have been generated: <ul>%s</ul> These codes are " "displayed only now. Save them securely.") % "".join(f"<li><tt>{password}</tt></li>" for password, _pwhashed in codes))
def page_api() -> None: try: if not request.has_var("output_format"): response.set_content_type("application/json") output_format = "json" else: output_format = request.get_ascii_input_mandatory( "output_format", "json").lower() if output_format not in _FORMATTERS: response.set_content_type("text/plain") raise MKUserError( None, "Only %s are supported as output formats" % " and ".join('"%s"' % f for f in _FORMATTERS), ) # TODO: Add some kind of helper for boolean-valued variables? pretty_print = False pretty_print_var = request.get_str_input_mandatory( "pretty_print", "no").lower() if pretty_print_var not in ("yes", "no"): raise MKUserError(None, 'pretty_print must be "yes" or "no"') pretty_print = pretty_print_var == "yes" api_call = _get_api_call() _check_permissions(api_call) request_object = _get_request(api_call) _check_formats(output_format, api_call, request_object) _check_request_keys(api_call, request_object) resp = _execute_action(api_call, request_object) except MKAuthException as e: resp = { "result_code": 1, "result": _("Authorization Error. Insufficent permissions for '%s'") % e, } except MKException as e: resp = { "result_code": 1, "result": _("Checkmk exception: %s\n%s") % (e, "".join(traceback.format_exc())), } except Exception: if active_config.debug: raise logger.exception("error handling web API call") resp = { "result_code": 1, "result": _("Unhandled exception: %s") % traceback.format_exc(), } response.set_data( _FORMATTERS[output_format][1 if pretty_print else 0](resp))
def action(self) -> ActionResult: if not transactions.check_transaction(): return redirect(self.mode_url()) if request.has_var("_delete"): icon_name = request.var("_delete") if icon_name in self._load_custom_icons(): os.remove( "%s/local/share/check_mk/web/htdocs/images/icons/%s.png" % (cmk.utils.paths.omd_root, icon_name)) elif request.has_var("_save"): vs_upload = self._vs_upload() icon_info = vs_upload.from_html_vars("_upload_icon") vs_upload.validate_value(icon_info, "_upload_icon") self._upload_icon(icon_info) return redirect(self.mode_url())
def infos(self) -> SingleInfos: # Hack for create mode of dashlet editor. The user first selects a datasource and then the # single contexts, the dashlet editor needs to use these information. if requested_file_name(request) == "edit_dashlet" and request.has_var( "datasource"): ds_name = request.get_str_input_mandatory("datasource") return list(data_source_registry[ds_name]().infos) # TODO: Hmmm... return self._get_infos_from_view_spec(self._dashlet_spec)
def action(self) -> ActionResult: if transactions.transaction_valid(): if request.has_var("_do_upload"): self._upload_csv_file() csv_reader = self._open_csv_file() if request.var("_do_import"): return self._import(csv_reader) return None
def _werk_table_options_from_request() -> Dict[str, Any]: if request.var("show_unack") and not request.has_var("wo_set"): return _default_werk_table_options() werk_table_options: Dict[str, Any] = {} for name, _height, vs, default_value in _werk_table_option_entries(): value = default_value try: if request.has_var("wo_set"): value = vs.from_html_vars("wo_" + name) vs.validate_value(value, "wo_" + name) except MKUserError as e: html.user_error(e) werk_table_options.setdefault(name, value) from_date, until_date = Timerange.compute_range( werk_table_options["date"]).range werk_table_options["date_range"] = from_date, until_date return werk_table_options
def page(self): html.open_div(class_="diag_host") html.open_table() html.open_tr() html.open_td() html.begin_form("diag_host", method="POST") html.prevent_password_auto_completion() forms.header(_("Host Properties")) forms.section(legend=False) # The diagnose page shows both snmp variants at the same time # We need to analyse the preconfigured community and set either the # snmp_community or the snmp_v3_credentials vs_dict = {} for key, value in self._host.attributes().items(): if key == "snmp_community" and isinstance(value, tuple): vs_dict["snmp_v3_credentials"] = value continue vs_dict[key] = value vs_host = self._vs_host() vs_host.render_input("vs_host", vs_dict) html.help(vs_host.help()) forms.end() html.open_div(style="margin-bottom:10px") html.close_div() forms.header(_("Options")) value = {} forms.section(legend=False) vs_rules = self._vs_rules() vs_rules.render_input("vs_rules", value) html.help(vs_rules.help()) forms.end() # When clicking "Save & Test" on the "Edit host" page, this will be set # to immediately execute the tests using the just saved settings if request.has_var("_start_on_load"): html.final_javascript("cmk.page_menu.form_submit('diag_host', '_save');") html.hidden_fields() html.end_form() html.close_td() html.open_td(style="padding-left:10px;") self._show_diagnose_output()
def _show_view_as_dashlet(self, view_spec: ViewSpec): html.add_body_css_class("view") html.open_div(id_="dashlet_content_wrapper") is_reload = request.has_var("_reload") view_display_options = "SIXLW" if not is_reload: view_display_options += "HR" request.set_var("display_options", view_display_options) request.set_var("_display_options", view_display_options) html.add_body_css_class("dashlet") # Need to be loaded before processing the painter_options below. # TODO: Make this dependency explicit display_options.load_from_html(request, html) painter_options = PainterOptions.get_instance() painter_options.load(self._dashlet_spec["name"]) # Here the linked view default context has the highest priority # linkedview default>dashlet>url active filter> dashboard. However views # have the "show_filters" default to prefill the filtermenu with empty # valued filters(UX). Those need to be cleared out. Otherwise those # empty filters are the highest priority filters and the user can never # filter the view. view_context = { filtername: filtervalues for filtername, filtervalues in view_spec["context"].items() if { var: value for var, value in filtervalues.items() # These are the filters request variables. Keep set values # For the TriStateFilters unset == ignore == "-1" # all other cases unset is an empty string if (var.startswith("is_") and value != "-1" ) # TriState filters except ignore or (not var.startswith("is_") and value ) # Rest of filters with some value } } context = visuals.get_merged_context(self.context, view_context) view = views.View(self._dashlet_spec["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() views.process_view(views.GUIViewRenderer(view, show_buttons=False)) html.close_div()
def render_unacknowleged_werks(): werks = unacknowledged_incompatible_werks() if werks and not request.has_var("show_unack"): html.open_div(class_=["warning"]) html.write_text( _("<b>Warning:</b> There are %d unacknowledged incompatible werks:" ) % len(werks)) html.br() html.br() html.a( _("Show unacknowledged incompatible werks"), href=makeuri_contextless(request, [("show_unack", "1"), ("wo_compatibility", "3")]), ) html.close_div()
def selection_id() -> str: """Generates a selection id or uses the given one""" if not request.has_var("selection"): sel_id = utils.gen_id() request.set_var("selection", sel_id) return sel_id sel_id = request.get_str_input_mandatory("selection") # Avoid illegal file access by introducing .. or / if not re.match("^[-0-9a-zA-Z]+$", sel_id): new_id = utils.gen_id() request.set_var("selection", new_id) return new_id return sel_id
def show_host_log_list(site, host_name): title = _("Logfiles of host %s") % host_name breadcrumb = _host_log_list_breadcrumb(host_name, title) make_header(html, title, breadcrumb, _host_log_list_page_menu(breadcrumb, site, host_name)) if request.has_var("_ack") and not request.var("_do_actions") == _("No"): do_log_ack(site, host_name, file_name=None) return html.open_table(class_=["data"]) list_logs(site, host_name, logfiles_of_host(site, host_name)) html.close_table() html.footer()
def page(self): if not user.may("wato.automation"): raise MKAuthException( _("This account has no permission for automation.")) response.set_content_type("text/plain") _set_version_headers() # Parameter was added with 1.5.0p10 if not request.has_var("_version"): raise MKGeneralException( _("Your central site is incompatible with this remote site")) # - _version and _edition_short were added with 1.5.0p10 to the login call only # - x-checkmk-version and x-checkmk-edition were added with 2.0.0p1 # Prefer the headers and fall back to the request variables for now. central_version = (request.headers["x-checkmk-version"] if "x-checkmk-version" in request.headers else request.get_ascii_input_mandatory("_version")) central_edition_short = ( request.headers["x-checkmk-edition"] if "x-checkmk-edition" in request.headers else request.get_ascii_input_mandatory("_edition_short")) if not compatible_with_central_site( central_version, central_edition_short, cmk_version.__version__, cmk_version.edition().short, ): raise MKGeneralException( _("Your central site (Version: %s, Edition: %s) is incompatible with this " "remote site (Version: %s, Edition: %s)") % ( central_version, central_edition_short, cmk_version.__version__, cmk_version.edition().short, )) response.set_data( repr({ "version": cmk_version.__version__, "edition_short": cmk_version.edition().short, "login_secret": _get_login_secret(create_on_demand=True), }))
def action(self) -> ActionResult: if self._may_edit_config() and request.has_var("_delete"): key_id_as_str = request.var("_delete") if key_id_as_str is None: raise Exception("cannot happen") key_id = int(key_id_as_str) keys = self.key_store.load() if key_id not in keys: return None key = keys[key_id] if self._key_in_use(key_id, key): raise MKUserError("", _("This key is still used.")) del keys[key_id] self._log_delete_action(key_id, key) self.key_store.save(keys) return None
def confirm_with_preview(msg: Union[str, HTML], confirm_options: List[Tuple[str, str]], method: str = "POST") -> Optional[bool]: """Show a confirm dialog to the user BE AWARE: In case you just want to have some action confirmed by the user, you should use the javascript powere confirm dialg (make_confirm, add_confirm_on_submit, ...). This method is used only in places where we explicitly need to show important information to the user before he can decide whether or not to confirm the action. The confirm dialog is normally not a dialog which need to be protected by a transid itselfs. It is only a intermediate step to the real action But there are use cases where the confirm dialog is used during rendering a normal page, for example when deleting a dashlet from a dashboard. In such cases, the transid must be added by the confirm dialog. """ if request.var("_do_actions") == _("Cancel"): # User has pressed "Cancel", now invalidate the unused transid transactions.check_transaction() return None # None --> "Cancel" if not any( request.has_var(varname) for _title, varname in confirm_options): mobile = is_mobile(request, response) if mobile: html.open_center() html.open_div(class_="really") html.write_text(msg) html.begin_form("confirm", method=method, add_transid=False) html.hidden_fields(add_action_vars=True) for title, varname in confirm_options: html.button(varname, title, "really") html.button("_do_actions", _("Cancel")) html.end_form() html.close_div() if mobile: html.close_center() return False # False --> "Dialog shown, no answer yet" # Now check the transaction. True: "Yes", None --> Browser reload of "yes" page return True if transactions.check_transaction() else None
def page(self) -> None: row = self._get_crash_row() crash_info = self._get_crash_info(row) title = _("Crash report: %s") % self._crash_id breadcrumb = self._breadcrumb(title) make_header(html, title, breadcrumb, self._page_menu(breadcrumb, crash_info)) # Do not reveal crash context information to unauthenticated users or not permitted # users to prevent disclosure of internal information if not user.may("general.see_crash_reports"): html.show_error("<b>%s:</b> %s" % (_("Internal error"), crash_info["exc_value"])) html.p( _("An internal error occurred while processing your request. " "You can report this issue to your Checkmk administrator. " "Detailed information can be found on the crash report page " "or in <tt>var/log/web.log</tt>.")) html.footer() return if request.has_var("_report") and transactions.check_transaction(): details = self._handle_report_form(crash_info) else: details = ReportSubmitDetails(name="", mail="") if crash_info["crash_type"] == "gui": html.show_error("<b>%s:</b> %s" % (_("Internal error"), crash_info["exc_value"])) html.p( _("An internal error occured while processing your request. " "You can report this issue to the Checkmk team to help " "fixing this issue. Please use the form below for reporting." )) self._warn_about_local_files(crash_info) self._show_report_form(crash_info, details) self._show_crash_report(crash_info) self._show_crash_report_details(crash_info, row) html.footer()
def page_menu(self, breadcrumb: Breadcrumb) -> PageMenu: if not request.has_var("file_id"): return make_simple_form_page_menu( _("Hosts"), breadcrumb, form_name="upload", button_name="_do_upload", save_title=_("Upload"), ) # preview phase, after first upload return PageMenu( dropdowns=[ PageMenuDropdown( name="actions", title=_("Actions"), topics=[ PageMenuTopic( title=_("Actions"), entries=[ PageMenuEntry( title=_("Update preview"), icon_name="update", item=make_form_submit_link("preview", "_do_preview"), is_shortcut=True, is_suggested=True, ), PageMenuEntry( title=_("Import"), icon_name="save", item=make_form_submit_link("preview", "_do_import"), is_shortcut=True, is_suggested=True, ), ], ), ], ), ], breadcrumb=breadcrumb, )
def render_input(self, varprefix: str, value: dict[str, Any]) -> None: # The display mode differs when the valuespec is activated vars_copy = dict(request.itervars()) # The timeperiod mode can be set by either the GUI switch or by the value itself # GUI switch overrules the information stored in the value if request.has_var(self.tp_toggle_var): is_active = self._is_switched_on() else: is_active = self.is_active(value) # Set the actual used mode html.hidden_field(self.tp_current_mode, "%d" % is_active) vars_copy[self.tp_toggle_var] = "%d" % (not is_active) url_vars: HTTPVariables = [] url_vars += vars_copy.items() toggle_url = makeuri(request, url_vars) if is_active: value = self._get_timeperiod_value(value) self._get_timeperiod_valuespec().render_input(varprefix, value) html.buttonlink( toggle_url, _("Disable timespecific parameters"), class_=["toggle_timespecific_parameter"], ) else: value = self._get_timeless_value(value) self._enclosed_valuespec.render_input(varprefix, value) html.buttonlink( toggle_url, _("Enable timespecific parameters"), class_=["toggle_timespecific_parameter"], )
def _get_id(self): if not request.has_var("edit"): return None return request.get_item_input( "edit", dict(self._tag_config.aux_tag_list.get_choices()))[1]
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 plain_error() -> bool: """Webservice functions may decide to get a normal result code but a text with an error message in case of an error""" return request.has_var("_plain_error") or requested_file_name( request) == "webapi"
def fail_silently() -> bool: """Ajax-Functions want no HTML output in case of an error but just a plain server result code of 500""" return request.has_var("_ajaxid")