mirror of https://github.com/veops/cmdb.git
Merge branch 'develop' into i18n
This commit is contained in:
commit
cd0797e860
|
@ -38,6 +38,7 @@ pip-log.txt
|
||||||
.tox
|
.tox
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
cmdb-api/test-output
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
|
|
@ -23,6 +23,7 @@ from api.models.cmdb import CITypeGroupItem
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
from api.models.cmdb import PreferenceShowAttributes
|
from api.models.cmdb import PreferenceShowAttributes
|
||||||
from api.models.cmdb import PreferenceTreeView
|
from api.models.cmdb import PreferenceTreeView
|
||||||
|
from api.tasks.cmdb import ci_type_attribute_order_rebuild
|
||||||
|
|
||||||
|
|
||||||
class CITypeManager(object):
|
class CITypeManager(object):
|
||||||
|
@ -39,7 +40,9 @@ class CITypeManager(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_is_existed(key):
|
def check_is_existed(key):
|
||||||
return CITypeCache.get(key) or abort(404, "CIType <{0}> is not existed".format(key))
|
ci_type = CITypeCache.get(key) or abort(404, "CIType <{0}> is not existed".format(key))
|
||||||
|
|
||||||
|
return CIType.get_by_id(ci_type.id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ci_types(type_name=None):
|
def get_ci_types(type_name=None):
|
||||||
|
@ -55,16 +58,35 @@ class CITypeManager(object):
|
||||||
ci_type = CITypeCache.get(_type) or abort(404, "CIType <{0}> is not found".format(_type))
|
ci_type = CITypeCache.get(_type) or abort(404, "CIType <{0}> is not found".format(_type))
|
||||||
return ci_type.to_dict()
|
return ci_type.to_dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_unique(type_id=None, name=None, alias=None):
|
||||||
|
if name is not None:
|
||||||
|
ci_type = CIType.get_by(name=name, first=True, to_dict=False)
|
||||||
|
elif alias is not None:
|
||||||
|
ci_type = CIType.get_by(alias=alias, first=True, to_dict=False)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ci_type:
|
||||||
|
return
|
||||||
|
|
||||||
|
if type_id is not None and ci_type.id != type_id:
|
||||||
|
return abort(400, "CIType <{0}> is already existed".format(name or alias))
|
||||||
|
|
||||||
|
if type_id is None and ci_type is not None:
|
||||||
|
return abort(400, "CIType <{0}> is already existed".format(name or alias))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@kwargs_required("name")
|
@kwargs_required("name")
|
||||||
def add(cls, **kwargs):
|
def add(cls, **kwargs):
|
||||||
unique_key = kwargs.pop("unique_key", None)
|
unique_key = kwargs.pop("unique_key", None)
|
||||||
unique_key = AttributeCache.get(unique_key) or abort(404, "Unique key is not defined")
|
unique_key = AttributeCache.get(unique_key) or abort(404, "Unique key is not defined")
|
||||||
|
|
||||||
CIType.get_by(name=kwargs['name']) and abort(404, "CIType <{0}> is already existed".format(kwargs.get("name")))
|
|
||||||
|
|
||||||
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
||||||
|
|
||||||
|
cls._validate_unique(name=kwargs['name'])
|
||||||
|
cls._validate_unique(alias=kwargs['alias'])
|
||||||
|
|
||||||
kwargs["unique_id"] = unique_key.id
|
kwargs["unique_id"] = unique_key.id
|
||||||
ci_type = CIType.create(**kwargs)
|
ci_type = CIType.create(**kwargs)
|
||||||
|
|
||||||
|
@ -88,6 +110,9 @@ class CITypeManager(object):
|
||||||
|
|
||||||
ci_type = cls.check_is_existed(type_id)
|
ci_type = cls.check_is_existed(type_id)
|
||||||
|
|
||||||
|
cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
|
||||||
|
cls._validate_unique(type_id=type_id, alias=kwargs.get('alias'))
|
||||||
|
|
||||||
unique_key = kwargs.pop("unique_key", None)
|
unique_key = kwargs.pop("unique_key", None)
|
||||||
unique_key = AttributeCache.get(unique_key)
|
unique_key = AttributeCache.get(unique_key)
|
||||||
if unique_key is not None:
|
if unique_key is not None:
|
||||||
|
@ -305,6 +330,31 @@ class CITypeAttributeManager(object):
|
||||||
|
|
||||||
CITypeAttributesCache.clean(type_id)
|
CITypeAttributesCache.clean(type_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transfer(cls, type_id, _from, _to):
|
||||||
|
current_app.logger.info("[{0}] {1} -> {2}".format(type_id, _from, _to))
|
||||||
|
attr_id = _from.get('attr_id')
|
||||||
|
from_group_id = _from.get('group_id')
|
||||||
|
to_group_id = _to.get('group_id')
|
||||||
|
order = _to.get('order')
|
||||||
|
|
||||||
|
if from_group_id != to_group_id:
|
||||||
|
if from_group_id is not None:
|
||||||
|
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||||
|
|
||||||
|
if to_group_id is not None:
|
||||||
|
CITypeAttributeGroupManager.add_item(to_group_id, attr_id, order)
|
||||||
|
|
||||||
|
elif from_group_id:
|
||||||
|
CITypeAttributeGroupManager.update_item(from_group_id, attr_id, order)
|
||||||
|
|
||||||
|
else: # other attribute transfer
|
||||||
|
return abort(400, "invalid operation!!!")
|
||||||
|
|
||||||
|
CITypeAttributesCache.clean(type_id)
|
||||||
|
|
||||||
|
ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
|
|
||||||
class CITypeRelationManager(object):
|
class CITypeRelationManager(object):
|
||||||
"""
|
"""
|
||||||
|
@ -441,6 +491,8 @@ class CITypeAttributeGroupManager(object):
|
||||||
return abort(400, "Group <{0}> duplicate".format(name))
|
return abort(400, "Group <{0}> duplicate".format(name))
|
||||||
if name is not None:
|
if name is not None:
|
||||||
group.update(name=name)
|
group.update(name=name)
|
||||||
|
else:
|
||||||
|
name = group.name
|
||||||
|
|
||||||
cls.create_or_update(group.type_id, name, attr_order, group_order)
|
cls.create_or_update(group.type_id, name, attr_order, group_order)
|
||||||
|
|
||||||
|
@ -455,3 +507,86 @@ class CITypeAttributeGroupManager(object):
|
||||||
item.soft_delete()
|
item.soft_delete()
|
||||||
|
|
||||||
return group_id
|
return group_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_item(cls, group_id, attr_id, order):
|
||||||
|
db.session.remove()
|
||||||
|
|
||||||
|
existed = CITypeAttributeGroupItem.get_by(group_id=group_id,
|
||||||
|
attr_id=attr_id,
|
||||||
|
first=True,
|
||||||
|
to_dict=False)
|
||||||
|
if existed is not None:
|
||||||
|
existed.update(order=order)
|
||||||
|
else:
|
||||||
|
CITypeAttributeGroupItem.create(group_id=group_id, attr_id=attr_id, order=order)
|
||||||
|
|
||||||
|
gt_items = db.session.query(CITypeAttributeGroupItem).filter(
|
||||||
|
CITypeAttributeGroupItem.deleted.is_(False)).filter(CITypeAttributeGroupItem.order > order)
|
||||||
|
for _item in gt_items:
|
||||||
|
_order = _item.order
|
||||||
|
_item.update(order=_order + 1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_item(cls, group_id, attr_id, order):
|
||||||
|
db.session.remove()
|
||||||
|
|
||||||
|
existed = CITypeAttributeGroupItem.get_by(group_id=group_id,
|
||||||
|
attr_id=attr_id,
|
||||||
|
first=True,
|
||||||
|
to_dict=False)
|
||||||
|
existed or abort(404, "Group<{0}> - Attribute<{1}> is not found".format(group_id, attr_id))
|
||||||
|
|
||||||
|
if existed.order > order: # forward, +1
|
||||||
|
items = db.session.query(CITypeAttributeGroupItem).filter(
|
||||||
|
CITypeAttributeGroupItem.deleted.is_(False)).filter(
|
||||||
|
CITypeAttributeGroupItem.order >= order).filter(
|
||||||
|
CITypeAttributeGroupItem.order < existed.order)
|
||||||
|
for item in items:
|
||||||
|
item.update(order=item.order + 1)
|
||||||
|
|
||||||
|
elif existed.order < order: # backward, -1
|
||||||
|
items = db.session.query(CITypeAttributeGroupItem).filter(
|
||||||
|
CITypeAttributeGroupItem.deleted.is_(False)).filter(
|
||||||
|
CITypeAttributeGroupItem.order > existed.order).filter(
|
||||||
|
CITypeAttributeGroupItem.order <= order)
|
||||||
|
for item in items:
|
||||||
|
item.update(order=item.order - 1)
|
||||||
|
|
||||||
|
existed.update(order=order)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_item(cls, group_id, attr_id):
|
||||||
|
db.session.remove()
|
||||||
|
|
||||||
|
item = CITypeAttributeGroupItem.get_by(group_id=group_id,
|
||||||
|
attr_id=attr_id,
|
||||||
|
first=True,
|
||||||
|
to_dict=False)
|
||||||
|
|
||||||
|
if item is not None:
|
||||||
|
item.soft_delete()
|
||||||
|
order = item.order
|
||||||
|
gt_items = db.session.query(CITypeAttributeGroupItem).filter(
|
||||||
|
CITypeAttributeGroupItem.deleted.is_(False)).filter(CITypeAttributeGroupItem.order > order)
|
||||||
|
for _item in gt_items:
|
||||||
|
_order = _item.order
|
||||||
|
_item.update(order=_order - 1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transfer(cls, type_id, _from, _to):
|
||||||
|
current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to))
|
||||||
|
from_group = CITypeAttributeGroup.get_by_id(_from)
|
||||||
|
from_group or abort(404, "Group <{0}> is not found".format(_from))
|
||||||
|
|
||||||
|
to_group = CITypeAttributeGroup.get_by_id(_to)
|
||||||
|
to_group or abort(404, "Group <{0}> is not found".format(_to))
|
||||||
|
|
||||||
|
from_order, to_order = from_group.order, to_group.order
|
||||||
|
|
||||||
|
from_group.update(order=to_order)
|
||||||
|
to_group.update(order=from_order)
|
||||||
|
|
||||||
|
CITypeAttributesCache.clean(type_id)
|
||||||
|
|
||||||
|
ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE)
|
||||||
|
|
|
@ -25,6 +25,9 @@ class FormatMixin(object):
|
||||||
|
|
||||||
|
|
||||||
class CRUDMixin(FormatMixin):
|
class CRUDMixin(FormatMixin):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(CRUDMixin, self).__init__(**kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, flush=False, **kwargs):
|
def create(cls, flush=False, **kwargs):
|
||||||
return cls(**kwargs).save(flush=flush)
|
return cls(**kwargs).save(flush=flush)
|
||||||
|
|
|
@ -47,7 +47,7 @@ def _auth_with_token():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token = auth_headers
|
token = auth_headers
|
||||||
data = jwt.decode(token, current_app.config['SECRET_KEY'])
|
data = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||||
user = User.query.filter_by(email=data['sub']).first()
|
user = User.query.filter_by(email=data['sub']).first()
|
||||||
if not user:
|
if not user:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -15,13 +15,13 @@ from api.lib.database import Model
|
||||||
class RelationType(Model):
|
class RelationType(Model):
|
||||||
__tablename__ = "c_relation_types"
|
__tablename__ = "c_relation_types"
|
||||||
|
|
||||||
name = db.Column(db.String(16), index=True)
|
name = db.Column(db.String(16), index=True, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class CITypeGroup(Model):
|
class CITypeGroup(Model):
|
||||||
__tablename__ = "c_ci_type_groups"
|
__tablename__ = "c_ci_type_groups"
|
||||||
|
|
||||||
name = db.Column(db.String(32))
|
name = db.Column(db.String(32), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class CITypeGroupItem(Model):
|
class CITypeGroupItem(Model):
|
||||||
|
@ -35,12 +35,12 @@ class CITypeGroupItem(Model):
|
||||||
class CIType(Model):
|
class CIType(Model):
|
||||||
__tablename__ = "c_ci_types"
|
__tablename__ = "c_ci_types"
|
||||||
|
|
||||||
name = db.Column(db.String(32))
|
name = db.Column(db.String(32), nullable=False)
|
||||||
alias = db.Column(db.String(32))
|
alias = db.Column(db.String(32), nullable=False)
|
||||||
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
icon_url = db.Column(db.String(256))
|
icon_url = db.Column(db.String(256), default='', nullable=False)
|
||||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||||
|
|
||||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
||||||
|
@ -89,7 +89,7 @@ class CITypeAttribute(Model):
|
||||||
class CITypeAttributeGroup(Model):
|
class CITypeAttributeGroup(Model):
|
||||||
__tablename__ = "c_ci_type_attribute_groups"
|
__tablename__ = "c_ci_type_attribute_groups"
|
||||||
|
|
||||||
name = db.Column(db.String(64))
|
name = db.Column(db.String(64), nullable=False)
|
||||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||||
order = db.Column(db.SmallInteger, default=0)
|
order = db.Column(db.SmallInteger, default=0)
|
||||||
|
|
||||||
|
@ -266,8 +266,8 @@ class OperationRecord(Model):
|
||||||
__tablename__ = "c_records"
|
__tablename__ = "c_records"
|
||||||
|
|
||||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||||
origin = db.Column(db.String(32))
|
origin = db.Column(db.String(32), nullable=False)
|
||||||
ticket_id = db.Column(db.String(32))
|
ticket_id = db.Column(db.String(32), nullable=False)
|
||||||
reason = db.Column(db.Text)
|
reason = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from api.extensions import celery
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.extensions import es
|
from api.extensions import es
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
|
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.cmdb.const import CMDB_QUEUE
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
|
@ -71,3 +72,23 @@ def ci_relation_delete(parent_id, child_id):
|
||||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||||
|
|
||||||
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(name="cmdb.ci_type_attribute_order_rebuild", queue=CMDB_QUEUE)
|
||||||
|
def ci_type_attribute_order_rebuild(type_id):
|
||||||
|
current_app.logger.info('rebuild attribute order')
|
||||||
|
db.session.remove()
|
||||||
|
|
||||||
|
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
||||||
|
|
||||||
|
attrs = CITypeAttributeCache.get(type_id)
|
||||||
|
id2attr = {attr.attr_id: attr for attr in attrs}
|
||||||
|
|
||||||
|
res = CITypeAttributeGroupManager.get_by_type_id(type_id, True)
|
||||||
|
order = 0
|
||||||
|
for group in res:
|
||||||
|
for _attr in group.get('attributes'):
|
||||||
|
if order != id2attr.get(_attr['id']) and id2attr.get(_attr['id']):
|
||||||
|
id2attr.get(_attr['id']).update(order=order)
|
||||||
|
|
||||||
|
order += 1
|
||||||
|
|
|
@ -164,6 +164,34 @@ class CITypeAttributeView(APIView):
|
||||||
return self.jsonify(attributes=attr_id_list)
|
return self.jsonify(attributes=attr_id_list)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeAttributeTransferView(APIView):
|
||||||
|
url_prefix = "/ci_types/<int:type_id>/attributes/transfer"
|
||||||
|
|
||||||
|
@args_required('from')
|
||||||
|
@args_required('to')
|
||||||
|
def post(self, type_id):
|
||||||
|
_from = request.values.get('from') # {'attr_id': xx, 'group_id': xx}
|
||||||
|
_to = request.values.get('to') # {'group_id': xx, 'order': xxx}
|
||||||
|
|
||||||
|
CITypeAttributeManager.transfer(type_id, _from, _to)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeAttributeGroupTransferView(APIView):
|
||||||
|
url_prefix = "/ci_types/<int:type_id>/attribute_groups/transfer"
|
||||||
|
|
||||||
|
@args_required('from')
|
||||||
|
@args_required('to')
|
||||||
|
def post(self, type_id):
|
||||||
|
_from = request.values.get('from') # group_id
|
||||||
|
_to = request.values.get('to') # group_id
|
||||||
|
|
||||||
|
CITypeAttributeGroupManager.transfer(type_id, _from, _to)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
class CITypeAttributeGroupView(APIView):
|
class CITypeAttributeGroupView(APIView):
|
||||||
url_prefix = ("/ci_types/<int:type_id>/attribute_groups",
|
url_prefix = ("/ci_types/<int:type_id>/attribute_groups",
|
||||||
"/ci_types/attribute_groups/<int:group_id>")
|
"/ci_types/attribute_groups/<int:group_id>")
|
||||||
|
|
|
@ -21,7 +21,7 @@ class CMDBTestClient(FlaskClient):
|
||||||
headers.setdefault("User-Agent", "py.test")
|
headers.setdefault("User-Agent", "py.test")
|
||||||
kwargs["headers"] = headers
|
kwargs["headers"] = headers
|
||||||
|
|
||||||
json_data = kwargs.pop("json")
|
json_data = kwargs.pop("json", None)
|
||||||
if json_data is not None:
|
if json_data is not None:
|
||||||
kwargs["data"] = json.dumps(json_data)
|
kwargs["data"] = json.dumps(json_data)
|
||||||
if not kwargs.get("content_type"):
|
if not kwargs.get("content_type"):
|
||||||
|
@ -49,6 +49,7 @@ def app():
|
||||||
_app.config['SECRET_KEY'] = CMDBTestClient.TEST_APP_SECRET
|
_app.config['SECRET_KEY'] = CMDBTestClient.TEST_APP_SECRET
|
||||||
_app.test_client_class = CMDBTestClient
|
_app.test_client_class = CMDBTestClient
|
||||||
_app.response_class = CMDBTestResponse
|
_app.response_class = CMDBTestResponse
|
||||||
|
|
||||||
ctx = _app.test_request_context()
|
ctx = _app.test_request_context()
|
||||||
ctx.push()
|
ctx.push()
|
||||||
yield _app
|
yield _app
|
||||||
|
@ -99,7 +100,6 @@ def clean_db():
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if not User.get_by(email="test@xx.com"):
|
if not User.get_by(email="test@xx.com"):
|
||||||
print("hello world xxxxx")
|
|
||||||
u = User.create(
|
u = User.create(
|
||||||
flush=True,
|
flush=True,
|
||||||
username="test",
|
username="test",
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""provide some sample data in database"""
|
||||||
|
import uuid
|
||||||
|
import random
|
||||||
|
|
||||||
|
from api.models.cmdb import Attribute, CIType, CITypeAttributeGroup, CITypeAttribute
|
||||||
|
|
||||||
|
|
||||||
|
def init_attributes(num=1):
|
||||||
|
attrs = []
|
||||||
|
for i in range(num):
|
||||||
|
attrs.append(Attribute.create(
|
||||||
|
name=uuid.uuid4().hex[:8],
|
||||||
|
alias=uuid.uuid4().hex[:8],
|
||||||
|
value_type=str(random.randint(0, 100) % 7)
|
||||||
|
))
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
def init_ci_types(num=1):
|
||||||
|
attrs = init_attributes(num)
|
||||||
|
|
||||||
|
ci_types = []
|
||||||
|
for i in range(num):
|
||||||
|
ci_type = CIType.create(
|
||||||
|
name=uuid.uuid4().hex[:8],
|
||||||
|
alias=uuid.uuid4().hex[:8],
|
||||||
|
unique_id=attrs[i].id
|
||||||
|
)
|
||||||
|
CITypeAttribute.create(
|
||||||
|
type_id=ci_type.id,
|
||||||
|
attr_id=attrs[i].id,
|
||||||
|
)
|
||||||
|
ci_types.append(ci_type)
|
||||||
|
|
||||||
|
return ci_types
|
||||||
|
|
||||||
|
|
||||||
|
def init_attribute_groups(num=1):
|
||||||
|
ci_types = init_ci_types(num)
|
||||||
|
|
||||||
|
ags = []
|
||||||
|
for i in range(num):
|
||||||
|
ags.append(CITypeAttributeGroup.create(
|
||||||
|
name=uuid.uuid4().hex[:8],
|
||||||
|
type_id=ci_types[i].id,
|
||||||
|
order=i
|
||||||
|
))
|
||||||
|
return ags
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from api.models.cmdb import Attribute
|
from api.models.cmdb import Attribute
|
||||||
|
|
||||||
|
from tests.sample import init_attributes
|
||||||
|
|
||||||
|
|
||||||
def test_create_attribute(session, client):
|
def test_create_attribute(session, client):
|
||||||
url = "/api/v0.1/attributes"
|
url = "/api/v0.1/attributes"
|
||||||
|
@ -16,7 +18,7 @@ def test_create_attribute(session, client):
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json["attr_id"]
|
assert resp.json["attr_id"]
|
||||||
|
|
||||||
# check there is a ci_types in database
|
# check there is a attribute in database
|
||||||
attr_id = resp.json["attr_id"]
|
attr_id = resp.json["attr_id"]
|
||||||
attr_ins = Attribute.get_by_id(attr_id)
|
attr_ins = Attribute.get_by_id(attr_id)
|
||||||
assert attr_ins.id == attr_id
|
assert attr_ins.id == attr_id
|
||||||
|
@ -24,3 +26,34 @@ def test_create_attribute(session, client):
|
||||||
assert attr_ins.alias == "区域"
|
assert attr_ins.alias == "区域"
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_attribute(session, client):
|
||||||
|
attr_ins = init_attributes(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/attributes/" + str(attr_ins.id)
|
||||||
|
payload = {
|
||||||
|
"name": "update",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.put(url, json=payload)
|
||||||
|
|
||||||
|
# check resp status code and content
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json["attr_id"] == attr_ins.id
|
||||||
|
|
||||||
|
# check attribute updated in database
|
||||||
|
attr_ins = Attribute.get_by_id(attr_ins.id)
|
||||||
|
assert attr_ins.name == "update"
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_attribute(session, client):
|
||||||
|
attr_ins = init_attributes(1)[0]
|
||||||
|
url = "/api/v0.1/attributes/" + str(attr_ins.id)
|
||||||
|
|
||||||
|
resp = client.delete(url)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
# attr should be soft delete
|
||||||
|
attr_ins = Attribute.get_by_id(attr_ins.id)
|
||||||
|
assert attr_ins.deleted is True
|
||||||
|
assert attr_ins.deleted_at
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
class TestCI:
|
|
||||||
|
|
||||||
def test_ci_search_only_type_query(self, app):
|
|
||||||
with app.test_client() as c:
|
|
||||||
rv = c.get('/api/v0.1/ci/s?q=_type:server', json={})
|
|
||||||
json_data = rv.get_json()
|
|
||||||
assert type(json_data.get("result")) is list
|
|
||||||
|
|
|
@ -1 +1,167 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from api.models.cmdb import (
|
||||||
|
CIType, CITypeAttribute,
|
||||||
|
Attribute, CITypeAttributeGroup,
|
||||||
|
CITypeAttributeGroupItem)
|
||||||
|
|
||||||
|
from tests.sample import (
|
||||||
|
init_attributes, init_ci_types,
|
||||||
|
init_attribute_groups)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_ci_type(session, client):
|
||||||
|
attr = init_attributes(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types"
|
||||||
|
payload = {
|
||||||
|
"name": "test",
|
||||||
|
"alias": "测试",
|
||||||
|
"unique_key": attr.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.post(url, json=payload)
|
||||||
|
|
||||||
|
# check resp status code and content
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json["type_id"]
|
||||||
|
|
||||||
|
# check there is a attribute in database
|
||||||
|
type_id = resp.json["type_id"]
|
||||||
|
ci_type_ins = CIType.get_by_id(type_id)
|
||||||
|
assert ci_type_ins.id == type_id
|
||||||
|
assert ci_type_ins.name == "test"
|
||||||
|
assert ci_type_ins.alias == "测试"
|
||||||
|
assert ci_type_ins.unique_id == attr.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_ci_type(session, client):
|
||||||
|
ci_type_ins = init_ci_types(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types/" + str(ci_type_ins.id)
|
||||||
|
payload = {
|
||||||
|
"name": "update",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.put(url, json=payload)
|
||||||
|
|
||||||
|
# check resp status code and content
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json["type_id"] == ci_type_ins.id
|
||||||
|
|
||||||
|
# check ci_type updated in database
|
||||||
|
ci_type_ins = CIType.get_by_id(ci_type_ins.id)
|
||||||
|
assert ci_type_ins.name == "update"
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_ci_type(session, client):
|
||||||
|
ci_type_ins = init_ci_types(1)[0]
|
||||||
|
url = "/api/v0.1/ci_types/" + str(ci_type_ins.id)
|
||||||
|
|
||||||
|
resp = client.delete(url)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
# attr should be soft delete
|
||||||
|
ci_type_ins = CIType.get_by_id(ci_type_ins.id)
|
||||||
|
assert ci_type_ins.deleted is True
|
||||||
|
assert ci_type_ins.deleted_at
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind_attributes_ci_type(session, client):
|
||||||
|
attrs = init_attributes(3)
|
||||||
|
ci_type = init_ci_types(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types/{}/attributes".format(ci_type.id)
|
||||||
|
payload = {
|
||||||
|
"attr_id": [str(x.id) for x in attrs]
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.post(url, json=payload)
|
||||||
|
|
||||||
|
# check resp status code and content
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert len(resp.json["attributes"]) == len(attrs)
|
||||||
|
|
||||||
|
# check ci_type has 4 attributes
|
||||||
|
ci_type_attribute_ids = [x.attr_id for x in CITypeAttribute.query.filter_by(type_id=ci_type.id).all()]
|
||||||
|
for attr in attrs:
|
||||||
|
assert attr.id in ci_type_attribute_ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_attributes_ci_type(session, client):
|
||||||
|
ci_type = init_ci_types(1)[0]
|
||||||
|
url = "/api/v0.1/ci_types/{}/attributes".format(ci_type.name)
|
||||||
|
|
||||||
|
resp = client.get(url)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert len(resp.json["attributes"]) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_attributes_ci_type(session, client):
|
||||||
|
ci_type = init_ci_types(1)[0]
|
||||||
|
attr = Attribute.query.first()
|
||||||
|
url = "/api/v0.1/ci_types/{}/attributes".format(ci_type.id)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"attributes": [
|
||||||
|
{"attr_id": attr.id, "default_show": False, "is_required": True}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
resp = client.put(url, json=payload)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
ci_type_attr_ins = CITypeAttribute.query.filter_by(type_id=ci_type.id).first()
|
||||||
|
assert ci_type_attr_ins
|
||||||
|
assert ci_type_attr_ins.is_required is True
|
||||||
|
assert ci_type_attr_ins.default_show is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_attribute_group_ci_type(session, client):
|
||||||
|
ci_type = init_ci_types(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types/{}/attribute_groups".format(ci_type.id)
|
||||||
|
payload = {
|
||||||
|
"name": "A",
|
||||||
|
"order": 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.post(url, json=payload)
|
||||||
|
|
||||||
|
# check resp status code and content
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json["group_id"]
|
||||||
|
|
||||||
|
ins = CITypeAttributeGroup.query.filter_by(type_id=ci_type.id).first()
|
||||||
|
assert ins
|
||||||
|
assert ins.id == resp.json["group_id"]
|
||||||
|
assert ins.name == "A"
|
||||||
|
assert ins.order == 100
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_attribute_group_ci_type(session, client):
|
||||||
|
attribute_groups = init_attribute_groups(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types/attribute_groups/{}".format(attribute_groups.id)
|
||||||
|
payload = {
|
||||||
|
"attributes": [x.id for x in Attribute.query.all()]
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = client.put(url, json=payload)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json["group_id"]
|
||||||
|
|
||||||
|
ag_items = CITypeAttributeGroupItem.query.filter_by(group_id=attribute_groups.id).all()
|
||||||
|
for a in Attribute.query.all():
|
||||||
|
assert a.id in [x.attr_id for x in ag_items]
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_attribute_group_ci_type(session, client):
|
||||||
|
attribute_groups = init_attribute_groups(1)[0]
|
||||||
|
|
||||||
|
url = "/api/v0.1/ci_types/attribute_groups/{}".format(attribute_groups.id)
|
||||||
|
resp = client.delete(url)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
attribute_group_ins = CITypeAttributeGroup.query.filter_by(id=attribute_groups.id).first()
|
||||||
|
assert attribute_group_ins.deleted is True
|
||||||
|
assert attribute_group_ins.deleted_at
|
||||||
|
|
|
@ -1,78 +1,126 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card :bordered="false">
|
<div>
|
||||||
<a-spin :tip="loadTip" :spinning="loading">
|
<a-card :bordered="false">
|
||||||
<search-form ref="search" @refresh="refreshTable" :preferenceAttrList="preferenceAttrList" />
|
<a-spin :tip="loadTip" :spinning="loading">
|
||||||
|
<search-form ref="search" @refresh="refreshTable" :preferenceAttrList="preferenceAttrList" />
|
||||||
|
|
||||||
<ci-detail ref="detail" :typeId="typeId" />
|
<ci-detail ref="detail" :typeId="typeId" />
|
||||||
|
|
||||||
<div class="table-operator">
|
<div class="table-operator">
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
@click="$refs.create.visible = true; $refs.create.action='create'"
|
@click="$refs.create.visible = true; $refs.create.action='create'"
|
||||||
>{{ $t('button.add') }}</a-button>
|
>新建</a-button>
|
||||||
<a-dropdown v-action:edit v-if="selectedRowKeys.length > 0">
|
<a-button class="right" @click="showDrawer(typeId)">显示字段</a-button>
|
||||||
<a-menu slot="overlay">
|
<a-dropdown v-action:edit v-if="selectedRowKeys.length > 0">
|
||||||
<a-menu-item
|
<a-menu slot="overlay">
|
||||||
key="batchUpdate"
|
<a-menu-item
|
||||||
@click="$refs.create.visible = true; $refs.create.action='update'"
|
key="batchUpdate"
|
||||||
>
|
@click="$refs.create.visible = true; $refs.create.action='update'"
|
||||||
<span @click="$refs.create.visible = true">
|
>
|
||||||
<a-icon type="edit" /> 修改
|
<span @click="$refs.create.visible = true">
|
||||||
</span>
|
<a-icon type="edit" /> 修改
|
||||||
</a-menu-item>
|
</span>
|
||||||
<a-menu-item key="batchDownload" @click="batchDownload">
|
</a-menu-item>
|
||||||
<json-excel :fetch="batchDownload" name="cmdb.xls">
|
<a-menu-item key="batchDownload" @click="batchDownload">
|
||||||
<a-icon type="download" /> 下载
|
<json-excel :fetch="batchDownload" name="cmdb.xls">
|
||||||
</json-excel>
|
<a-icon type="download" /> 下载
|
||||||
</a-menu-item>
|
</json-excel>
|
||||||
<a-menu-item key="batchDelete" @click="batchDelete">
|
</a-menu-item>
|
||||||
<a-icon type="delete" />{{ $t('tip.delete') }}
|
<a-menu-item key="batchDelete" @click="batchDelete">
|
||||||
</a-menu-item>
|
<a-icon type="delete" />删除
|
||||||
</a-menu>
|
</a-menu-item>
|
||||||
<a-button style="margin-left: 8px">
|
</a-menu>
|
||||||
{{ $t('ci.batchOperate') }}
|
<a-button style="margin-left: 8px">
|
||||||
<a-icon type="down" />
|
批量操作
|
||||||
</a-button>
|
<a-icon type="down" />
|
||||||
</a-dropdown>
|
</a-button>
|
||||||
</div>
|
</a-dropdown>
|
||||||
<s-table
|
</div>
|
||||||
bordered
|
<s-table
|
||||||
ref="table"
|
bordered
|
||||||
size="middle"
|
ref="table"
|
||||||
rowKey="ci_id"
|
size="middle"
|
||||||
:columns="columns"
|
rowKey="ci_id"
|
||||||
:data="loadInstances"
|
:columns="columns"
|
||||||
:alert="options.alert"
|
:data="loadInstances"
|
||||||
:rowSelection="options.rowSelection"
|
:alert="options.alert"
|
||||||
:scroll="{ x: scrollX, y: scrollY }"
|
:rowSelection="options.rowSelection"
|
||||||
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录`, pageSizeOptions: pageSizeOptions}"
|
:scroll="{ x: scrollX, y: scrollY }"
|
||||||
showPagination="auto"
|
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录`, pageSizeOptions: pageSizeOptions}"
|
||||||
:pageSize="25"
|
showPagination="auto"
|
||||||
>
|
:pageSize="25"
|
||||||
<template :slot="col.dataIndex" slot-scope="text, record" v-for="col in columns">
|
>
|
||||||
<editable-cell
|
<template :slot="col.dataIndex" slot-scope="text, record" v-for="col in columns">
|
||||||
:key="'edit_' + col.dataIndex"
|
<editable-cell
|
||||||
:text="text"
|
:key="'edit_' + col.dataIndex"
|
||||||
@change="onCellChange(record.key, col.dataIndex, $event, record[col.dataIndex])"
|
:text="text"
|
||||||
/>
|
@change="onCellChange(record.key, col.dataIndex, $event, record[col.dataIndex])"
|
||||||
</template>
|
/>
|
||||||
|
|
||||||
<span slot="action" slot-scope="text, record">
|
|
||||||
<template>
|
|
||||||
<a
|
|
||||||
@click="$refs.detail.visible = true; $refs.detail.ciId = record.key; $refs.detail.create()"
|
|
||||||
>{{ $t('tip.detail') }}</a>
|
|
||||||
|
|
||||||
<a-divider type="vertical" />
|
|
||||||
<a @click="deleteCI(record)">{{ $t('tip.delete') }}</a>
|
|
||||||
</template>
|
</template>
|
||||||
</span>
|
|
||||||
</s-table>
|
|
||||||
|
|
||||||
<create-instance-form @refresh="refreshTable" ref="create" @submit="batchUpdate" />
|
<span slot="action" slot-scope="text, record">
|
||||||
</a-spin>
|
<template>
|
||||||
</a-card>
|
<a
|
||||||
|
@click="$refs.detail.visible = true; $refs.detail.ciId = record.key; $refs.detail.create()"
|
||||||
|
>详情</a>
|
||||||
|
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a @click="deleteCI(record)">删除</a>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</s-table>
|
||||||
|
|
||||||
|
<create-instance-form @refresh="refreshTable" ref="create" @submit="batchUpdate" />
|
||||||
|
</a-spin>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-drawer
|
||||||
|
title="显示字段定义"
|
||||||
|
:width="600"
|
||||||
|
@close="onClose"
|
||||||
|
:visible="visible"
|
||||||
|
:wrapStyle="{height: 'calc(100% - 108px)', overflow: 'auto', paddingBottom: '108px'}"
|
||||||
|
>
|
||||||
|
<template>
|
||||||
|
<a-transfer
|
||||||
|
:dataSource="attrList"
|
||||||
|
:showSearch="true"
|
||||||
|
:listStyle="{
|
||||||
|
width: '230px',
|
||||||
|
height: '500px',
|
||||||
|
}"
|
||||||
|
:titles="['未选属性','已选属性']"
|
||||||
|
:render="item=>item.title"
|
||||||
|
:targetKeys="selectedAttrList"
|
||||||
|
@change="handleChange"
|
||||||
|
@search="handleSearch"
|
||||||
|
>
|
||||||
|
<span slot="notFoundContent">没数据</span>
|
||||||
|
</a-transfer>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '100%',
|
||||||
|
borderTop: '1px solid #e9e9e9',
|
||||||
|
padding: '10px 16px',
|
||||||
|
background: '#fff',
|
||||||
|
textAlign: 'right',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-button :style="{marginRight: '8px'}" @click="onClose">取消</a-button>
|
||||||
|
<a-button @click="subInstanceSubmit" type="primary">提交</a-button>
|
||||||
|
</div>
|
||||||
|
</a-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -85,8 +133,9 @@ import CreateInstanceForm from './modules/CreateInstanceForm'
|
||||||
import EditableCell from './modules/EditableCell'
|
import EditableCell from './modules/EditableCell'
|
||||||
import CiDetail from './modules/CiDetail'
|
import CiDetail from './modules/CiDetail'
|
||||||
import { searchCI, updateCI, deleteCI } from '@/api/cmdb/ci'
|
import { searchCI, updateCI, deleteCI } from '@/api/cmdb/ci'
|
||||||
import { getSubscribeAttributes } from '@/api/cmdb/preference'
|
import { getSubscribeAttributes, subscribeCIType } from '@/api/cmdb/preference'
|
||||||
import { notification } from 'ant-design-vue'
|
import { notification } from 'ant-design-vue'
|
||||||
|
import { getCITypeAttributesByName } from '@/api/cmdb/CITypeAttr'
|
||||||
|
|
||||||
var valueTypeMap = {
|
var valueTypeMap = {
|
||||||
'0': 'int',
|
'0': 'int',
|
||||||
|
@ -123,6 +172,10 @@ export default {
|
||||||
|
|
||||||
preferenceAttrList: [],
|
preferenceAttrList: [],
|
||||||
|
|
||||||
|
selectedAttrList: [],
|
||||||
|
attrList: [],
|
||||||
|
visible: false,
|
||||||
|
|
||||||
instanceList: [],
|
instanceList: [],
|
||||||
// 表头
|
// 表头
|
||||||
columns: [],
|
columns: [],
|
||||||
|
@ -202,6 +255,57 @@ export default {
|
||||||
},
|
},
|
||||||
inject: ['reload'],
|
inject: ['reload'],
|
||||||
methods: {
|
methods: {
|
||||||
|
showDrawer () {
|
||||||
|
this.getAttrList()
|
||||||
|
},
|
||||||
|
getAttrList () {
|
||||||
|
getCITypeAttributesByName(this.typeId).then(res => {
|
||||||
|
const attributes = res.attributes
|
||||||
|
getSubscribeAttributes(this.typeId).then(_res => {
|
||||||
|
const attrList = []
|
||||||
|
const selectedAttrList = []
|
||||||
|
const subAttributes = _res.attributes
|
||||||
|
this.instanceSubscribed = _res.is_subscribed
|
||||||
|
subAttributes.forEach(item => {
|
||||||
|
selectedAttrList.push(item.id.toString())
|
||||||
|
})
|
||||||
|
|
||||||
|
attributes.forEach(item => {
|
||||||
|
const data = {
|
||||||
|
key: item.id.toString(),
|
||||||
|
title: item.alias || item.name
|
||||||
|
}
|
||||||
|
attrList.push(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.attrList = attrList
|
||||||
|
this.selectedAttrList = selectedAttrList
|
||||||
|
this.visible = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onClose () {
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
subInstanceSubmit () {
|
||||||
|
subscribeCIType(this.typeId, this.selectedAttrList)
|
||||||
|
.then(res => {
|
||||||
|
notification.success({
|
||||||
|
message: '修改成功'
|
||||||
|
})
|
||||||
|
this.reload()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
notification.error({
|
||||||
|
message: e.response.data.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleChange (targetKeys, direction, moveKeys) {
|
||||||
|
this.selectedAttrList = targetKeys
|
||||||
|
},
|
||||||
|
handleSearch (dir, value) {},
|
||||||
|
|
||||||
setColumnWidth () {
|
setColumnWidth () {
|
||||||
let rows = []
|
let rows = []
|
||||||
try {
|
try {
|
||||||
|
@ -445,15 +549,18 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style lang='less' scoped>
|
||||||
.ant-table-thead > tr > th,
|
/deep/ .ant-table-thead > tr > th,
|
||||||
.ant-table-tbody > tr > td {
|
/deep/ .ant-table-tbody > tr > td {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.spin-content {
|
/deep/ .spin-content {
|
||||||
border: 1px solid #91d5ff;
|
border: 1px solid #91d5ff;
|
||||||
background-color: #e6f7ff;
|
background-color: #e6f7ff;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue