feat(api): add builtin attributes (#631)

This commit is contained in:
pycook 2024-10-22 18:21:07 +08:00 committed by GitHub
parent 5d28c28023
commit 4a3c21eec4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 89 additions and 27 deletions

View File

@ -1,14 +1,13 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import click
import copy import copy
import datetime import datetime
import json import json
import requests
import time import time
import uuid import uuid
import click
import requests
from flask import current_app from flask import current_app
from flask.cli import with_appcontext from flask.cli import with_appcontext
from flask_login import login_user from flask_login import login_user
@ -37,11 +36,14 @@ from api.lib.secrets.secrets import InnerKVManger
from api.models.acl import App from api.models.acl import App
from api.models.acl import ResourceType from api.models.acl import ResourceType
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import AttributeHistory
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
from api.models.cmdb import CIType from api.models.cmdb import CIType
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.models.cmdb import OperationRecord
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
from api.tasks.cmdb import batch_ci_cache
@click.command() @click.command()
@ -557,5 +559,20 @@ def cmdb_patch(version):
existed.update(option=option, commit=False) existed.update(option=option, commit=False)
db.session.commit() db.session.commit()
if version >= "2.4.14": # update ci columns: updated_at and updated_by
ci_ids = []
for i in CI.get_by(only_query=True).filter(CI.updated_at.is_(None)):
hist = AttributeHistory.get_by(ci_id=i.id, only_query=True).order_by(AttributeHistory.id.desc()).first()
if hist is not None:
record = OperationRecord.get_by_id(hist.record_id)
if record is not None:
u = UserCache.get(record.uid)
i.update(updated_at=record.created_at, updated_by=u and u.nickname, flush=True)
ci_ids.append(i.id)
db.session.commit()
batch_ci_cache.apply_async(args=(ci_ids,))
except Exception as e: except Exception as e:
print("cmdb patch failed: {}".format(e)) print("cmdb patch failed: {}".format(e))

View File

@ -45,6 +45,7 @@ from api.lib.notify import notify_send
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.perm.acl.cache import UserCache
from api.lib.secrets.inner import InnerCrypt from api.lib.secrets.inner import InnerCrypt
from api.lib.secrets.vault import VaultClient from api.lib.secrets.vault import VaultClient
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
@ -206,6 +207,8 @@ class CIManager(object):
res['_type'] = ci_type.id res['_type'] = ci_type.id
res['ci_type_alias'] = ci_type.alias res['ci_type_alias'] = ci_type.alias
res['_id'] = ci_id res['_id'] = ci_id
res['_updated_at'] = str(ci.updated_at)
res['_updated_by'] = ci.updated_by
return res return res
@ -581,6 +584,9 @@ class CIManager(object):
else: else:
ci_relation_add(ref_ci_dict, ci.id) ci_relation_add(ref_ci_dict, ci.id)
u = UserCache.get(current_user.uid)
ci.update(updated_at=now, updated_by=u and u.nickname)
@staticmethod @staticmethod
def update_unique_value(ci_id, unique_name, unique_value): 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))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))

View File

@ -1,6 +1,8 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from flask_babel import lazy_gettext as _l
from api.lib.utils import BaseEnum from api.lib.utils import BaseEnum
@ -110,17 +112,23 @@ class ExecuteStatusEnum(BaseEnum):
FAILED = '1' FAILED = '1'
RUNNING = '2' RUNNING = '2'
class RelationSourceEnum(BaseEnum): class RelationSourceEnum(BaseEnum):
ATTRIBUTE_VALUES = "0" ATTRIBUTE_VALUES = "0"
AUTO_DISCOVERY = "1" AUTO_DISCOVERY = "1"
BUILTIN_ATTRIBUTES = {
"_updated_at": _l("Update Time"),
"_updated_by": _l("Updated By"),
}
CMDB_QUEUE = "one_cmdb_async" CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION" REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2" REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id'} BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id', *BUILTIN_ATTRIBUTES.keys()}
L_TYPE = None L_TYPE = None
L_CI = None L_CI = None

View File

