コード例 #1
0
def edit_users(changed_users):
    all_users = userdb.load_users(lock=True)
    new_users_info = []
    modified_users_info = []
    for user_id, settings in changed_users.items():
        user_attrs = settings.get("attributes")
        is_new_user = settings.get("is_new_user", True)
        _validate_user_attributes(all_users, user_id, user_attrs, is_new_user=is_new_user)
        if is_new_user:
            new_users_info.append(user_id)
        else:
            modified_users_info.append(user_id)

        if is_new_user:
            add_internal_attributes(user_attrs)

        old_object = make_user_audit_log_object(all_users.get(user_id, {}))
        log_audit(action="edit-user",
                  message=(_("Created new user: %s") %
                           user_id if is_new_user else _("Modified user: %s") % user_id),
                  diff_text=make_diff_text(old_object, make_user_audit_log_object(user_attrs)),
                  object_ref=make_user_object_ref(user_id))

        all_users[user_id] = user_attrs

    if new_users_info:
        add_change("edit-users", _("Created new users: %s") % ", ".join(new_users_info))
    if modified_users_info:
        add_change("edit-users", _("Modified users: %s") % ", ".join(modified_users_info))

    userdb.save_users(all_users)
コード例 #2
0
ファイル: global_settings.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        varname = request.var("_varname")
        if not varname:
            return None

        action = request.var("_action")

        config_variable = config_variable_registry[varname]()
        def_value = self._default_values[varname]

        if not transactions.check_transaction():
            return None

        if varname in self._current_settings:
            self._current_settings[
                varname] = not self._current_settings[varname]
        else:
            self._current_settings[varname] = not def_value
        msg = _("Changed Configuration variable %s to %s.") % (
            varname,
            "on" if self._current_settings[varname] else "off",
        )
        save_global_settings(self._current_settings)

        _changes.add_change(
            "edit-configvar",
            msg,
            domains=[config_variable.domain()],
            need_restart=config_variable.need_restart(),
        )

        if action == "_reset":
            flash(msg)
        return redirect(mode_url("globalvars"))
