Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    def page(self) -> CBORPageResult:
        assert user.id is not None
        user.need_permission("general.manage_2fa")

        raw_data = request.get_data()
        logger.debug("Raw request: %r", raw_data)
        data: dict[str, object] = cbor.decode(raw_data)
        client_data = ClientData(data["clientDataJSON"])
        att_obj = AttestationObject(data["attestationObject"])
        logger.debug("Client data: %r", client_data)
        logger.debug("Attestation object: %r", att_obj)

        auth_data = make_fido2_server().register_complete(
            session.session_info.webauthn_action_state, client_data, att_obj
        )

        ident = auth_data.credential_data.credential_id.hex()
        credentials = load_two_factor_credentials(user.id, lock=True)

        if ident in credentials["webauthn_credentials"]:
            raise MKGeneralException(_("Your WebAuthn credetial is already in use"))

        credentials["webauthn_credentials"][ident] = WebAuthnCredential(
            {
                "credential_id": ident,
                "registered_at": int(time.time()),
                "alias": "",
                "credential_data": bytes(auth_data.credential_data),
            }
        )
        save_two_factor_credentials(user.id, credentials)

        flash(_("Registration successful"))
        return {"status": "OK"}
Ejemplo n.º 3
0
def list_rules(param):
    """List rules"""
    user.need_permission("wato.rulesets")
    all_sets = watolib.AllRulesets()
    all_sets.load()
    ruleset_name = param["ruleset_name"]

    try:
        ruleset = all_sets.get(ruleset_name.replace("-", ":"))
    except KeyError:
        return problem(
            status=400,
            title="Unknown ruleset.",
            detail=f"The ruleset of name {ruleset_name!r} is not known.",
        )

    result = []
    for folder, index, rule in ruleset.get_rules():
        result.append(_serialize_rule(folder, index, rule))

    return serve_json(
        constructors.collection_object(
            domain_type="rule",
            value=result,
            extensions={
                "found_rules": len(result),
            },
        ))
Ejemplo n.º 4
0
def show_ruleset(param):
    """Show a ruleset"""
    ruleset_name = param["ruleset_name"]
    user.need_permission("wato.rulesets")
    collection = watolib.SingleRulesetRecursively(ruleset_name)
    collection.load()
    ruleset = collection.get(ruleset_name)
    return serve_json(_serialize_ruleset(ruleset))
Ejemplo n.º 5
0
 def page(self):
     user.need_permission("wato.backups")
     if request.var("job") == "restore":
         page: backup.PageAbstractBackupJobState = backup.PageBackupRestoreState(
         )
     else:
         page = ModeBackupJobState()
     page.show_job_details()
Ejemplo n.º 6
0
    def get_request(self) -> FetchAgentOutputRequest:
        user.need_permission("wato.download_agent_output")

        ascii_input = request.get_ascii_input("request")
        if ascii_input is None:
            raise MKUserError("request",
                              _('The parameter "%s" is missing.') % "request")
        return FetchAgentOutputRequest.deserialize(
            ast.literal_eval(ascii_input))
Ejemplo n.º 7
0
def _check_permissions(api_call):
    if not user.get_attribute("automation_secret"):
        raise MKAuthException("The API is only available for automation users")

    if not config.wato_enabled:
        raise MKUserError(None, _("Setup is disabled on this site."))

    for permission in ["wato.use", "wato.api_allowed"] + api_call.get("required_permissions", []):
        user.need_permission(permission)
Ejemplo n.º 8
0
def acknowledge_werks(werks, check_permission=True):
    if check_permission:
        user.need_permission("general.acknowledge_werks")

    ack_ids = load_acknowledgements()
    for werk in werks:
        ack_ids.append(werk["id"])
        werk["compatible"] = "incomp_ack"
    save_acknowledgements(ack_ids)
Ejemplo n.º 9
0
    def page(self):
        user.need_permission("wato.services")

        job_status_snapshot = self._job.get_status_snapshot()
        if job_status_snapshot.is_active():
            html.show_message(
                _('Bulk discovery currently running in <a href="%s">background</a>.'
                  ) % self._job.detail_url())
            return

        self._show_start_form()
