From dc77bca17c8ce9b1719d3c08819e8fa410093f9f Mon Sep 17 00:00:00 2001 From: pycook Date: Fri, 7 Jun 2024 10:29:32 +0800 Subject: [PATCH] feat: dynamic attribute (#534) --- cmdb-api/api/lib/cmdb/ci.py | 40 ++++---- cmdb-api/api/lib/cmdb/ci_type.py | 4 +- cmdb-api/api/lib/cmdb/topology.py | 2 +- cmdb-api/api/lib/perm/acl/acl.py | 3 + cmdb-api/api/lib/perm/acl/permission.py | 20 +++- cmdb-api/api/lib/perm/auth.py | 4 +- cmdb-api/api/models/cmdb.py | 1 + cmdb-ui/src/modules/cmdb/lang/en.js | 6 +- cmdb-ui/src/modules/cmdb/lang/zh.js | 10 +- .../cmdb/views/ci/modules/MetadataDrawer.vue | 6 ++ .../cmdb/views/ci_types/attributeCard.vue | 4 + .../cmdb/views/ci_types/attributeEditForm.vue | 92 ++++++++++++------ .../cmdb/views/ci_types/ceateNewAttribute.vue | 95 +++++++++++++------ .../cmdb/views/ci_types/ciTypedetail.vue | 12 +-- .../cmdb/views/custom_dashboard/chartForm.vue | 2 +- 15 files changed, 205 insertions(+), 96 deletions(-) diff --git a/cmdb-api/api/lib/cmdb/ci.py b/cmdb-api/api/lib/cmdb/ci.py index f7e2c90..588791e 100644 --- a/cmdb-api/api/lib/cmdb/ci.py +++ b/cmdb-api/api/lib/cmdb/ci.py @@ -383,12 +383,12 @@ class CIManager(object): computed_attrs.append(attr.to_dict()) elif attr.is_password: if attr.name in ci_dict: - password_dict[attr.id] = ci_dict.pop(attr.name) + password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic) elif attr.alias in ci_dict: - password_dict[attr.id] = ci_dict.pop(attr.alias) + password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic) if attr.re_check and password_dict.get(attr.id): - value_manager.check_re(attr.re_check, password_dict[attr.id]) + value_manager.check_re(attr.re_check, password_dict[attr.id][0]) if computed_attrs: value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) @@ -421,7 +421,8 @@ class CIManager(object): operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD try: ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery) - record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id) + record_id, has_dynamic = value_manager.create_or_update_attr_value( + ci, ci_dict, key2attr, ticket_id=ticket_id) except BadRequest as e: if existed is None: cls.delete(ci.id) @@ -431,7 +432,7 @@ class CIManager(object): for attr_id in password_dict: record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id) - if record_id: # has change + if record_id or has_dynamic: # has change ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE) if ref_ci_dict: # add relations @@ -440,7 +441,6 @@ class CIManager(object): return ci.id def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict): - current_app.logger.info((ci_id, ci_dict, __sync)) now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') ci = self.confirm_ci_existed(ci_id) @@ -465,12 +465,12 @@ class CIManager(object): computed_attrs.append(attr.to_dict()) elif attr.is_password: if attr.name in ci_dict: - password_dict[attr.id] = ci_dict.pop(attr.name) + password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic) elif attr.alias in ci_dict: - password_dict[attr.id] = ci_dict.pop(attr.alias) + password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic) if attr.re_check and password_dict.get(attr.id): - value_manager.check_re(attr.re_check, password_dict[attr.id]) + value_manager.check_re(attr.re_check, password_dict[attr.id][0]) if computed_attrs: value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci) @@ -495,7 +495,8 @@ class CIManager(object): ci_dict.pop(k) try: - record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id) + record_id, has_dynamic = value_manager.create_or_update_attr_value( + ci, ci_dict, key2attr, ticket_id=ticket_id) except BadRequest as e: raise e @@ -503,25 +504,25 @@ class CIManager(object): for attr_id in password_dict: record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id) - if record_id: # has change + if record_id or has_dynamic: # has change if not __sync: ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE) else: - ci_cache((ci_id, OperateType.UPDATE, record_id)) + ci_cache(ci_id, OperateType.UPDATE, record_id) ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k} if ref_ci_dict: if not __sync: ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE) else: - ci_relation_add((ref_ci_dict, ci.id)) + ci_relation_add(ref_ci_dict, ci.id) @staticmethod def update_unique_value(ci_id, unique_name, unique_value): ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) key2attr = {unique_name: AttributeCache.get(unique_name)} - record_id = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr) + record_id, _ = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr) ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE) @@ -761,6 +762,7 @@ class CIManager(object): @classmethod def save_password(cls, ci_id, attr_id, value, record_id, type_id): + value, is_dynamic = value changed = None encrypt_value = None value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD] @@ -777,14 +779,18 @@ class CIManager(object): if existed is None: if value: value_table.create(ci_id=ci_id, attr_id=attr_id, value=encrypt_value) - changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)] + if not is_dynamic: + changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)] elif existed.value != encrypt_value: if value: existed.update(ci_id=ci_id, attr_id=attr_id, value=encrypt_value) - changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW, PASSWORD_DEFAULT_SHOW, type_id)] + if not is_dynamic: + changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW, + PASSWORD_DEFAULT_SHOW, type_id)] else: existed.delete() - changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)] + if not is_dynamic: + changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)] if current_app.config.get('SECRETS_ENGINE') == 'vault': vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN')) diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index e3d3055..b05bbda 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -856,7 +856,9 @@ class CITypeRelationManager(object): if ci_type is None: return nodes, edges - nodes.append(ci_type.to_dict()) + ci_type_dict = ci_type.to_dict() + ci_type_dict.setdefault('level', [0]) + nodes.append(ci_type_dict) node_ids.add(ci_type.id) def _find(_id, lv): diff --git a/cmdb-api/api/lib/cmdb/topology.py b/cmdb-api/api/lib/cmdb/topology.py index 134f00c..103d2ee 100644 --- a/cmdb-api/api/lib/cmdb/topology.py +++ b/cmdb-api/api/lib/cmdb/topology.py @@ -172,6 +172,7 @@ class TopologyViewManager(object): _type = CITypeCache.get(central_node_type) if not _type: return dict(nodes=nodes, links=links) + type2meta = {_type.id: _type.icon} root_ids = [] show_key = AttributeCache.get(_type.show_id or _type.unique_id) @@ -192,7 +193,6 @@ class TopologyViewManager(object): prefix = REDIS_PREFIX_CI_RELATION key = list(map(str, root_ids)) id2node = {} - type2meta = {} for level in sorted([i for i in path.keys() if int(i) > 0]): type_ids = {int(i) for i in path[level]} diff --git a/cmdb-api/api/lib/perm/acl/acl.py b/cmdb-api/api/lib/perm/acl/acl.py index a829401..8591f49 100644 --- a/cmdb-api/api/lib/perm/acl/acl.py +++ b/cmdb-api/api/lib/perm/acl/acl.py @@ -253,6 +253,9 @@ def is_app_admin(app=None): if app is None: return False + if hasattr(current_user, 'username') and current_user.username == 'worker': + return True + app_id = app.id if 'acl_admin' in session.get("acl", {}).get("parentRoles", []): return True diff --git a/cmdb-api/api/lib/perm/acl/permission.py b/cmdb-api/api/lib/perm/acl/permission.py index 4ca3518..ce996fa 100644 --- a/cmdb-api/api/lib/perm/acl/permission.py +++ b/cmdb-api/api/lib/perm/acl/permission.py @@ -79,7 +79,8 @@ class PermissionCRUD(object): return r and cls.get_all(r.id) @staticmethod - def grant(rid, perms, resource_id=None, group_id=None, rebuild=True, source=AuditOperateSource.acl): + def grant(rid, perms, resource_id=None, group_id=None, rebuild=True, + source=AuditOperateSource.acl, force_update=False): app_id = None rt_id = None @@ -106,8 +107,23 @@ class PermissionCRUD(object): if not perms: perms = [i.get('name') for i in ResourceTypeCRUD.get_perms(group.resource_type_id)] - _role_permissions = [] + if force_update: + revoke_role_permissions = [] + existed_perms = RolePermission.get_by(rid=rid, + app_id=app_id, + group_id=group_id, + resource_id=resource_id, + to_dict=False) + for role_perm in existed_perms: + perm = PermissionCache.get(role_perm.perm_id, rt_id) + if perm and perm.name not in perms: + role_perm.soft_delete() + revoke_role_permissions.append(role_perm) + AuditCRUD.add_permission_log(app_id, AuditOperateType.revoke, rid, rt_id, + revoke_role_permissions, source=source) + + _role_permissions = [] for _perm in set(perms): perm = PermissionCache.get(_perm, rt_id) if not perm: diff --git a/cmdb-api/api/lib/perm/auth.py b/cmdb-api/api/lib/perm/auth.py index c00f8b0..f2c66e7 100644 --- a/cmdb-api/api/lib/perm/auth.py +++ b/cmdb-api/api/lib/perm/auth.py @@ -51,12 +51,12 @@ def _auth_with_key(): user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path) if user and authenticated: login_user(user) - reset_session(user) + # reset_session(user) return True role, authenticated = Role.query.authenticate_with_key(key, secret, req_args, path) if role and authenticated: - reset_session(None, role=role.name) + # reset_session(None, role=role.name) return True return False diff --git a/cmdb-api/api/models/cmdb.py b/cmdb-api/api/models/cmdb.py index 64a69aa..d372057 100644 --- a/cmdb-api/api/models/cmdb.py +++ b/cmdb-api/api/models/cmdb.py @@ -104,6 +104,7 @@ class Attribute(Model): is_link = db.Column(db.Boolean, default=False) is_password = db.Column(db.Boolean, default=False) is_sortable = db.Column(db.Boolean, default=False) + is_dynamic = db.Column(db.Boolean, default=False) default = db.Column(db.JSON) # {"default": None} diff --git a/cmdb-ui/src/modules/cmdb/lang/en.js b/cmdb-ui/src/modules/cmdb/lang/en.js index 539b0a5..c47f924 100644 --- a/cmdb-ui/src/modules/cmdb/lang/en.js +++ b/cmdb-ui/src/modules/cmdb/lang/en.js @@ -200,7 +200,9 @@ const cmdb_en = { show: 'show attribute', setAsShow: 'Set as show attribute', cancelSetAsShow: 'Cancel show attribute', - showTips: 'The names of nodes in the service tree and topology view' + showTips: 'The names of nodes in the service tree and topology view', + isDynamic: 'Dynamic', + dynamicTips: 'For example, for monitoring data and frequently updated data, it is recommended to set it as a dynamic attribute, so that the change history of the attribute will not be recorded.', }, components: { unselectAttributes: 'Unselected', @@ -538,7 +540,7 @@ if __name__ == "__main__": rollbackSuccess: 'Rollback successfully', rollbackingTips: 'Rollbacking', batchRollbacking: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed', - baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID and password attributes do not support', + baselineTips: 'Changes at this point in time will also be rollbacked, Unique ID, password and dynamic attributes do not support', }, serviceTree: { remove: 'Remove', diff --git a/cmdb-ui/src/modules/cmdb/lang/zh.js b/cmdb-ui/src/modules/cmdb/lang/zh.js index 3daf592..ca5d25a 100644 --- a/cmdb-ui/src/modules/cmdb/lang/zh.js +++ b/cmdb-ui/src/modules/cmdb/lang/zh.js @@ -78,7 +78,7 @@ const cmdb_zh = { password: '密码', link: '链接', list: '多值', - listTips: '字段的值是1个或者多个,接口返回的值的类型是list', + listTips: '属性的值是1个或者多个,接口返回的值的类型是list', computeForAllCITips: '所有CI触发计算', confirmcomputeForAllCITips: '确认触发所有CI的计算?', isUnique: '是否唯一', @@ -89,7 +89,7 @@ const cmdb_zh = { isSortable: '可排序', isIndex: '是否索引', index: '索引', - indexTips: '字段可被用于检索,加速查询', + indexTips: '属性可被用于全文检索,加速查询', confirmDelete: '确认删除【{name}】?', confirmDelete2: '确认删除?', computeSuccess: '触发成功!', @@ -200,7 +200,9 @@ const cmdb_zh = { show: '展示属性', setAsShow: '设置为展示属性', cancelSetAsShow: '取消设置为展示属性', - showTips: '服务树和拓扑视图里节点的名称' + showTips: '服务树和拓扑视图里节点的名称', + isDynamic: '动态属性', + dynamicTips: '譬如监控类的数据, 频繁更新的数据, 建议设置为动态属性, 则不会记录该属性的变更历史', }, components: { unselectAttributes: '未选属性', @@ -538,7 +540,7 @@ if __name__ == "__main__": rollbackSuccess: '回滚成功', rollbackingTips: '正在批量回滚中', batchRollbacking: '正在回滚,共{total}个,成功{successNum}个,失败{errorNum}个', - baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性不支持回滚', + baselineTips: '该时间点的变更也会被回滚, 唯一标识、密码属性、动态属性不支持回滚', }, serviceTree: { remove: '移除', diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/MetadataDrawer.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/MetadataDrawer.vue index 42cbdc8..ff4f0df 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/MetadataDrawer.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/MetadataDrawer.vue @@ -163,6 +163,12 @@ export default { width: 110, help: this.$t('cmdb.ci.tips10'), }, + { + field: 'is_dynamic', + title: this.$t('cmdb.ciType.isDynamic'), + width: 110, + help: this.$t('cmdb.ciType.dynamicTips'), + }, ] }, }, diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue index a26422a..ca2b520 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue @@ -180,6 +180,10 @@ export default { label: this.$t('cmdb.ciType.isIndex'), property: 'is_index', }, + { + label: this.$t('cmdb.ciType.isDynamic'), + property: 'is_dynamic', + }, ] }, inherited() { diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue index eca7735..ad6d2bb 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue @@ -157,33 +157,6 @@ - - - - - - - - - - {{ $t('cmdb.ciType.index') }} + + + + + + + + + + +