コード例 #3
0
ファイル: tags.py プロジェクト: LinuxHaus/checkmk
    def _delete_aux_tag(self) -> ActionResult:
        del_id = request.get_item_input(
            "_del_aux", dict(self._tag_config.aux_tag_list.get_choices()))[1]

        # Make sure that this aux tag is not begin used by any tag group
        for group in self._tag_config.tag_groups:
            for grouped_tag in group.tags:
                if del_id in grouped_tag.aux_tag_ids:
                    raise MKUserError(
                        None,
                        _("You cannot delete this auxiliary tag. "
                          "It is being used in the tag group <b>%s</b>.") %
                        group.title,
                    )

        message = _rename_tags_after_confirmation(
            self.breadcrumb(), OperationRemoveAuxTag(del_id))
        if message is False:
            return FinalizeRequest(code=200)

        if message:
            self._tag_config.aux_tag_list.remove(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 auxiliary tag %s (%s)") % (message, del_id))
            if isinstance(message, str):
                flash(message)
        return redirect(mode_url("tags"))
コード例 #4
0
ファイル: global_settings.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        if request.var("_reset"):
            if not transactions.check_transaction():
                return None

            try:
                del self._current_settings[self._varname]
            except KeyError:
                pass

            msg = escape_to_html(
                _("Resetted configuration variable %s to its default.") %
                self._varname)
        else:
            new_value = self._valuespec.from_html_vars("ve")
            self._valuespec.validate_value(new_value, "ve")
            self._current_settings[self._varname] = new_value
            msg = HTML(
                _("Changed global configuration variable %s to %s.") % (
                    escaping.escape_attribute(self._varname),
                    self._valuespec.value_to_html(new_value),
                ))

        self._save()
        _changes.add_change(
            "edit-configvar",
            msg,
            sites=self._affected_sites(),
            domains=[self._config_variable.domain()],
            need_restart=self._config_variable.need_restart(),
        )

        return redirect(self._back_url())
コード例 #5
0
ファイル: user_profile.py プロジェクト: bbaumer/checkmk
def _synchronize_profiles_to_sites(logger, profiles_to_synchronize):
    if not profiles_to_synchronize:
        return

    remote_sites = [(site_id, config.site(site_id))
                    for site_id in config.get_login_slave_sites()]

    logger.info('Credentials changed for %s. Trying to sync to %d sites' %
                (", ".join(profiles_to_synchronize.keys()), len(remote_sites)))

    states = sites.states()

    pool = ThreadPool()
    jobs = []
    for site_id, site in remote_sites:
        jobs.append(
            pool.apply_async(_sychronize_profile_worker,
                             (states, site_id, site, profiles_to_synchronize)))

    results = []
    start_time = time.time()
    while time.time() - start_time < 30:
        for job in jobs[:]:
            try:
                results.append(job.get(timeout=0.5))
                jobs.remove(job)
            except mp_TimeoutError:
                pass
        if not jobs:
            break

    contacted_sites = {x[0] for x in remote_sites}
    working_sites = {result.site_id for result in results}
    for site_id in contacted_sites - working_sites:
        results.append(
            SynchronizationResult(
                site_id,
                error_text=_("No response from update thread"),
                failed=True))

    for result in results:
        if result.error_text:
            logger.info('  FAILED [%s]: %s' %
                        (result.site_id, result.error_text))
            if config.wato_enabled:
                add_change("edit-users",
                           _('Password changed (sync failed: %s)') %
                           result.error_text,
                           add_user=False,
                           sites=[result.site_id],
                           need_restart=False)

    pool.terminate()
    pool.join()

    num_failed = sum([1 for result in results if result.failed])
    num_disabled = sum([1 for result in results if result.disabled])
    num_succeeded = sum([1 for result in results if result.succeeded])
    logger.info('  Disabled: %d, Succeeded: %d, Failed: %d' %
                (num_disabled, num_succeeded, num_failed))
コード例 #6
0
 def insert_rule_after(self, rule, after):
     index = self._rules[rule.folder.path()].index(after) + 1
     self._rules[rule.folder.path()].insert(index, rule)
     add_change("clone-ruleset",
                _("Cloned rule in ruleset '%s'") % self.title(),
                sites=rule.folder.all_site_ids())
     self._on_change()
コード例 #7
0
ファイル: tags.py プロジェクト: LinuxHaus/checkmk
    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"))
コード例 #8
0
ファイル: timeperiods.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        delname = request.var("_delete")
        if not delname:
            return redirect(mode_url("timeperiods"))

        if not transactions.check_transaction():
            return redirect(mode_url("timeperiods"))

        if delname in watolib.timeperiods.builtin_timeperiods():
            raise MKUserError("_delete",
                              _("Builtin timeperiods can not be modified"))

        usages = self._find_usages_of_timeperiod(delname)
        if usages:
            message = "<b>%s</b><br>%s:<ul>" % (
                _("You cannot delete this timeperiod."),
                _("It is still in use by"),
            )
            for title, link in usages:
                message += '<li><a href="%s">%s</a></li>\n' % (link, title)
            message += "</ul>"
            raise MKUserError(None, message)

        del self._timeperiods[delname]
        watolib.timeperiods.save_timeperiods(self._timeperiods)
        _changes.add_change("edit-timeperiods",
                            _("Deleted timeperiod %s") % delname)
        return redirect(mode_url("timeperiods"))
コード例 #9
0
def edit_users(changed_users):
    all_users = userdb.load_users(lock=True)
    new_users_info = []
    modified_users_info = []
    for user_id, settings in changed_users.items():
        user_attrs = settings.get("attributes")
        is_new_user = settings.get("is_new_user", True)
        _validate_user_attributes(all_users,
                                  user_id,
                                  user_attrs,
                                  is_new_user=is_new_user)
        if is_new_user:
            new_users_info.append(user_id)
        else:
            modified_users_info.append(user_id)

        all_users[user_id] = user_attrs

    if new_users_info:
        add_change("edit-users",
                   _("Created new user: %s") % ", ".join(new_users_info))
    if modified_users_info:
        add_change("edit-users",
                   _("Modified user: %s") % ", ".join(modified_users_info))

    userdb.save_users(all_users)
コード例 #10
0
ファイル: host_rename.py プロジェクト: PLUTEX/checkmk
    def rename_host_in_folder_rules(folder):
        rulesets = watolib.FolderRulesets(folder)
        rulesets.load()

        changed_folder_rulesets = []
        for varname, ruleset in rulesets.get_rulesets().items():
            for _rule_folder, _rulenr, rule in ruleset.get_rules():
                orig_rule = rule.clone(preserve_id=True)
                if rule.replace_explicit_host_condition(oldname, newname):
                    changed_folder_rulesets.append(varname)

                    log_audit(
                        "edit-rule",
                        _('Renamed host condition from "%s" to "%s"') % (oldname, newname),
                        diff_text=make_diff_text(orig_rule.to_log(), rule.to_log()),
                        object_ref=rule.object_ref(),
                    )

        if changed_folder_rulesets:
            add_change(
                "edit-ruleset",
                _("Renamed host in %d rulesets of folder %s")
                % (len(changed_folder_rulesets), folder.title()),
                object_ref=folder.object_ref(),
                sites=folder.all_site_ids(),
            )
            rulesets.save()

        changed_rulesets.extend(changed_folder_rulesets)

        for subfolder in folder.subfolders():
            rename_host_in_folder_rules(subfolder)
コード例 #11
0
ファイル: user_profile.py プロジェクト: mahdi7839/checkmk
def _add_profile_replication_change(site_id: SiteId,
                                    result: Union[bool, str]) -> None:
    """Add pending change entry to make sync possible later for admins"""
    add_change("edit-users",
               _('Profile changed (sync failed: %s)') % result,
               sites=[site_id],
               need_restart=False)
コード例 #12
0
def delete_users(users_to_delete):
    user.need_permission("wato.users")
    user.need_permission("wato.edit")
    if user.id in users_to_delete:
        raise MKUserError(None, _("You cannot delete your own account!"))

    all_users = userdb.load_users(lock=True)

    deleted_users = []
    for entry in users_to_delete:
        if entry in all_users:  # Silently ignore not existing users
            deleted_users.append(entry)
            del all_users[entry]
        else:
            raise MKUserError(None, _("Unknown user: %s") % entry)

    if deleted_users:
        for user_id in deleted_users:
            log_audit(
                "edit-user",
                _("Deleted user: %s") % user_id,
                object_ref=make_user_object_ref(user_id),
            )
        add_change("edit-users",
                   _("Deleted user: %s") % ", ".join(deleted_users))
        userdb.save_users(all_users, datetime.now())
コード例 #13
0
    def action(self) -> ActionResult:
        if not transactions.check_transaction():
            return redirect(self.mode_url())

        if request.var("_delete"):
            delid = request.get_ascii_input_mandatory("_delete")

            if delid not in self._roles:
                raise MKUserError(None, _("This role does not exist."))

            if transactions.transaction_valid() and self._roles[delid].get("builtin"):
                raise MKUserError(None, _("You cannot delete the builtin roles!"))

            users = userdb.load_users()
            for user in users.values():
                if delid in user["roles"]:
                    raise MKUserError(
                        None,
                        _("You cannot delete roles, that are still in use (%s)!") % delid,
                    )

            self._rename_user_role(delid, None)  # Remove from existing users
            del self._roles[delid]
            self._save_roles()
            _changes.add_change(
                "edit-roles", _("Deleted role '%s'") % delid, sites=get_login_sites()
            )

        elif request.var("_clone"):
            cloneid = request.get_ascii_input_mandatory("_clone")

            try:
                cloned_role = self._roles[cloneid]
            except KeyError:
                raise MKUserError(None, _("This role does not exist."))

            newid = cloneid
            while newid in self._roles:
                newid += "x"

            new_role = {}
            new_role.update(cloned_role)

            new_alias = new_role["alias"]
            while not groups.is_alias_used("roles", newid, new_alias)[0]:
                new_alias += _(" (copy)")
            new_role["alias"] = new_alias

            if cloned_role.get("builtin"):
                new_role["builtin"] = False
                new_role["basedon"] = cloneid

            self._roles[newid] = new_role
            self._save_roles()
            _changes.add_change(
                "edit-roles", _("Created new role '%s'") % newid, sites=get_login_sites()
            )

        return redirect(self.mode_url())
コード例 #14
0
ファイル: tags.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        if not transactions.check_transaction():
            return redirect(mode_url("tags"))

        vs = self._valuespec()
        tag_group_spec = vs.from_html_vars("tag_group")
        vs.validate_value(tag_group_spec, "tag_group")

        # Create new object with existing host tags
        changed_hosttags_config = cmk.utils.tags.TagConfig.from_config(
            self._tag_config_file.load_for_modification())
        changed_tag_group = cmk.utils.tags.TagGroup.from_config(tag_group_spec)
        self._tag_group = changed_tag_group

        if self._new:
            # Inserts and verifies changed tag group
            changed_hosttags_config.insert_tag_group(changed_tag_group)
            try:
                changed_hosttags_config.validate_config()
            except MKGeneralException as e:
                raise MKUserError(None, "%s" % e)
            self._save_tags_and_update_hosts(
                changed_hosttags_config.get_dict_format())
            _changes.add_change(
                "edit-hosttags",
                _("Created new host tag group '%s'") % changed_tag_group.id)
            flash(
                _("Created new host tag group '%s'") % changed_tag_group.title)
            return redirect(mode_url("tags"))

        # Updates and verifies changed tag group
        changed_hosttags_config.update_tag_group(changed_tag_group)
        try:
            changed_hosttags_config.validate_config()
        except MKGeneralException as e:
            raise MKUserError(None, "%s" % e)

        remove_tag_ids, replace_tag_ids = identify_modified_tags(
            changed_tag_group, self._untainted_tag_group)
        tg_id = self._tag_group.id
        if tg_id is None:
            raise Exception("tag group ID not set")
        operation = OperationReplaceGroupedTags(tg_id, remove_tag_ids,
                                                replace_tag_ids)

        # Now check, if any folders, hosts or rules are affected
        message = _rename_tags_after_confirmation(self.breadcrumb(), operation)
        if message is False:
            return FinalizeRequest(code=200)

        self._save_tags_and_update_hosts(
            changed_hosttags_config.get_dict_format())
        _changes.add_change(
            "edit-hosttags",
            _("Edited host tag group %s (%s)") % (message, self._id))
        if isinstance(message, str):
            flash(message)

        return redirect(mode_url("tags"))
コード例 #15
0
 def move_rule_to_bottom(self, rule):
     rules = self._rules[rule.folder.path()]
     index = rules.index(rule)
     rules.remove(rule)
     rules.append(rule)
     add_change("edit-ruleset",
                _("Moved rule #%d to bottom in ruleset \"%s\"") % (index, self.title()),
                sites=rule.folder.all_site_ids())
コード例 #16
0
 def move_rule_down(self, rule):
     rules = self._rules[rule.folder.path()]
     index = rules.index(rule)
     del rules[index]
     rules[index + 1:index + 1] = [rule]
     add_change("edit-ruleset",
                _("Moved rule #%d down in ruleset \"%s\"") % (index, self.title()),
                sites=rule.folder.all_site_ids())
コード例 #17
0
 def move_rule_to(self, rule, index):
     rules = self._rules[rule.folder.path()]
     old_index = rules.index(rule)
     rules.remove(rule)
     rules.insert(index, rule)
     add_change("edit-ruleset",
                _("Moved rule #%d to #%d in ruleset \"%s\"") % (old_index, index, self.title()),
                sites=rule.folder.all_site_ids())
コード例 #18
0
ファイル: groups.py プロジェクト: n00rm/checkmk
def _add_group_change(group, action_name, text):
    group_sites = None
    if cmk.is_managed_edition() and not managed.is_global(
            managed.get_customer_id(group)):
        group_sites = managed.get_sites_of_customer(
            managed.get_customer_id(group))

    add_change(action_name, text, sites=group_sites)
コード例 #19
0
ファイル: rulesets.py プロジェクト: ricardolopez010/checkmk
 def move_rule_to(self, rule, index):
     rules = self._rules[rule.folder.path()]
     old_index = rules.index(rule)
     rules.remove(rule)
     rules.insert(index, rule)
     add_change("edit-ruleset",
                _("Moved rule %s from position #%d to #%d in ruleset \"%s\" in folder \"%s\"") %
                (rule.id, old_index, index, self.title(), rule.folder.alias_path()),
                sites=rule.folder.all_site_ids(),
                object_ref=self.object_ref())
コード例 #20
0
def _add_group_change(group: GroupSpec, action_name: str, text: str) -> None:
    group_sites = None
    if cmk_version.is_managed_edition():
        cid = managed.get_customer_id(group)
        if not managed.is_global(cid):
            if cid is None:  # conditional caused by bad typing
                raise Exception("cannot happen: no customer ID")
            group_sites = list(managed.get_sites_of_customer(cid).keys())

    add_change(action_name, text, sites=group_sites)
コード例 #21
0
ファイル: activate_changes.py プロジェクト: LinuxHaus/checkmk
    def action(self) -> ActionResult:
        if request.var("_action") != "discard":
            return None

        if not transactions.check_transaction():
            return None

        if not self._may_discard_changes():
            return None

        if not self.has_changes():
            return None

        # Now remove all currently pending changes by simply restoring the last automatically
        # taken snapshot. Then activate the configuration. This should revert all pending changes.
        file_to_restore = self._get_last_wato_snapshot_file()

        if not file_to_restore:
            raise MKUserError(None,
                              _("There is no WATO snapshot to be restored."))

        msg = _("Discarded pending changes (Restored %s)") % file_to_restore

        # All sites and domains can be affected by a restore: Better restart everything.
        _changes.add_change(
            "changes-discarded",
            msg,
            domains=ABCConfigDomain.enabled_domains(),
            need_restart=True,
        )

        self._extract_snapshot(file_to_restore)
        activate_changes.execute_activate_changes([
            d.get_domain_request([])
            for d in ABCConfigDomain.enabled_domains()
        ])

        for site_id in activation_sites():
            self.confirm_site_changes(site_id)

        build_index_background()

        make_header(
            html,
            self.title(),
            breadcrumb=self.breadcrumb(),
            show_body_start=display_options.enabled(display_options.H),
            show_top_heading=display_options.enabled(display_options.T),
        )
        html.open_div(class_="wato")

        html.show_message(_("Successfully discarded all pending changes."))
        html.javascript("hide_changes_buttons();")
        html.footer()
        return FinalizeRequest(code=200)
コード例 #22
0
ファイル: rulesets.py プロジェクト: ricardolopez010/checkmk
    def clone_rule(self, orig_rule, rule):
        if rule.folder == orig_rule.folder:
            self.insert_rule_after(rule, orig_rule)
        else:
            self.append_rule(rule.folder, rule)

        add_change("new-rule",
                   _("Cloned rule from rule %s in ruleset \"%s\" in folder \"%s\"") %
                   (orig_rule.id, self.title(), rule.folder.alias_path()),
                   sites=rule.folder.all_site_ids(),
                   diff_text=make_diff_text({}, rule.to_web_api()),
                   object_ref=rule.object_ref())
コード例 #23
0
ファイル: rulesets.py プロジェクト: ricardolopez010/checkmk
    def edit_rule(self, orig_rule, rule):
        folder_rules = self._rules[orig_rule.folder.path()]
        index = folder_rules.index(orig_rule)

        folder_rules[index] = rule

        add_change("edit-rule",
                   _("Changed properties of rule #%d in ruleset \"%s\" in folder \"%s\"") %
                   (index, self.title(), rule.folder.alias_path()),
                   sites=rule.folder.all_site_ids(),
                   diff_text=make_diff_text(orig_rule.to_web_api(), rule.to_web_api()),
                   object_ref=rule.object_ref())
        self._on_change()
コード例 #24
0
ファイル: rulesets.py プロジェクト: ricardolopez010/checkmk
    def delete_rule(self, rule, create_change=True):
        folder_rules = self._rules[rule.folder.path()]
        index = folder_rules.index(rule)

        folder_rules.remove(rule)
        del self._rules_by_id[rule.id]

        if create_change:
            add_change("edit-rule",
                       _("Deleted rule #%d in ruleset \"%s\" in folder \"%s\"") %
                       (index, self.title(), rule.folder.alias_path()),
                       sites=rule.folder.all_site_ids(),
                       object_ref=rule.object_ref())
        self._on_change()
コード例 #25
0
ファイル: rulesets.py プロジェクト: m3rlinux/checkmk
    def clone_rule(self, orig_rule: Rule, rule: Rule) -> None:
        if rule.folder == orig_rule.folder:
            self.insert_rule_after(rule, orig_rule)
        else:
            self.append_rule(rule.folder, rule)

        add_change(
            "new-rule",
            _('Cloned rule from rule %s in ruleset "%s" in folder "%s"')
            % (orig_rule.id, self.title(), rule.folder.alias_path()),
            sites=rule.folder.all_site_ids(),
            diff_text=make_diff_text({}, rule.to_log()),
            object_ref=rule.object_ref(),
        )
コード例 #26
0
ファイル: simple_modes.py プロジェクト: LinuxHaus/checkmk
 def _add_change(
     self,
     *,
     action: str,
     text: str,
     affected_sites: Optional[List[SiteId]],
 ) -> None:
     """Add a WATO change entry for this object type modifications"""
     _changes.add_change(
         "%s-%s" % (action, self._mode_type.type_name()),
         text,
         domains=self._mode_type.affected_config_domains(),
         sites=affected_sites,
     )
コード例 #27
0
def _add_change(ident, new_password):
    if new_password:
        add_change(
            "add-password",
            f"Added the password {ident}",
            domains=[ConfigDomainCore],
            sites=None,
        )
    else:  # update or delete
        add_change(
            "edit-password",
            f"Edited the password '{ident}'",
            domains=[ConfigDomainCore],
            sites=None,
        )
コード例 #28
0
    def action(self) -> ActionResult:
        if not transactions.check_transaction():
            return redirect(self.mode_url())

        if not request.var("_delete"):
            return redirect(self.mode_url())

        delname = request.var("_delete")
        for index, attr in enumerate(self._attrs):
            if attr["name"] == delname:
                self._attrs.pop(index)
        save_custom_attrs_to_mk_file(self._all_attrs)
        self._update_config()
        _changes.add_change("edit-%sattrs" % self._type, _("Deleted attribute %s") % (delname))
        return redirect(self.mode_url())
コード例 #29
0
ファイル: tags.py プロジェクト: LinuxHaus/checkmk
    def _move_tag_group(self) -> ActionResult:  # pylint: disable=useless-return
        move_nr = request.get_integer_input_mandatory("_move")
        move_to = request.get_integer_input_mandatory("_index")

        moved = self._tag_config.tag_groups.pop(move_nr)
        self._tag_config.tag_groups.insert(move_to, moved)

        try:
            self._tag_config.validate_config()
        except MKGeneralException as e:
            raise MKUserError(None, "%s" % e)
        self._tag_config_file.save(self._tag_config.get_dict_format())
        self._load_effective_config()
        _changes.add_change("edit-tags", _("Changed order of tag groups"))
        return None
コード例 #30
0
ファイル: rulesets.py プロジェクト: m3rlinux/checkmk
    def edit_rule(self, orig_rule: Rule, rule: Rule) -> None:
        folder_rules = self._rules[orig_rule.folder.path()]
        index = folder_rules.index(orig_rule)

        folder_rules[index] = rule

        add_change(
            "edit-rule",
            _('Changed properties of rule #%d in ruleset "%s" in folder "%s"')
            % (index, self.title(), rule.folder.alias_path()),
            sites=rule.folder.all_site_ids(),
            diff_text=make_diff_text(orig_rule.to_log(), rule.to_log()),
            object_ref=rule.object_ref(),
        )
        self._on_change()