Ejemplo n.º 10
0
    def page(self):
        user.need_permission("general.see_crash_reports")

        filename = "Checkmk_Crash_%s_%s_%s.tar.gz" % \
            (urlencode(self._site_id), urlencode(self._crash_id), time.strftime("%Y-%m-%d_%H-%M-%S"))

        response.headers[
            'Content-Disposition'] = 'Attachment; filename=%s' % filename
        response.headers['Content-Type'] = 'application/x-tar'
        response.set_data(
            _pack_crash_report(self._get_serialized_crash_report()))
Ejemplo n.º 11
0
def _check_modify_group_permissions(group_type: GroupType) -> None:
    required_permissions = {
        "contact": ["wato.users"],
        "host": ["wato.groups"],
        "service": ["wato.groups"],
    }

    # Check permissions
    perms = required_permissions.get(group_type)
    if perms is None:
        raise Exception("invalid group type %r" % (group_type, ))
    for permission in perms:
        user.need_permission(permission)
Ejemplo n.º 12
0
    def page(self):
        watolib.init_wato_datastructures(with_wato_lock=True)

        user.need_permission("wato.activate")

        api_request = self.webapi_request()

        activate_until = api_request.get("activate_until")
        if not activate_until:
            raise MKUserError("activate_until",
                              _('Missing parameter "%s".') % "activate_until")

        manager = watolib.ActivateChangesManager()
        manager.load()

        affected_sites_request = ensure_str(
            api_request.get("sites", "").strip())
        if not affected_sites_request:
            affected_sites = manager.dirty_and_active_activation_sites()
        else:
            affected_sites = affected_sites_request.split(",")

        comment: Optional[str] = api_request.get("comment", "").strip()

        activate_foreign = api_request.get("activate_foreign", "0") == "1"

        valuespec = _vs_activation("", manager.has_foreign_changes())
        if valuespec:
            valuespec.validate_value(
                {
                    "comment": comment,
                    "foreign": activate_foreign,
                },
                "activate",
            )

        if comment == "":
            comment = None

        activation_id = manager.start(
            sites=affected_sites,
            activate_until=ensure_str(activate_until),
            comment=None if comment is None else ensure_str(comment),
            activate_foreign=activate_foreign,
        )

        return {
            "activation_id": activation_id,
        }
Ejemplo n.º 13
0
    def page(self):
        user.need_permission("wato.activate")

        api_request = self.webapi_request()
        # ? type of activate_until is unclear
        activate_until = api_request.get("activate_until")
        if not activate_until:
            raise MKUserError("activate_until",
                              _('Missing parameter "%s".') % "activate_until")

        manager = watolib.ActivateChangesManager()
        manager.load()
        # ? type of api_request is unclear
        affected_sites_request = ensure_str(  # pylint: disable= six-ensure-str-bin-call
            api_request.get("sites", "").strip())
        if not affected_sites_request:
            affected_sites = manager.dirty_and_active_activation_sites()
        else:
            affected_sites = [
                SiteId(s) for s in affected_sites_request.split(",")
            ]

        comment: Optional[str] = api_request.get("comment", "").strip()

        activate_foreign = api_request.get("activate_foreign", "0") == "1"

        valuespec = _vs_activation("", manager.has_foreign_changes())
        if valuespec:
            valuespec.validate_value(
                {
                    "comment": comment,
                    "foreign": activate_foreign,
                },
                "activate",
            )

        if comment == "":
            comment = None

        activation_id = manager.start(
            sites=affected_sites,
            activate_until=ensure_str(activate_until),  # pylint: disable= six-ensure-str-bin-call
            comment=comment,
            activate_foreign=activate_foreign,
        )

        return {
            "activation_id": activation_id,
        }
Ejemplo n.º 14
0
    def page(self):
        user.need_permission("wato.activate")

        api_request = self.webapi_request()

        activation_id = api_request.get("activation_id")
        if not activation_id:
            raise MKUserError("activation_id",
                              _('Missing parameter "%s".') % "activation_id")

        manager = watolib.ActivateChangesManager()
        manager.load()
        manager.load_activation(activation_id)

        return manager.get_state()
Ejemplo n.º 15
0
    def page(self):
        watolib.init_wato_datastructures(with_wato_lock=True)

        user.need_permission("wato.activate")

        api_request = self.webapi_request()

        activation_id = api_request.get("activation_id")
        if not activation_id:
            raise MKUserError("activation_id", _("Missing parameter \"%s\".") % "activation_id")

        manager = watolib.ActivateChangesManager()
        manager.load()
        manager.load_activation(activation_id)

        return manager.get_state()
