From ebce839eaa50eed0201f140f6a4ea9ee03b89aed Mon Sep 17 00:00:00 2001 From: pycook Date: Mon, 11 Sep 2023 19:15:31 +0800 Subject: [PATCH 01/12] fix upload template and add /api/v0.1/attributes//calc_computed_attribute --- cmdb-api/api/lib/cmdb/attribute.py | 6 ++++-- cmdb-api/api/lib/cmdb/ci_type.py | 32 +++++++++++++++++----------- cmdb-api/api/views/cmdb/attribute.py | 12 +++++------ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/cmdb-api/api/lib/cmdb/attribute.py b/cmdb-api/api/lib/cmdb/attribute.py index be0654c..f80927a 100644 --- a/cmdb-api/api/lib/cmdb/attribute.py +++ b/cmdb-api/api/lib/cmdb/attribute.py @@ -163,13 +163,15 @@ class AttributeManager(object): if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin('cmdb'): return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG)) - @staticmethod - def calc_computed_attribute(attr_id): + @classmethod + def calc_computed_attribute(cls, attr_id): """ calculate computed attribute for all ci :param attr_id: :return: """ + cls.can_create_computed_attribute() + from api.tasks.cmdb import calc_computed_attribute calc_computed_attribute.apply_async(args=(attr_id, current_user.uid), queue=CMDB_QUEUE) diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index 0f28d97..349d5ee 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding:utf-8 -*- import copy import datetime @@ -114,7 +114,7 @@ class CITypeManager(object): @kwargs_required("name") def add(cls, **kwargs): - unique_key = kwargs.pop("unique_key", None) + unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None) unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define) kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"] @@ -276,10 +276,10 @@ class CITypeGroupManager(object): def update(gid, name, type_ids): """ update part - :param gid: - :param name: - :param type_ids: - :return: + :param gid: + :param name: + :param type_ids: + :return: """ existed = CITypeGroup.get_by_id(gid) or abort( 404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid))) @@ -386,10 +386,10 @@ class CITypeAttributeManager(object): def add(cls, type_id, attr_ids=None, **kwargs): """ add attributes to CIType - :param type_id: + :param type_id: :param attr_ids: list - :param kwargs: - :return: + :param kwargs: + :return: """ attr_ids = list(set(attr_ids)) @@ -416,9 +416,9 @@ class CITypeAttributeManager(object): def update(cls, type_id, attributes): """ update attributes to CIType - :param type_id: + :param type_id: :param attributes: list - :return: + :return: """ cls._check(type_id, [i.get('attr_id') for i in attributes]) @@ -446,9 +446,9 @@ class CITypeAttributeManager(object): def delete(cls, type_id, attr_ids=None): """ delete attributes from CIType - :param type_id: + :param type_id: :param attr_ids: list - :return: + :return: """ from api.tasks.cmdb import ci_cache @@ -823,6 +823,12 @@ class CITypeTemplateManager(object): for added_id in set(id2obj_dicts.keys()) - set(existed_ids): if cls == CIType: CITypeManager.add(**id2obj_dicts[added_id]) + elif cls == CITypeRelation: + CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'), + id2obj_dicts[added_id].get('child_id'), + id2obj_dicts[added_id].get('relation_type_id'), + id2obj_dicts[added_id].get('constraint'), + ) else: cls.create(flush=True, **id2obj_dicts[added_id]) diff --git a/cmdb-api/api/views/cmdb/attribute.py b/cmdb-api/api/views/cmdb/attribute.py index 2a3edf2..fffbcdc 100644 --- a/cmdb-api/api/views/cmdb/attribute.py +++ b/cmdb-api/api/views/cmdb/attribute.py @@ -56,12 +56,7 @@ class AttributeView(APIView): @args_required("name") @args_validate(AttributeManager.cls) - def post(self, attr_id=None): - if request.url.endswith("/calc_computed_attribute"): - AttributeManager.calc_computed_attribute(attr_id) - - return self.jsonify(attr_id=attr_id) - + def post(self): choice_value = handle_arg_list(request.values.get("choice_value")) params = request.values params["choice_value"] = choice_value @@ -74,6 +69,11 @@ class AttributeView(APIView): @args_validate(AttributeManager.cls) def put(self, attr_id): + if request.url.endswith("/calc_computed_attribute"): + AttributeManager.calc_computed_attribute(attr_id) + + return self.jsonify(attr_id=attr_id) + choice_value = handle_arg_list(request.values.get("choice_value")) params = request.values params["choice_value"] = choice_value From 3cb78f30d24b522e885c031256b7f85ff45c83d1 Mon Sep 17 00:00:00 2001 From: wang-liang0615 <53748875+wang-liang0615@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:16:05 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=B1=9E=E6=80=A7=20?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E8=AE=A1=E7=AE=97=20(#174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmdb-ui/src/modules/cmdb/api/CITypeAttr.js | 7 ++++ .../cmdb/views/ci_types/attributeCard.vue | 17 +++++++- .../cmdb/views/ci_types/attributeEditForm.vue | 39 ++++++++++++------- .../cmdb/views/ci_types/computedArea.vue | 22 +++++++++++ 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js b/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js index dd8aa99..3720c52 100644 --- a/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js +++ b/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js @@ -153,3 +153,10 @@ export function canDefineComputed() { method: 'HEAD', }) } + +export function calcComputedAttribute(attr_id) { + return axios({ + url: `/v0.1/attributes/${attr_id}/calc_computed_attribute`, + method: 'PUT', + }) +} 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 b4ae78e..ab7144e 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue @@ -51,6 +51,9 @@ + + + @@ -59,7 +62,7 @@ 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 ab8fbfc..21a3e27 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeEditForm.vue @@ -10,7 +10,7 @@ :headerStyle="{ borderBottom: 'none' }" wrapClassName="attribute-edit-form" > - + 基础设置 @@ -343,7 +343,13 @@ name="is_password" v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]" /> - + @@ -353,7 +359,7 @@
取消 - 确定 + 确定
@@ -366,6 +372,7 @@ import { updateAttributeById, updateCITypeAttributesById, canDefineComputed, + calcComputedAttribute, } from '@/modules/cmdb/api/CITypeAttr' import { valueTypeMap } from '../../utils/const' import ComputedArea from './computedArea.vue' @@ -576,15 +583,14 @@ export default { }) }, - handleSubmit(e) { - e.preventDefault() - this.form.validateFields((err, values) => { + async handleSubmit(isCalcComputed = false) { + await this.form.validateFields(async (err, values) => { if (!err) { console.log('Received values of form: ', values) if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) { console.log('changed is_required') - updateCITypeAttributesById(this.CITypeId, { + await updateCITypeAttributesById(this.CITypeId, { attributes: [ { attr_id: this.record.id, is_required: values.is_required, default_show: values.default_show }, ], @@ -630,19 +636,21 @@ export default { const fontOptions = this.$refs.fontArea.getData() if (values.id) { - this.updateAttribute(values.id, { ...values, option: { fontOptions } }) + await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed) } else { // this.createAttribute(values) } } }) }, - updateAttribute(attrId, data) { - updateAttributeById(attrId, data).then((res) => { - this.$message.success(`更新成功`) - this.handleOk() - this.onClose() - }) + async updateAttribute(attrId, data, isCalcComputed = false) { + await updateAttributeById(attrId, data) + if (isCalcComputed) { + await calcComputedAttribute(attrId) + } + this.$message.success(`更新成功`) + this.handleOk() + this.onClose() }, handleOk() { this.$emit('ok') @@ -682,6 +690,9 @@ export default { default_value: key, }) }, + async handleCalcComputed() { + await this.handleSubmit(true) + }, }, watch: {}, } diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/computedArea.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/computedArea.vue index 0cc2e8e..0f06347 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/computedArea.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/computedArea.vue @@ -8,6 +8,14 @@ 代码 + @@ -25,6 +33,10 @@ export default { type: Boolean, default: true, }, + showCalcComputed: { + type: Boolean, + default: false, + } }, data() { return { @@ -62,6 +74,16 @@ export default { this.activeKey = 'expr' } }, + handleCalcComputed() { + const that = this + this.$confirm({ + title: '警告', + content: `确认触发将保存当前配置及触发所有CI的计算?`, + onOk() { + that.$emit('handleCalcComputed') + }, + }) + }, }, } From 68905281f2c27b83bf3a1d95ae60a96fe26316a1 Mon Sep 17 00:00:00 2001 From: pycook Date: Tue, 12 Sep 2023 20:00:56 +0800 Subject: [PATCH 03/12] Detect circular dependencies when adding CIType relationships --- cmdb-api/api/lib/cmdb/ci_type.py | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index 349d5ee..83b6f3e 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -3,9 +3,11 @@ import copy import datetime +import toposort from flask import abort from flask import current_app from flask_login import current_user +from toposort import toposort_flatten from api.extensions import db from api.lib.cmdb.attribute import AttributeManager @@ -370,6 +372,16 @@ class CITypeAttributeManager(object): return result + @staticmethod + def get_common_attributes(type_ids): + result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False) + attr2types = {} + for i in result: + attr2types.setdefault(i.attr_id, []).append(i.type_id) + + return [AttributeCache.get(attr_id).to_dict() for attr_id in attr2types + if len(attr2types[attr_id]) == len(type_ids)] + @staticmethod def _check(type_id, attr_ids): ci_type = CITypeManager.check_is_existed(type_id) @@ -564,6 +576,22 @@ class CITypeRelationManager(object): return [cls._wrap_relation_type_dict(child.child_id, child) for child in children] + @classmethod + def recursive_level2children(cls, parent_id): + result = dict() + + def get_children(_id, level): + children = CITypeRelation.get_by(parent_id=_id, to_dict=False) + result[level + 1] = [i.child.to_dict() for i in children] + + for i in children: + if i.child_id != _id: + get_children(i.child_id, level + 1) + + get_children(parent_id, 0) + + return result + @classmethod def get_parents(cls, child_id): parents = CITypeRelation.get_by(child_id=child_id, to_dict=False) @@ -586,6 +614,17 @@ class CITypeRelationManager(object): p = CITypeManager.check_is_existed(parent) c = CITypeManager.check_is_existed(child) + rels = {} + for i in CITypeRelation.get_by(to_dict=False): + rels.setdefault(i.child_id, set()).add(i.parent_id) + rels.setdefault(c.id, set()).add(p.id) + + try: + toposort_flatten(rels) + except toposort.CircularDependencyError as e: + current_app.logger.warning(str(e)) + return abort(400, ErrFormat.circular_dependency_error) + existed = cls._get(p.id, c.id) if existed is not None: existed.update(relation_type_id=relation_type_id, From 881b8cc1fa38c2a1584f1c5fa2c3df73644dcb3b Mon Sep 17 00:00:00 2001 From: pycook Date: Tue, 12 Sep 2023 20:01:30 +0800 Subject: [PATCH 04/12] cmdb-api/api/lib/resp_format.py --- cmdb-api/api/lib/resp_format.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmdb-api/api/lib/resp_format.py b/cmdb-api/api/lib/resp_format.py index e3cf4da..5a7c852 100644 --- a/cmdb-api/api/lib/resp_format.py +++ b/cmdb-api/api/lib/resp_format.py @@ -9,6 +9,8 @@ class CommonErrFormat(object): not_found = "不存在" + circular_dependency_error = "存在循环依赖!" + unknown_search_error = "未知搜索错误" invalid_json = "json格式似乎不正确了, 请仔细确认一下!" From 25ad2192fdf206ecc5782ef689a4c7693414a62e Mon Sep 17 00:00:00 2001 From: pycook Date: Fri, 15 Sep 2023 15:26:20 +0800 Subject: [PATCH 05/12] enhance dashboard --- cmdb-api/api/commands/click_cmdb.py | 4 + cmdb-api/api/lib/cmdb/cache.py | 162 ++++++++++++++++---- cmdb-api/api/lib/cmdb/custom_dashboard.py | 16 +- cmdb-api/api/lib/cmdb/query_sql.py | 2 +- cmdb-api/api/views/cmdb/ci_type.py | 11 +- cmdb-api/api/views/cmdb/ci_type_relation.py | 7 +- cmdb-api/api/views/cmdb/custom_dashboard.py | 20 ++- 7 files changed, 178 insertions(+), 44 deletions(-) diff --git a/cmdb-api/api/commands/click_cmdb.py b/cmdb-api/api/commands/click_cmdb.py index 1362806..6d41d98 100644 --- a/cmdb-api/api/commands/click_cmdb.py +++ b/cmdb-api/api/commands/click_cmdb.py @@ -9,6 +9,7 @@ import time import click from flask import current_app from flask.cli import with_appcontext +from flask_login import login_user import api.lib.cmdb.ci from api.extensions import db @@ -24,6 +25,7 @@ from api.lib.cmdb.const import ValueTypeEnum from api.lib.exception import AbortException from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.cache import AppCache +from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.resource import ResourceCRUD from api.lib.perm.acl.resource import ResourceTypeCRUD from api.lib.perm.acl.role import RoleCRUD @@ -207,6 +209,8 @@ def cmdb_counter(): """ from api.lib.cmdb.cache import CMDBCounterCache + current_app.test_request_context().push() + login_user(UserCache.get('worker')) while True: try: db.session.remove() diff --git a/cmdb-api/api/lib/cmdb/cache.py b/cmdb-api/api/lib/cmdb/cache.py index d166d42..bbc1c22 100644 --- a/cmdb-api/api/lib/cmdb/cache.py +++ b/cmdb-api/api/lib/cmdb/cache.py @@ -2,14 +2,11 @@ from __future__ import unicode_literals -import requests from flask import current_app from api.extensions import cache -from api.extensions import db from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.models.cmdb import Attribute -from api.models.cmdb import CI from api.models.cmdb import CIType from api.models.cmdb import CITypeAttribute from api.models.cmdb import RelationType @@ -210,7 +207,6 @@ class CITypeAttributeCache(object): @classmethod def get(cls, type_id, attr_id): - attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id)) attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id)) attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False) @@ -251,53 +247,72 @@ class CMDBCounterCache(object): result = {} for custom in customs: if custom['category'] == 0: - result[custom['id']] = cls.summary_counter(custom['type_id']) + res = cls.sum_counter(custom) elif custom['category'] == 1: - result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id']) - elif custom['category'] == 2: - result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level']) + res = cls.attribute_counter(custom) + else: + res = cls.relation_counter(custom.get('type_id'), + custom.get('level'), + custom.get('options', {}).get('filter', ''), + custom.get('options', {}).get('type_ids', '')) + + if res: + result[custom['id']] = res cls.set(result) return result @classmethod - def update(cls, custom): + def update(cls, custom, flush=True): result = cache.get(cls.KEY) or {} if not result: result = cls.reset() if custom['category'] == 0: - result[custom['id']] = cls.summary_counter(custom['type_id']) + res = cls.sum_counter(custom) elif custom['category'] == 1: - result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id']) - elif custom['category'] == 2: - result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level']) + res = cls.attribute_counter(custom) + else: + res = cls.relation_counter(custom.get('type_id'), + custom.get('level'), + custom.get('options', {}).get('filter', ''), + custom.get('options', {}).get('type_ids', '')) - cls.set(result) + if res and flush: + result[custom['id']] = res + cls.set(result) + + return res @staticmethod - def summary_counter(type_id): - return db.session.query(CI.id).filter(CI.deleted.is_(False)).filter(CI.type_id == type_id).count() + def relation_counter(type_id, level, other_filer, type_ids): + from api.lib.cmdb.search.ci_relation.search import Search as RelSearch + from api.lib.cmdb.search import SearchError + from api.lib.cmdb.search.ci import search - @staticmethod - def relation_counter(type_id, level): + query = "_type:{}".format(type_id) + s = search(query, count=1000000) + try: + type_names, _, _, _, _, _ = s.search() + except SearchError as e: + current_app.logger.error(e) + return - uri = current_app.config.get('CMDB_API') - - type_names = requests.get("{}/ci/s?q=_type:{}&count=10000".format(uri, type_id)).json().get('result') type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names] - url = "{}/ci_relations/statistics?root_ids={}&level={}".format( - uri, ','.join([i[0] for i in type_id_names]), level) - stats = requests.get(url).json() + s = RelSearch([i[0] for i in type_id_names], level, other_filer or '') + try: + stats = s.statistics(type_ids) + except SearchError as e: + current_app.logger.error(e) + return id2name = dict(type_id_names) type_ids = set() for i in (stats.get('detail') or []): for j in stats['detail'][i]: type_ids.add(j) - for type_id in type_ids: _type = CITypeCache.get(type_id) id2name[type_id] = _type and _type.alias @@ -317,9 +332,94 @@ class CMDBCounterCache(object): return result @staticmethod - def attribute_counter(type_id, attr_id): - uri = current_app.config.get('CMDB_API') - url = "{}/ci/s?q=_type:{}&fl={}&facet={}".format(uri, type_id, attr_id, attr_id) - res = requests.get(url).json() - if res.get('facet'): - return dict([i[:2] for i in list(res.get('facet').values())[0]]) + def attribute_counter(custom): + from api.lib.cmdb.search import SearchError + from api.lib.cmdb.search.ci import search + + custom.setdefault('options', {}) + type_id = custom.get('type_id') + attr_id = custom.get('attr_id') + type_ids = custom['options'].get('type_ids') or (type_id and [type_id]) + attr_ids = list(map(str, custom['options'].get('attr_ids') or (attr_id and [attr_id]))) + other_filter = custom['options'].get('filter') + other_filter = "({})".format(other_filter) if other_filter else '' + + if custom['options'].get('ret') == 'cis': + query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter) + s = search(query, fl=attr_ids, ret_key='alias', count=100) + try: + cis, _, _, _, _, _ = s.search() + except SearchError as e: + current_app.logger.error(e) + return + + return cis + + result = dict() + # level = 1 + query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter) + s = search(query, fl=attr_ids, facet=[attr_ids[0]], count=1) + try: + _, _, _, _, _, facet = s.search() + except SearchError as e: + current_app.logger.error(e) + return + for i in (list(facet.values()) or [[]])[0]: + result[i[0]] = i[1] + if len(attr_ids) == 1: + return result + + # level = 2 + for v in result: + query = "_type:({}),{},{}:{}".format(";".join(map(str, type_ids)), other_filter, attr_ids[0], v) + s = search(query, fl=attr_ids, facet=[attr_ids[1]], count=1) + try: + _, _, _, _, _, facet = s.search() + except SearchError as e: + current_app.logger.error(e) + return + result[v] = dict() + for i in (list(facet.values()) or [[]])[0]: + result[v][i[0]] = i[1] + + if len(attr_ids) == 2: + return result + + # level = 3 + for v1 in result: + if not isinstance(result[v1], dict): + continue + for v2 in result[v1]: + query = "_type:({}),{},{}:{},{}:{}".format(";".join(map(str, type_ids)), other_filter, + attr_ids[0], v1, attr_ids[1], v2) + s = search(query, fl=attr_ids, facet=[attr_ids[2]], count=1) + try: + _, _, _, _, _, facet = s.search() + except SearchError as e: + current_app.logger.error(e) + return + result[v1][v2] = dict() + for i in (list(facet.values()) or [[]])[0]: + result[v1][v2][i[0]] = i[1] + + return result + + @staticmethod + def sum_counter(custom): + from api.lib.cmdb.search import SearchError + from api.lib.cmdb.search.ci import search + + custom.setdefault('options', {}) + type_id = custom.get('type_id') + type_ids = custom['options'].get('type_ids') or (type_id and [type_id]) + other_filter = custom['options'].get('filter') or '' + + query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter) + s = search(query, count=1) + try: + _, _, _, _, numfound, _ = s.search() + except SearchError as e: + current_app.logger.error(e) + return + + return numfound diff --git a/cmdb-api/api/lib/cmdb/custom_dashboard.py b/cmdb-api/api/lib/cmdb/custom_dashboard.py index 8153eec..133769a 100644 --- a/cmdb-api/api/lib/cmdb/custom_dashboard.py +++ b/cmdb-api/api/lib/cmdb/custom_dashboard.py @@ -14,6 +14,14 @@ class CustomDashboardManager(object): def get(): return sorted(CustomDashboard.get_by(to_dict=True), key=lambda x: (x["category"], x['order'])) + @staticmethod + def preview(**kwargs): + from api.lib.cmdb.cache import CMDBCounterCache + + res = CMDBCounterCache.update(kwargs, flush=False) + + return res + @staticmethod def add(**kwargs): from api.lib.cmdb.cache import CMDBCounterCache @@ -23,9 +31,9 @@ class CustomDashboardManager(object): new = CustomDashboard.create(**kwargs) - CMDBCounterCache.update(new.to_dict()) + res = CMDBCounterCache.update(new.to_dict()) - return new + return new, res @staticmethod def update(_id, **kwargs): @@ -35,9 +43,9 @@ class CustomDashboardManager(object): new = existed.update(**kwargs) - CMDBCounterCache.update(new.to_dict()) + res = CMDBCounterCache.update(new.to_dict()) - return new + return new, res @staticmethod def batch_update(id2options): diff --git a/cmdb-api/api/lib/cmdb/query_sql.py b/cmdb-api/api/lib/cmdb/query_sql.py index c84fd64..f5c598b 100644 --- a/cmdb-api/api/lib/cmdb/query_sql.py +++ b/cmdb-api/api/lib/cmdb/query_sql.py @@ -42,7 +42,7 @@ FACET_QUERY1 = """ FACET_QUERY = """ SELECT {0}.value, - count({0}.ci_id) + count(distinct({0}.ci_id)) FROM {0} INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id WHERE {0}.attr_id={2:d} diff --git a/cmdb-api/api/views/cmdb/ci_type.py b/cmdb-api/api/views/cmdb/ci_type.py index ba2686f..8bb8f8b 100644 --- a/cmdb-api/api/views/cmdb/ci_type.py +++ b/cmdb-api/api/views/cmdb/ci_type.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding:utf-8 -*- import json @@ -154,9 +154,15 @@ class EnableCITypeView(APIView): class CITypeAttributeView(APIView): - url_prefix = ("/ci_types//attributes", "/ci_types//attributes") + url_prefix = ("/ci_types//attributes", "/ci_types//attributes", + "/ci_types/common_attributes") def get(self, type_id=None, type_name=None): + if request.path.endswith("/common_attributes"): + type_ids = handle_arg_list(request.values.get('type_ids')) + + return self.jsonify(attributes=CITypeAttributeManager.get_common_attributes(type_ids)) + t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found) type_id = t.id unique_id = t.unique_id @@ -500,3 +506,4 @@ class CITypeFilterPermissionView(APIView): @auth_with_app_token def get(self, type_id): return self.jsonify(CIFilterPermsCRUD().get(type_id)) + diff --git a/cmdb-api/api/views/cmdb/ci_type_relation.py b/cmdb-api/api/views/cmdb/ci_type_relation.py index 55d5d97..20ce323 100644 --- a/cmdb-api/api/views/cmdb/ci_type_relation.py +++ b/cmdb-api/api/views/cmdb/ci_type_relation.py @@ -19,9 +19,14 @@ from api.resource import APIView class GetChildrenView(APIView): - url_prefix = "/ci_type_relations//children" + url_prefix = ("/ci_type_relations//children", + "/ci_type_relations//recursive_level2children", + ) def get(self, parent_id): + if request.url.endswith("recursive_level2children"): + return self.jsonify(CITypeRelationManager.recursive_level2children(parent_id)) + return self.jsonify(children=CITypeRelationManager.get_children(parent_id)) diff --git a/cmdb-api/api/views/cmdb/custom_dashboard.py b/cmdb-api/api/views/cmdb/custom_dashboard.py index 4991ceb..1c20ff9 100644 --- a/cmdb-api/api/views/cmdb/custom_dashboard.py +++ b/cmdb-api/api/views/cmdb/custom_dashboard.py @@ -13,7 +13,8 @@ from api.resource import APIView class CustomDashboardApiView(APIView): - url_prefix = ("/custom_dashboard", "/custom_dashboard/", "/custom_dashboard/batch") + url_prefix = ("/custom_dashboard", "/custom_dashboard/", "/custom_dashboard/batch", + "/custom_dashboard/preview") def get(self): return self.jsonify(CustomDashboardManager.get()) @@ -21,17 +22,26 @@ class CustomDashboardApiView(APIView): @role_required(RoleEnum.CONFIG) @args_validate(CustomDashboardManager.cls) def post(self): - cm = CustomDashboardManager.add(**request.values) + if request.url.endswith("/preview"): + return self.jsonify(counter=CustomDashboardManager.preview(**request.values)) - return self.jsonify(cm.to_dict()) + cm, counter = CustomDashboardManager.add(**request.values) + + res = cm.to_dict() + res.update(counter=counter) + + return self.jsonify(res) @role_required(RoleEnum.CONFIG) @args_validate(CustomDashboardManager.cls) def put(self, _id=None): if _id is not None: - cm = CustomDashboardManager.update(_id, **request.values) + cm, counter = CustomDashboardManager.update(_id, **request.values) - return self.jsonify(cm.to_dict()) + res = cm.to_dict() + res.update(counter=counter) + + return self.jsonify(res) CustomDashboardManager.batch_update(request.values.get("id2options")) From 7f66f7688b6f2a412b4393e7f136b0dadcc5df3f Mon Sep 17 00:00:00 2001 From: simontigers <47096077+simontigers@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:30:30 +0800 Subject: [PATCH 06/12] feat: init resource for backend (#176) --- ...mon_setting.py => click_common_setting.py} | 55 ++++++++++++++++++- cmdb-api/api/lib/common_setting/acl.py | 20 +++++++ 2 files changed, 73 insertions(+), 2 deletions(-) rename cmdb-api/api/commands/{init_common_setting.py => click_common_setting.py} (77%) diff --git a/cmdb-api/api/commands/init_common_setting.py b/cmdb-api/api/commands/click_common_setting.py similarity index 77% rename from cmdb-api/api/commands/init_common_setting.py rename to cmdb-api/api/commands/click_common_setting.py index ea5afc9..e0fea63 100644 --- a/cmdb-api/api/commands/init_common_setting.py +++ b/cmdb-api/api/commands/click_common_setting.py @@ -161,6 +161,55 @@ class InitDepartment(object): info = f"update department acl_rid: {acl_rid}" current_app.logger.info(info) + def init_backend_resource(self): + acl = self.check_app('backend') + resources_types = acl.get_all_resources_types() + + results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups'])) + if len(results) == 0: + payload = dict( + app_id=acl.app_name, + name='操作权限', + description='', + perms=['read', 'grant', 'delete', 'update'] + ) + resource_type = acl.create_resources_type(payload) + else: + resource_type = results[0] + + for name in ['公司信息']: + payload = dict( + type_id=resource_type['id'], + app_id=acl.app_name, + name=name, + ) + try: + acl.create_resource(payload) + except Exception as e: + if '已经存在' in str(e): + pass + else: + raise Exception(e) + + def check_app(self, app_name): + acl = ACLManager(app_name) + payload = dict( + name=app_name, + description=app_name + ) + try: + app = acl.validate_app() + if app: + return acl + + acl.create_app(payload) + except Exception as e: + current_app.logger.error(e) + if '不存在' in str(e): + acl.create_app(payload) + return acl + raise Exception(e) + @click.command() @with_appcontext @@ -177,5 +226,7 @@ def init_department(): """ Department initialization """ - InitDepartment().init() - InitDepartment().create_acl_role_with_department() + cli = InitDepartment() + cli.init_wide_company() + cli.create_acl_role_with_department() + cli.init_backend_resource() diff --git a/cmdb-api/api/lib/common_setting/acl.py b/cmdb-api/api/lib/common_setting/acl.py index 163a373..99e1860 100644 --- a/cmdb-api/api/lib/common_setting/acl.py +++ b/cmdb-api/api/lib/common_setting/acl.py @@ -6,6 +6,7 @@ from api.lib.common_setting.resp_format import ErrFormat from api.lib.perm.acl.cache import RoleCache, AppCache from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD from api.lib.perm.acl.user import UserCRUD +from api.lib.perm.acl.resource import ResourceTypeCRUD, ResourceCRUD class ACLManager(object): @@ -94,3 +95,22 @@ class ACLManager(object): avatar=user_info.get('avatar')) return result + + def validate_app(self): + return AppCache.get(self.app_name) + + def get_all_resources_types(self, q=None, page=1, page_size=999999): + app_id = self.validate_app().id + numfound, res, id2perms = ResourceTypeCRUD.search(q, app_id, page, page_size) + + return dict( + numfound=numfound, + groups=[i.to_dict() for i in res], + id2perms=id2perms + ) + + def create_resource(self, payload): + payload['app_id'] = self.validate_app().id + resource = ResourceCRUD.add(**payload) + + return resource.to_dict() From 606be7f8e83feeeb4c4cf80601598d73ca6bb17b Mon Sep 17 00:00:00 2001 From: pycook Date: Fri, 15 Sep 2023 17:36:10 +0800 Subject: [PATCH 07/12] dashboard ui update --- .../src/components/CMDBFilterComp/index.vue | 26 +- cmdb-ui/src/modules/cmdb/api/CITypeAttr.js | 8 + .../src/modules/cmdb/api/CITypeRelation.js | 7 + .../src/modules/cmdb/api/customDashboard.js | 8 + .../cmdb/views/custom_dashboard/chart.vue | 179 ++++- .../cmdb/views/custom_dashboard/chartForm.vue | 735 +++++++++++++++--- .../views/custom_dashboard/chartOptions.js | 240 ++++-- .../custom_dashboard/colorListPicker.vue | 54 ++ .../views/custom_dashboard/colorPicker.vue | 79 ++ .../cmdb/views/custom_dashboard/constant.js | 5 +- .../views/custom_dashboard/customLayout.vue | 106 ++- 11 files changed, 1271 insertions(+), 176 deletions(-) create mode 100644 cmdb-ui/src/modules/cmdb/views/custom_dashboard/colorListPicker.vue create mode 100644 cmdb-ui/src/modules/cmdb/views/custom_dashboard/colorPicker.vue diff --git a/cmdb-ui/src/components/CMDBFilterComp/index.vue b/cmdb-ui/src/components/CMDBFilterComp/index.vue index 6c347a6..07c0456 100644 --- a/cmdb-ui/src/components/CMDBFilterComp/index.vue +++ b/cmdb-ui/src/components/CMDBFilterComp/index.vue @@ -68,7 +68,8 @@ export default { }, methods: { - visibleChange(open) { + visibleChange(open, isInitOne = true) { + // isInitOne 初始化exp为空时,ruleList是否默认给一条 // const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g const exp = this.expression.match(new RegExp(this.regQ, 'g')) ? this.expression.match(new RegExp(this.regQ, 'g'))[0] @@ -151,15 +152,20 @@ export default { }) this.ruleList = [...expArray] } else if (open) { - this.ruleList = [ - { - id: uuidv4(), - type: 'and', - property: this.canSearchPreferenceAttrList[0].name, - exp: 'is', - value: null, - }, - ] + this.ruleList = isInitOne + ? [ + { + id: uuidv4(), + type: 'and', + property: + this.canSearchPreferenceAttrList && this.canSearchPreferenceAttrList.length + ? this.canSearchPreferenceAttrList[0].name + : undefined, + exp: 'is', + value: null, + }, + ] + : [] } }, handleClear() { diff --git a/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js b/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js index 3720c52..1a963b1 100644 --- a/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js +++ b/cmdb-ui/src/modules/cmdb/api/CITypeAttr.js @@ -77,6 +77,14 @@ export function getCITypeAttributesByTypeIds(params) { }) } +export function getCITypeCommonAttributesByTypeIds(params) { + return axios({ + url: `/v0.1/ci_types/common_attributes`, + method: 'get', + params: params + }) +} + /** * 删除属性 * @param attrId diff --git a/cmdb-ui/src/modules/cmdb/api/CITypeRelation.js b/cmdb-ui/src/modules/cmdb/api/CITypeRelation.js index 20d4f5e..3aaf654 100644 --- a/cmdb-ui/src/modules/cmdb/api/CITypeRelation.js +++ b/cmdb-ui/src/modules/cmdb/api/CITypeRelation.js @@ -61,3 +61,10 @@ export function revokeTypeRelation(first_type_id, second_type_id, rid, data) { data }) } + +export function getRecursive_level2children(type_id) { + return axios({ + url: `/v0.1/ci_type_relations/${type_id}/recursive_level2children`, + method: 'GET' + }) +} diff --git a/cmdb-ui/src/modules/cmdb/api/customDashboard.js b/cmdb-ui/src/modules/cmdb/api/customDashboard.js index dcdd1f6..863609c 100644 --- a/cmdb-ui/src/modules/cmdb/api/customDashboard.js +++ b/cmdb-ui/src/modules/cmdb/api/customDashboard.js @@ -37,3 +37,11 @@ export function batchUpdateCustomDashboard(data) { data }) } + +export function postCustomDashboardPreview(data) { + return axios({ + url: '/v0.1/custom_dashboard/preview', + method: 'post', + data + }) +} diff --git a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chart.vue b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chart.vue index 0b896c2..8808e6d 100644 --- a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chart.vue +++ b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chart.vue @@ -1,11 +1,55 @@ @@ -127,12 +168,14 @@ export default { }, } }, - mounted() { - this.getLayout() + created() { getCITypes().then((res) => { this.ci_types = res.ci_types }) }, + mounted() { + this.getLayout() + }, methods: { async getLayout() { const res = await getCustomDashboard() @@ -196,6 +239,13 @@ export default { }) } }, + getCiType(item) { + if (item.type_id || item.options?.type_ids) { + const _find = this.ci_types.find((type) => type.id === item.type_id || type.id === item.options?.type_ids[0]) + return _find || null + } + return null + }, }, } @@ -206,15 +256,18 @@ export default { text-align: center; } .cmdb-dashboard-grid-item { - border-radius: 15px; + border-radius: 8px; + padding: 6px 12px; .cmdb-dashboard-grid-item-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; font-weight: 700; - padding-left: 6px; - color: #000000bd; + color: #000000; } .cmdb-dashboard-grid-item-operation { position: absolute; - right: 6px; + right: 12px; top: 6px; } .cmdb-dashboard-grid-item-chart-type { @@ -224,3 +277,26 @@ export default { } } + + From 1a7c3fcd21b63d9065fa872564c7915bccc07065 Mon Sep 17 00:00:00 2001 From: pycook Date: Fri, 15 Sep 2023 17:57:39 +0800 Subject: [PATCH 08/12] release v2.3.3 --- cmdb-api/settings.example.py | 2 - cmdb-ui/public/iconfont/demo_index.html | 313 +++++++++++++++++++++++- cmdb-ui/public/iconfont/iconfont.css | 60 ++++- cmdb-ui/public/iconfont/iconfont.js | 2 +- cmdb-ui/public/iconfont/iconfont.json | 93 ++++++- cmdb-ui/public/iconfont/iconfont.ttf | Bin 256772 -> 260520 bytes cmdb-ui/public/iconfont/iconfont.woff | Bin 143624 -> 145904 bytes cmdb-ui/public/iconfont/iconfont.woff2 | Bin 116812 -> 118832 bytes docker-compose.yml | 4 +- 9 files changed, 457 insertions(+), 17 deletions(-) diff --git a/cmdb-api/settings.example.py b/cmdb-api/settings.example.py index 84c34ad..0102322 100644 --- a/cmdb-api/settings.example.py +++ b/cmdb-api/settings.example.py @@ -94,5 +94,3 @@ ES_HOST = '127.0.0.1' USE_ES = False BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y'] - -CMDB_API = "http://127.0.0.1:5000/api/v0.1" diff --git a/cmdb-ui/public/iconfont/demo_index.html b/cmdb-ui/public/iconfont/demo_index.html index f7d3d47..2a0be2d 100644 --- a/cmdb-ui/public/iconfont/demo_index.html +++ b/cmdb-ui/public/iconfont/demo_index.html @@ -54,6 +54,84 @@