@ -16,6 +16,7 @@ from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
@ -24,7 +25,6 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.exception import AbortException from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeGroup from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
@ -136,17 +136,24 @@ class PreferenceManager(object):
_type = CITypeCache.get(type_id) _type = CITypeCache.get(type_id)
type_id = _type and _type.id type_id = _type and _type.id
attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join( # attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join(
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter( # CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( # PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.type_id == type_id).filter( # PreferenceShowAttributes.type_id == type_id).filter(
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by( # PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
CITypeAttribute.attr_id).all() # CITypeAttribute.attr_id).all()
attrs = PreferenceShowAttributes.get_by(uid=current_user.uid, type_id=type_id, to_dict=False)
result = [] result = []
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order): for i in sorted(attrs, key=lambda x: x.order):
item = i.PreferenceShowAttributes.attr.to_dict() if i.attr_id:
item.update(dict(is_fixed=i.PreferenceShowAttributes.is_fixed)) item = i.attr.to_dict()
elif i.builtin_attr:
item = dict(name=i.builtin_attr, alias=BUILTIN_ATTRIBUTES[i.builtin_attr])
else:
item = dict(name="", alias="")
item.update(dict(is_fixed=i.is_fixed))
result.append(item) result.append(item)
is_subscribed = True is_subscribed = True
@ -155,10 +162,14 @@ class PreferenceManager(object):
choice_web_hook_parse=False, choice_web_hook_parse=False,
choice_other_parse=False) choice_other_parse=False)
result = [i for i in result if i['default_show']] result = [i for i in result if i['default_show']]
for i in BUILTIN_ATTRIBUTES:
result.append(dict(name=i, alias=BUILTIN_ATTRIBUTES[i]))
is_subscribed = False is_subscribed = False
for i in result: for i in result:
if i["is_choice"]: if i.get("is_choice"):
i.update(dict(choice_value=AttributeManager.get_choice_values( i.update(dict(choice_value=AttributeManager.get_choice_values(
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other")))) i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
@ -172,24 +183,34 @@ class PreferenceManager(object):
_attr, is_fixed = x _attr, is_fixed = x
else: else:
_attr, is_fixed = x, False _attr, is_fixed = x, False
attr = AttributeCache.get(_attr) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
if _attr in BUILTIN_ATTRIBUTES:
attr = None
builtin_attr = _attr
else:
attr = AttributeCache.get(_attr) or abort(
404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
builtin_attr = None
existed = PreferenceShowAttributes.get_by(type_id=type_id, existed = PreferenceShowAttributes.get_by(type_id=type_id,
uid=current_user.uid, uid=current_user.uid,
attr_id=attr.id, attr_id=attr and attr.id,
builtin_attr=builtin_attr,
first=True, first=True,
to_dict=False) to_dict=False)
if existed is None: if existed is None:
PreferenceShowAttributes.create(type_id=type_id, PreferenceShowAttributes.create(type_id=type_id,
uid=current_user.uid, uid=current_user.uid,
attr_id=attr.id, attr_id=attr and attr.id,
builtin_attr=builtin_attr,
order=order, order=order,
is_fixed=is_fixed) is_fixed=is_fixed)
else: else:
existed.update(order=order, is_fixed=is_fixed) existed.update(order=order, is_fixed=is_fixed)
attr_dict = {int(i[0]) if isinstance(i, list) else int(i): j for i, j in attr_order} attr_dict = {(int(i[0]) if i[0].isdigit() else i[0]) if isinstance(i, list) else
(int(i) if i.isdigit() else i): j for i, j in attr_order}
for i in existed_all: for i in existed_all:
if i.attr_id not in attr_dict: if (i.attr_id and i.attr_id not in attr_dict) or (i.builtin_attr and i.builtin_attr not in attr_dict):
i.soft_delete() i.soft_delete()
if not existed_all and attr_order: if not existed_all and attr_order:

View File

@ -15,6 +15,7 @@ from api.extensions import db
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
@ -304,13 +305,20 @@ class Search(object):
(self.page - 1) * self.count, sort_type, self.count)) (self.page - 1) * self.count, sort_type, self.count))
def __sort_by_field(self, field, sort_type, query_sql): def __sort_by_field(self, field, sort_type, query_sql):
if field not in BUILTIN_ATTRIBUTES:
attr = AttributeCache.get(field) attr = AttributeCache.get(field)
attr_id = attr.id attr_id = attr.id
table_name = TableMap(attr=attr).table_name table_name = TableMap(attr=attr).table_name
_v_query_sql = """SELECT {0}.ci_id, {1}.value _v_query_sql = """SELECT ALIAS.ci_id, {0}.value
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id FROM ({1}) AS ALIAS INNER JOIN {0} ON {0}.ci_id = ALIAS.ci_id
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id) WHERE {0}.attr_id = {2}""".format(table_name, query_sql, attr_id)
new_table = _v_query_sql
else:
_v_query_sql = """SELECT c_cis.id AS ci_id, c_cis.{0} AS value
FROM c_cis INNER JOIN ({1}) AS ALIAS ON ALIAS.ci_id = c_cis.id""".format(
field[1:], query_sql)
new_table = _v_query_sql new_table = _v_query_sql
if self.only_type_query or not self.type_id_list or self.multi_type_has_ci_filter: if self.only_type_query or not self.type_id_list or self.multi_type_has_ci_filter:

View File

@ -253,6 +253,7 @@ class CI(Model):
status = db.Column(db.Enum(*CIStatusEnum.all(), name="status")) status = db.Column(db.Enum(*CIStatusEnum.all(), name="status"))
heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now()) heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
is_auto_discovery = db.Column('a', db.Boolean, default=False) is_auto_discovery = db.Column('a', db.Boolean, default=False)
updated_by = db.Column(db.String(64))
ci_type = db.relationship("CIType", backref="c_cis.type_id") ci_type = db.relationship("CIType", backref="c_cis.type_id")
@ -534,6 +535,7 @@ class CustomDashboard(Model):
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id')) type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id')) attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'))
builtin_attr = db.Column(db.String(256), nullable=True)
level = db.Column(db.Integer) level = db.Column(db.Integer)
options = db.Column(db.JSON) options = db.Column(db.JSON)