Ejemplo n.º 16
0
def list_rulesets(param):
    """Search rule sets"""
    user.need_permission("wato.rulesets")
    all_sets = (
        watolib.FolderRulesets(param["folder"]) if param.get("folder") else watolib.AllRulesets()
    )
    all_sets.load()

    def _get_search_options(params):
        # We remove 'folder' because that has already been handled at the start of the endpoint.
        options = dict(params)
        if "folder" in options:
            del options["folder"]
        return options

    if search_options := _get_search_options(param):
        all_sets = watolib.SearchedRulesets(all_sets, search_options)
Ejemplo n.º 17
0
    def action(self) -> ActionResult:
        if not transactions.check_transaction():
            return None

        user.need_permission("wato.edit_hosts")

        changed_attributes = watolib.collect_attributes("bulk", new=False)
        host_names = get_hostnames_from_checkboxes()
        for host_name in host_names:
            host = watolib.Folder.current().host(host_name)
            host.update_attributes(changed_attributes)
            # call_hook_hosts_changed() is called too often.
            # Either offer API in class Host for bulk change or
            # delay saving until end somehow

        flash(_("Edited %d hosts") % len(host_names))
        return redirect(watolib.Folder.current().url())
Ejemplo n.º 18
0
    def action(self) -> ActionResult:
        folder = watolib.Folder.current()
        if not transactions.check_transaction():
            return redirect(mode_url("folder", folder=folder.path()))

        if request.var("_update_dns_cache") and self._should_use_dns_cache():
            user.need_permission("wato.update_dns_cache")
            update_dns_cache_result = update_dns_cache(self._host.site_id())
            infotext = (_("Successfully updated IP addresses of %d hosts.") %
                        update_dns_cache_result.n_updated)
            if update_dns_cache_result.failed_hosts:
                infotext += "<br><br><b>Hostnames failed to lookup:</b> " + ", ".join(
                    [
                        "<tt>%s</tt>" % h
                        for h in update_dns_cache_result.failed_hosts
                    ])
            flash(infotext)
            return None

        if request.var("delete"):  # Delete this host
            folder.delete_hosts([self._host.name()])
            return redirect(mode_url("folder", folder=folder.path()))

        attributes = watolib.collect_attributes(
            "host" if not self._is_cluster() else "cluster", new=False)
        host = watolib.Host.host(self._host.name())
        if host is None:
            flash(f"Host {self._host.name()} could not be found.")
            return None

        host.edit(attributes, self._get_cluster_nodes())
        self._host = folder.load_host(self._host.name())

        if request.var("_save"):
            return redirect(
                mode_url("inventory",
                         folder=folder.path(),
                         host=self._host.name()))
        if request.var("diag_host"):
            return redirect(
                mode_url("diag_host",
                         folder=folder.path(),
                         host=self._host.name(),
                         _start_on_load="1"))
        return redirect(mode_url("folder", folder=folder.path()))
Ejemplo n.º 19
0
def robotmk_download_page() -> cmk.gui.pages.PageResult:
    user.need_permission("general.see_crash_reports")

    site_id, host_name, service_description = _get_mandatory_request_vars()

    filename = "RobotMK_report_%s_%s_%s_%s.tar.gz" % (
        urlencode(site_id),
        urlencode(host_name),
        urlencode(service_description),
        time.strftime("%Y-%m-%d_%H-%M-%S"),
    )

    response.headers[
        "Content-Disposition"] = "Attachment; filename=%s" % filename
    response.headers["Content-Type"] = "application/x-tar"
    html_content: bytes = _get_html_from_livestatus(site_id, host_name,
                                                    service_description)[0]
    response.set_data(_pack_html_content(html_content))
Ejemplo n.º 20
0
    def action(self) -> ActionResult:
        if request.var("_action") == "clear":
            user.need_permission("wato.auditlog")
            user.need_permission("wato.clear_auditlog")
            user.need_permission("wato.edit")
            return self._clear_audit_log_after_confirm()

        if html.request.var("_action") == "csv":
            user.need_permission("wato.auditlog")
            return self._export_audit_log(self._parse_audit_log())

        return None
Ejemplo n.º 21
0
    def action(self) -> ActionResult:
        if not transactions.check_transaction():
            return None

        user.need_permission("wato.edit_hosts")
        to_clean = self._bulk_collect_cleaned_attributes()
        if "contactgroups" in to_clean:
            self._folder.need_permission("write")

        hosts = get_hosts_from_checkboxes()

        # Check all permissions before doing any edit
        for host in hosts:
            host.need_permission("write")

        for host in hosts:
            host.clean_attributes(to_clean)

        return redirect(self._folder.url())
Ejemplo n.º 22
0
    def action(self) -> ActionResult:
        user.need_permission("wato.services")

        tasks = get_tasks(self._get_hosts_to_discover(), self._bulk_size)

        try:
            transactions.check_transaction()
            self._job.set_function(
                self._job.do_execute, self._mode, self._do_scan, self._error_handling, tasks
            )
            self._job.start()
        except Exception as e:
            if config.debug:
                raise
            logger.exception("Failed to start bulk discovery")
            raise MKUserError(
                None, _("Failed to start discovery: %s") % ("%s" % e).replace("\n", "\n<br>")
            )

        raise HTTPRedirect(self._job.detail_url())
Ejemplo n.º 23
0
    def action(self) -> ActionResult:
        folder = watolib.Folder.current()
        if not transactions.check_transaction():
            return redirect(mode_url("folder", folder=folder.path()))

        if request.var("_update_dns_cache") and self._should_use_dns_cache():
            user.need_permission("wato.update_dns_cache")
            num_updated, failed_hosts = watolib.check_mk_automation(
                self._host.site_id(), "update-dns-cache", [])
            infotext = _(
                "Successfully updated IP addresses of %d hosts.") % num_updated
            if failed_hosts:
                infotext += "<br><br><b>Hostnames failed to lookup:</b> " \
                          + ", ".join(["<tt>%s</tt>" % h for h in failed_hosts])
            flash(infotext)
            return None

        if request.var("delete"):  # Delete this host
            folder.delete_hosts([self._host.name()])
            return redirect(mode_url("folder", folder=folder.path()))

        attributes = watolib.collect_attributes(
            "host" if not self._is_cluster() else "cluster", new=False)
        watolib.Host.host(self._host.name()).edit(attributes,
                                                  self._get_cluster_nodes())
        self._host = folder.host(self._host.name())

        if request.var("services"):
            return redirect(
                mode_url("inventory",
                         folder=folder.path(),
                         host=self._host.name()))
        if request.var("diag_host"):
            return redirect(
                mode_url("diag_host",
                         folder=folder.path(),
                         host=self._host.name(),
                         _start_on_load="1"))
        return redirect(mode_url("folder", folder=folder.path()))
Ejemplo n.º 24
0
    def page(self) -> CBORPageResult:
        assert user.id is not None
        user.need_permission("general.manage_2fa")

        registration_data, state = make_fido2_server().register_begin(
            {
                "id": user.id.encode("utf-8"),
                "name": user.id,
                "displayName": user.alias,
                "icon": "",
            },
            [
                AttestedCredentialData.unpack_from(v["credential_data"])[0]
                for v in load_two_factor_credentials(user.id)["webauthn_credentials"].values()
            ],
            user_verification="discouraged",
            authenticator_attachment="cross-platform",
        )

        session.session_info.webauthn_action_state = state
        logger.debug("Registration data: %r", registration_data)
        return registration_data
Ejemplo n.º 25
0
    def _from_vars(self) -> None:
        user.need_permission("wato.download_agent_output")

        host_name = request.var("host")
        if not host_name:
            raise MKGeneralException(_("The host is missing."))

        ty = request.var("type")
        if ty not in ["walk", "agent"]:
            raise MKGeneralException(_("Invalid type specified."))

        self._back_url = request.get_url_input("back_url", deflt="") or None

        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)
Ejemplo n.º 26
0
def delete_rule(param):
    """Delete a rule"""
    user.need_permission("wato.rulesets")
    rule_id = param["rule_id"]
    rule: watolib.Rule
    all_sets = watolib.AllRulesets()
    all_sets.load()

    found = False
    for ruleset in all_sets.get_rulesets().values():
        for _folder, _index, rule in ruleset.get_rules():
            if rule.id == rule_id:
                ruleset.delete_rule(rule)
                all_sets.save()
                found = True
    if found:
        return http.Response(status=204)

    return problem(
        status=404,
        title="Rule not found.",
        detail=f"The rule with ID {rule_id!r} could not be found.",
    )
Ejemplo n.º 27
0
def rename_host(params):
    """Rename a host"""
    user.need_permission("wato.rename_hosts")
    if activate_changes.get_pending_changes_info():
        return problem(
            status=409,
            title="Pending changes are present",
            detail=
            "Please activate all pending changes before executing a host rename process",
        )
    host_name = params["host_name"]
    host: watolib.CREHost = watolib.Host.load_host(host_name)
    new_name = params["body"]["new_name"]
    _, auth_problems = perform_rename_hosts([(host.folder(), host_name,
                                              new_name)])
    if auth_problems:
        return problem(
            status=422,
            title="Rename process failed",
            detail=
            f"It was not possible to rename the host {host_name} to {new_name}",
        )
    return _serve_host(host, effective_attributes=False)
Ejemplo n.º 28
0
def move(params):
    """Move a host to another folder"""
    user.need_permission("wato.move_hosts")
    host_name = params["host_name"]
    host: watolib.CREHost = watolib.Host.load_host(host_name)
    _require_host_etag(host)
    current_folder = host.folder()
    target_folder: CREFolder = params["body"]["target_folder"]
    if target_folder is current_folder:
        return problem(
            status=400,
            title="Invalid move action",
            detail="The host is already part of the specified target folder",
        )

    try:
        current_folder.move_hosts([host_name], target_folder)
    except MKUserError as exc:
        return problem(
            status=400,
            title="Problem moving host",
            detail=exc.message,
        )
    return _serve_host(host, effective_attributes=False)
Ejemplo n.º 29
0
    def do_discovery(self, discovery_result):
        old_autochecks: SetAutochecksTable = {}
        autochecks_to_save: SetAutochecksTable = {}
        remove_disabled_rule, add_disabled_rule, saved_services = set(), set(
        ), set()
        apply_changes = False

        for (table_source, check_type, _checkgroup, item, discovered_params,
             _check_params, descr, _state, _output, _perfdata, service_labels,
             found_on_nodes) in discovery_result.check_table:
            # Versions >2.0b2 always provide a found on nodes information
            # If this information is missing (fallback value is None), the remote system runs on an older version

            table_target = self._get_table_target(table_source, check_type,
                                                  item)
            key = check_type, item
            value = descr, discovered_params, service_labels, found_on_nodes

            if table_source in [
                    DiscoveryState.MONITORED, DiscoveryState.IGNORED
            ]:
                old_autochecks[key] = value

            if table_source != table_target:
                if table_target == DiscoveryState.UNDECIDED:
                    user.need_permission("wato.service_discovery_to_undecided")
                elif table_target in [
                        DiscoveryState.MONITORED,
                        DiscoveryState.CLUSTERED_NEW,
                        DiscoveryState.CLUSTERED_OLD,
                ]:
                    user.need_permission("wato.service_discovery_to_undecided")
                elif table_target == DiscoveryState.IGNORED:
                    user.need_permission("wato.service_discovery_to_ignored")
                elif table_target == DiscoveryState.REMOVED:
                    user.need_permission("wato.service_discovery_to_removed")

                apply_changes = True

            if table_source == DiscoveryState.UNDECIDED:
                if table_target == DiscoveryState.MONITORED:
                    autochecks_to_save[key] = value
                    saved_services.add(descr)
                elif table_target == DiscoveryState.IGNORED:
                    add_disabled_rule.add(descr)

            elif table_source == DiscoveryState.VANISHED:
                if table_target != DiscoveryState.REMOVED:
                    autochecks_to_save[key] = value
                    saved_services.add(descr)
                if table_target == DiscoveryState.IGNORED:
                    add_disabled_rule.add(descr)

            elif table_source == DiscoveryState.MONITORED:
                if table_target in [
                        DiscoveryState.MONITORED,
                        DiscoveryState.IGNORED,
                ]:
                    autochecks_to_save[key] = value

                if table_target == DiscoveryState.IGNORED:
                    add_disabled_rule.add(descr)
                else:
                    saved_services.add(descr)

            elif table_source == DiscoveryState.IGNORED:
                if table_target in [
                        DiscoveryState.MONITORED,
                        DiscoveryState.UNDECIDED,
                        DiscoveryState.VANISHED,
                ]:
                    remove_disabled_rule.add(descr)
                if table_target in [
                        DiscoveryState.MONITORED,
                        DiscoveryState.IGNORED,
                ]:
                    autochecks_to_save[key] = value
                    saved_services.add(descr)
                if table_target == DiscoveryState.IGNORED:
                    add_disabled_rule.add(descr)

            elif table_source in [
                    DiscoveryState.CLUSTERED_NEW,
                    DiscoveryState.CLUSTERED_OLD,
            ]:
                autochecks_to_save[key] = value
                saved_services.add(descr)

            elif table_source in [
                    DiscoveryState.CLUSTERED_VANISHED,
                    DiscoveryState.CLUSTERED_IGNORED,
            ]:
                # We keep vanished clustered services on the node with the following reason:
                # If a service is mapped to a cluster then there are already operations
                # for adding, removing, etc. of this service on the cluster. Therefore we
                # do not allow any operation for this clustered service on the related node.
                # We just display the clustered service state (OLD, NEW, VANISHED).
                autochecks_to_save[key] = value
                saved_services.add(descr)

        if apply_changes:
            need_sync = False
            if remove_disabled_rule or add_disabled_rule:
                add_disabled_rule = add_disabled_rule - remove_disabled_rule - saved_services
                self._save_host_service_enable_disable_rules(
                    remove_disabled_rule, add_disabled_rule)
                need_sync = True
            self._save_services(
                old_autochecks,
                autochecks_to_save,
                need_sync,
            )
Ejemplo n.º 30
0
def _wato_page_handler(current_mode: str,
                       mode_permissions: Optional[List[PermissionName]],
                       mode_class: Type[WatoMode]) -> None:
    # Check general permission for this mode
    if mode_permissions is not None and not user.may("wato.seeall"):
        _ensure_mode_permissions(mode_permissions)

    mode = mode_class()

    # Do actions (might switch mode)
    if transactions.is_transaction():
        try:
            user.need_permission("wato.edit")

            # Even if the user has seen this mode because auf "seeall",
            # he needs an explicit access permission for doing changes:
            if user.may("wato.seeall"):
                if mode_permissions:
                    _ensure_mode_permissions(mode_permissions)

            if (cmk.gui.watolib.read_only.is_enabled()
                    and not cmk.gui.watolib.read_only.may_override()):
                raise MKUserError(None, cmk.gui.watolib.read_only.message())

            result = mode.action()
            if isinstance(result, (tuple, str, bool)):
                raise MKGeneralException(
                    f'WatoMode "{current_mode}" returns unsupported return value: {result!r}'
                )

            # We assume something has been modified and increase the config generation ID by one.
            update_config_generation()

            if config.wato_use_git:
                do_git_commit()

            # Handle two cases:
            # a) Don't render the page content after action
            #    (a confirm dialog is displayed by the action, or a non-HTML content was sent)
            # b) Redirect to another page
            if isinstance(result, FinalizeRequest):
                raise result

        except MKUserError as e:
            user_errors.add(e)

        except MKAuthException as e:
            user_errors.add(MKUserError(None, e.args[0]))

    breadcrumb = make_main_menu_breadcrumb(
        mode.main_menu()) + mode.breadcrumb()
    page_menu = mode.page_menu(breadcrumb)
    wato_html_head(
        title=mode.title(),
        breadcrumb=breadcrumb,
        page_menu=page_menu,
        show_body_start=display_options.enabled(display_options.H),
        show_top_heading=display_options.enabled(display_options.T),
    )

    if not transactions.is_transaction() or (
            cmk.gui.watolib.read_only.is_enabled()
            and cmk.gui.watolib.read_only.may_override()):
        _show_read_only_warning()

    # Show outcome of failed action on this page
    html.show_user_errors()

    # Show outcome of previous page (that redirected to this one)
    for message in get_flashed_messages():
        html.show_message(message)

    # Show content
    mode.handle_page()

    if is_sidebar_reload_needed():
        html.reload_whole_page()

    wato_html_footer(show_body_end=display_options.enabled(display_options.H))