mirror of
https://github.com/veops/cmdb.git
synced 2025-09-13 23:16:54 +08:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cf594f04ba | ||
|
4232094aed | ||
|
d08827d086 | ||
|
d25ae532cd | ||
|
9fbb6ee64d | ||
|
b62f0e96fd | ||
|
c1bcd0ce45 | ||
|
c8b55c34eb | ||
|
4b5906770f | ||
|
4188ac7252 | ||
|
2efbc6474a | ||
|
03eac0c4d2 | ||
|
2a861250eb | ||
|
8fc19d8b7c | ||
|
430d2ff6d0 | ||
|
2517009d70 | ||
|
67da360d80 | ||
|
24c56fb259 | ||
|
37d5da65de | ||
|
2224ebd533 | ||
|
bf6331d215 | ||
|
b18b90ab4e | ||
|
702e17a7a4 | ||
|
a7586aa140 | ||
|
ad3f96431c | ||
|
1515820713 | ||
|
7728b57878 | ||
|
a419eefd72 | ||
|
a44e5f6cf1 | ||
|
7d46e92c2d | ||
|
4117cf87ec | ||
|
9e0fe0b818 | ||
|
2a8f1ab9a4 | ||
|
c0fe99b8c7 | ||
|
42feb4b862 | ||
|
482d34993b | ||
|
7ff309b8b8 | ||
|
98eb47d44f | ||
|
9ab0f624ef | ||
|
3f3eda8b3c | ||
|
f788adc8cf |
@@ -74,17 +74,17 @@
|
|||||||
|
|
||||||
### Docker 一键快速构建
|
### Docker 一键快速构建
|
||||||
> 方法一
|
> 方法一
|
||||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||||
- 第二步: 拷贝项目
|
- 第二步: 拷贝项目
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/veops/cmdb.git
|
git clone https://github.com/veops/cmdb.git
|
||||||
```
|
```
|
||||||
- 第三步:进入主目录,执行:
|
- 第三步:进入主目录,执行:
|
||||||
```
|
```
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
> 方法二, 该方法适用于linux系统
|
> 方法二, 该方法适用于linux系统
|
||||||
- 第一步: 先安装 docker 环境, 以及docker-compose
|
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||||
```shell
|
```shell
|
||||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||||
|
@@ -15,6 +15,7 @@ Flask-SQLAlchemy = "==2.5.0"
|
|||||||
SQLAlchemy = "==1.4.49"
|
SQLAlchemy = "==1.4.49"
|
||||||
PyMySQL = "==1.1.0"
|
PyMySQL = "==1.1.0"
|
||||||
redis = "==4.6.0"
|
redis = "==4.6.0"
|
||||||
|
python-redis-lock = "==4.0.0"
|
||||||
# Migrations
|
# Migrations
|
||||||
Flask-Migrate = "==2.5.2"
|
Flask-Migrate = "==2.5.2"
|
||||||
# Deployment
|
# Deployment
|
||||||
@@ -65,6 +66,7 @@ hvac = "==2.0.0"
|
|||||||
colorama = ">=0.4.6"
|
colorama = ">=0.4.6"
|
||||||
pycryptodomex = ">=3.19.0"
|
pycryptodomex = ">=3.19.0"
|
||||||
lz4 = ">=4.3.2"
|
lz4 = ">=4.3.2"
|
||||||
|
python-magic = "==0.4.27"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
# Testing
|
# Testing
|
||||||
|
@@ -224,7 +224,7 @@ def common_check_new_columns():
|
|||||||
column_type = new_column.type.compile(engine.dialect)
|
column_type = new_column.type.compile(engine.dialect)
|
||||||
default_value = new_column.default.arg if new_column.default else None
|
default_value = new_column.default.arg if new_column.default else None
|
||||||
|
|
||||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + new_column.name + " " + column_type
|
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||||
if new_column.comment:
|
if new_column.comment:
|
||||||
sql += f" comment '{new_column.comment}'"
|
sql += f" comment '{new_column.comment}'"
|
||||||
|
|
||||||
|
@@ -331,14 +331,14 @@ class AutoDiscoveryCICRUD(DBMixin):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes_by_type_id(type_id):
|
def get_attributes_by_type_id(type_id):
|
||||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
attributes = [i[1] for i in CITypeAttributeManager.get_all_attributes(type_id) or []]
|
attributes = [i for i in CITypeAttributeManager.get_attributes_by_type_id(type_id) or []]
|
||||||
|
|
||||||
attr_names = set()
|
attr_names = set()
|
||||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||||
for adt in adts:
|
for adt in adts:
|
||||||
attr_names |= set((adt.attributes or {}).values())
|
attr_names |= set((adt.attributes or {}).values())
|
||||||
|
|
||||||
return [attr.to_dict() for attr in attributes if attr.name in attr_names]
|
return [attr for attr in attributes if attr['name'] in attr_names]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search(cls, page, page_size, fl=None, **kwargs):
|
def search(cls, page, page_size, fl=None, **kwargs):
|
||||||
|
@@ -309,7 +309,7 @@ class CMDBCounterCache(object):
|
|||||||
|
|
||||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||||
try:
|
try:
|
||||||
stats = s.statistics(type_ids)
|
stats = s.statistics(type_ids, need_filter=False)
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
return
|
return
|
||||||
|
@@ -6,6 +6,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -45,7 +46,6 @@ 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.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 Lock
|
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.lib.webhook import webhook_request
|
from api.lib.webhook import webhook_request
|
||||||
from api.models.cmdb import AttributeHistory
|
from api.models.cmdb import AttributeHistory
|
||||||
@@ -60,8 +60,8 @@ from api.tasks.cmdb import ci_delete_trigger
|
|||||||
from api.tasks.cmdb import ci_relation_add
|
from api.tasks.cmdb import ci_relation_add
|
||||||
from api.tasks.cmdb import ci_relation_cache
|
from api.tasks.cmdb import ci_relation_cache
|
||||||
from api.tasks.cmdb import ci_relation_delete
|
from api.tasks.cmdb import ci_relation_delete
|
||||||
|
from api.tasks.cmdb import delete_id_filter
|
||||||
|
|
||||||
PRIVILEGED_USERS = {"worker", "cmdb_agent", "agent"}
|
|
||||||
PASSWORD_DEFAULT_SHOW = "******"
|
PASSWORD_DEFAULT_SHOW = "******"
|
||||||
|
|
||||||
|
|
||||||
@@ -278,10 +278,10 @@ class CIManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _auto_inc_id(attr):
|
def _auto_inc_id(attr):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
|
|
||||||
value_table = TableMap(attr_name=attr.name).table
|
value_table = TableMap(attr_name=attr.name).table
|
||||||
with Lock("auto_inc_id_{}".format(attr.name), need_lock=True):
|
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)):
|
||||||
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
||||||
getattr(value_table, 'value').desc()).first()
|
getattr(value_table, 'value').desc()).first()
|
||||||
if max_v is not None:
|
if max_v is not None:
|
||||||
@@ -312,10 +312,10 @@ class CIManager(object):
|
|||||||
|
|
||||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||||
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
|
||||||
not ci_dict.get(unique_key.name)):
|
|
||||||
ci_dict[unique_key.name] = cls._auto_inc_id(unique_key)
|
|
||||||
|
|
||||||
|
unique_value = None
|
||||||
|
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||||
|
not ci_dict.get(unique_key.name)): # primary key is not auto inc id
|
||||||
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||||
|
|
||||||
@@ -327,8 +327,15 @@ class CIManager(object):
|
|||||||
ci = None
|
ci = None
|
||||||
record_id = None
|
record_id = None
|
||||||
password_dict = {}
|
password_dict = {}
|
||||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
with redis_lock.Lock(rd.r, ci_type.name):
|
||||||
with Lock(ci_type_name, need_lock=need_lock):
|
db.session.commit()
|
||||||
|
|
||||||
|
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||||
|
not ci_dict.get(unique_key.name)):
|
||||||
|
ci_dict[unique_key.name] = cls._auto_inc_id(unique_key)
|
||||||
|
current_app.logger.info(ci_dict[unique_key.name])
|
||||||
|
unique_value = ci_dict[unique_key.name]
|
||||||
|
|
||||||
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
if exist_policy == ExistPolicy.REJECT:
|
if exist_policy == ExistPolicy.REJECT:
|
||||||
@@ -463,8 +470,9 @@ class CIManager(object):
|
|||||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||||
|
|
||||||
record_id = None
|
record_id = None
|
||||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
with redis_lock.Lock(rd.r, ci.ci_type.name):
|
||||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
db.session.commit()
|
||||||
|
|
||||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||||
|
|
||||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
||||||
@@ -551,6 +559,7 @@ class CIManager(object):
|
|||||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||||
|
|
||||||
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return ci_id
|
return ci_id
|
||||||
|
|
||||||
@@ -857,6 +866,20 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
return numfound, len(ci_ids), result
|
return numfound, len(ci_ids), result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def recursive_children(ci_id):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def _get_children(_id):
|
||||||
|
children = CIRelation.get_by(first_ci_id=_id, to_dict=False)
|
||||||
|
result.extend([i.second_ci_id for i in children])
|
||||||
|
for child in children:
|
||||||
|
_get_children(child.second_ci_id)
|
||||||
|
|
||||||
|
_get_children(ci_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sort_handler(sort_by, query_sql):
|
def _sort_handler(sort_by, query_sql):
|
||||||
|
|
||||||
@@ -912,7 +935,7 @@ class CIRelationManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -972,7 +995,7 @@ class CIRelationManager(object):
|
|||||||
else:
|
else:
|
||||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||||
|
|
||||||
with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
|
with redis_lock.Lock(rd.r, "ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id)):
|
||||||
|
|
||||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||||
|
|
||||||
@@ -1008,6 +1031,7 @@ class CIRelationManager(object):
|
|||||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||||
|
|
||||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return cr_id
|
return cr_id
|
||||||
|
|
||||||
@@ -1019,9 +1043,13 @@ class CIRelationManager(object):
|
|||||||
to_dict=False,
|
to_dict=False,
|
||||||
first=True)
|
first=True)
|
||||||
|
|
||||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
if cr is not None:
|
||||||
|
cls.delete(cr.id)
|
||||||
|
|
||||||
return cr and cls.delete(cr.id)
|
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||||
|
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
|
return cr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||||
@@ -1062,7 +1090,7 @@ class CIRelationManager(object):
|
|||||||
class CITriggerManager(object):
|
class CITriggerManager(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(type_id):
|
def get(type_id):
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@@ -76,6 +76,10 @@ class CITypeManager(object):
|
|||||||
|
|
||||||
return CIType.get_by_id(ci_type.id)
|
return CIType.get_by_id(ci_type.id)
|
||||||
|
|
||||||
|
def get_icons(self):
|
||||||
|
return {i.id: i.icon or i.name for i in db.session.query(
|
||||||
|
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ci_types(type_name=None, like=True):
|
def get_ci_types(type_name=None, like=True):
|
||||||
resources = None
|
resources = None
|
||||||
@@ -223,10 +227,12 @@ class CITypeManager(object):
|
|||||||
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
||||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
||||||
|
|
||||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
|
||||||
item.soft_delete(commit=False)
|
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
|
||||||
|
if current_app.config.get('USE_ACL'):
|
||||||
|
resource_name = CITypeRelationManager.acl_resource_name(item.parent.name, item.child.name)
|
||||||
|
ACLManager().del_resource(resource_name, ResourceTypeEnum.CI_TYPE_RELATION)
|
||||||
|
|
||||||
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
|
|
||||||
item.soft_delete(commit=False)
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||||
@@ -644,10 +650,30 @@ class CITypeAttributeManager(object):
|
|||||||
existed.soft_delete()
|
existed.soft_delete()
|
||||||
|
|
||||||
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
||||||
AttributeValueManager.delete_attr_value(attr_id, ci.id)
|
AttributeValueManager.delete_attr_value(attr_id, ci.id, commit=False)
|
||||||
|
|
||||||
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
|
for item in PreferenceShowAttributes.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
child_ids = CITypeInheritanceManager.recursive_children(type_id)
|
||||||
|
for _type_id in [type_id] + child_ids:
|
||||||
|
for item in CITypeUniqueConstraint.get_by(type_id=_type_id, to_dict=False):
|
||||||
|
if attr_id in item.attr_ids:
|
||||||
|
attr_ids = copy.deepcopy(item.attr_ids)
|
||||||
|
attr_ids.remove(attr_id)
|
||||||
|
|
||||||
|
if attr_ids:
|
||||||
|
item.update(attr_ids=attr_ids, commit=False)
|
||||||
|
else:
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
|
||||||
|
item and item.soft_delete(commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
CITypeAttributeCache.clean(type_id, attr_id)
|
CITypeAttributeCache.clean(type_id, attr_id)
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||||
@@ -673,6 +699,10 @@ class CITypeAttributeManager(object):
|
|||||||
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
to_group = CITypeAttributeGroup.get_by(type_id=type_id, name=to_group_name, first=True, to_dict=False)
|
||||||
to_group_id = to_group and to_group.id
|
to_group_id = to_group and to_group.id
|
||||||
|
|
||||||
|
if not to_group_id and CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||||
|
to_group = CITypeAttributeGroup.create(type_id=type_id, name=to_group_name)
|
||||||
|
to_group_id = to_group and to_group.id
|
||||||
|
|
||||||
if from_group_id != to_group_id:
|
if from_group_id != to_group_id:
|
||||||
if from_group_id is not None:
|
if from_group_id is not None:
|
||||||
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
CITypeAttributeGroupManager.delete_item(from_group_id, attr_id)
|
||||||
@@ -1162,6 +1192,8 @@ class CITypeTemplateManager(object):
|
|||||||
i.pop('choice_web_hook', None)
|
i.pop('choice_web_hook', None)
|
||||||
i.pop('choice_other', None)
|
i.pop('choice_other', None)
|
||||||
i.pop('order', None)
|
i.pop('order', None)
|
||||||
|
i.pop('inherited', None)
|
||||||
|
i.pop('inherited_from', None)
|
||||||
choice_value = i.pop('choice_value', None)
|
choice_value = i.pop('choice_value', None)
|
||||||
if not choice_value:
|
if not choice_value:
|
||||||
i['is_choice'] = False
|
i['is_choice'] = False
|
||||||
@@ -1273,6 +1305,8 @@ class CITypeTemplateManager(object):
|
|||||||
_group = copy.deepcopy(group)
|
_group = copy.deepcopy(group)
|
||||||
_group.pop('attributes', None)
|
_group.pop('attributes', None)
|
||||||
_group.pop('id', None)
|
_group.pop('id', None)
|
||||||
|
_group.pop('inherited', None)
|
||||||
|
_group.pop('inherited_from', None)
|
||||||
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
existed = CITypeAttributeGroup.get_by(name=_group['name'],
|
||||||
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
type_id=type_id_map.get(_group['type_id'], _group['type_id']),
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
@@ -1328,9 +1362,13 @@ class CITypeTemplateManager(object):
|
|||||||
rule.pop("id", None)
|
rule.pop("id", None)
|
||||||
rule.pop("created_at", None)
|
rule.pop("created_at", None)
|
||||||
rule.pop("updated_at", None)
|
rule.pop("updated_at", None)
|
||||||
|
rule.pop("relation", None)
|
||||||
|
|
||||||
rule['uid'] = current_user.uid
|
rule['uid'] = current_user.uid
|
||||||
|
|
||||||
|
if not rule.get('attributes'):
|
||||||
|
continue
|
||||||
|
|
||||||
existed = False
|
existed = False
|
||||||
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False):
|
||||||
if ((i.extra_option or {}).get('alias') or None) == (
|
if ((i.extra_option or {}).get('alias') or None) == (
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
import copy
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from api.extensions import db
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.mixin import DBMixin
|
from api.lib.mixin import DBMixin
|
||||||
@@ -40,6 +43,11 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result[i['rid']]['ci_filter'] = ""
|
result[i['rid']]['ci_filter'] = ""
|
||||||
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
||||||
|
|
||||||
|
if i['id_filter']:
|
||||||
|
if not result[i['rid']]['id_filter']:
|
||||||
|
result[i['rid']]['id_filter'] = {}
|
||||||
|
result[i['rid']]['id_filter'].update(i['id_filter'] or {})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_by_ids(self, _ids, type_id=None):
|
def get_by_ids(self, _ids, type_id=None):
|
||||||
@@ -70,6 +78,11 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result[i['type_id']]['ci_filter'] = ""
|
result[i['type_id']]['ci_filter'] = ""
|
||||||
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
||||||
|
|
||||||
|
if i['id_filter']:
|
||||||
|
if not result[i['type_id']]['id_filter']:
|
||||||
|
result[i['type_id']]['id_filter'] = {}
|
||||||
|
result[i['type_id']]['id_filter'].update(i['id_filter'] or {})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -82,6 +95,54 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
|
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
|
||||||
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
||||||
|
|
||||||
|
def _revoke_children(self, rid, id_filter, rebuild=True):
|
||||||
|
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||||
|
for item in items:
|
||||||
|
changed, item_id_filter = False, copy.deepcopy(item.id_filter)
|
||||||
|
for prefix in id_filter:
|
||||||
|
for k, v in copy.deepcopy((item.id_filter or {})).items():
|
||||||
|
if k.startswith(prefix) and k != prefix:
|
||||||
|
item_id_filter.pop(k)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if not item_id_filter and current_app.config.get('USE_ACL'):
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
ACLManager().del_resource(str(item.id), ResourceTypeEnum.CI_FILTER, rebuild=rebuild)
|
||||||
|
elif changed:
|
||||||
|
item.update(id_filter=item_id_filter, commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def _revoke_parent(self, rid, parent_path, rebuild=True):
|
||||||
|
parent_path = [i for i in parent_path.split(',') if i] or []
|
||||||
|
revoke_nodes = [','.join(parent_path[:i]) for i in range(len(parent_path), 0, -1)]
|
||||||
|
for node_path in revoke_nodes:
|
||||||
|
delete_item, can_deleted = None, True
|
||||||
|
items = self.cls.get_by(rid=rid, ci_filter=None, attr_filter=None, to_dict=False)
|
||||||
|
for item in items:
|
||||||
|
if node_path in item.id_filter:
|
||||||
|
delete_item = item
|
||||||
|
if any(filter(lambda x: x.startswith(node_path) and x != node_path, item.id_filter.keys())):
|
||||||
|
can_deleted = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if can_deleted and delete_item:
|
||||||
|
id_filter = copy.deepcopy(delete_item.id_filter)
|
||||||
|
id_filter.pop(node_path)
|
||||||
|
delete_item = delete_item.update(id_filter=id_filter, filter_none=False)
|
||||||
|
|
||||||
|
if current_app.config.get('USE_ACL') and not id_filter:
|
||||||
|
ACLManager().del_resource(str(delete_item.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||||
|
delete_item.soft_delete()
|
||||||
|
items.remove(delete_item)
|
||||||
|
|
||||||
|
if rebuild:
|
||||||
|
from api.tasks.acl import role_rebuild
|
||||||
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
|
from api.lib.perm.acl.cache import AppCache
|
||||||
|
|
||||||
|
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
|
||||||
|
|
||||||
def _can_add(self, **kwargs):
|
def _can_add(self, **kwargs):
|
||||||
ci_filter = kwargs.get('ci_filter')
|
ci_filter = kwargs.get('ci_filter')
|
||||||
attr_filter = kwargs.get('attr_filter') or ""
|
attr_filter = kwargs.get('attr_filter') or ""
|
||||||
@@ -102,27 +163,58 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
|
|
||||||
def add(self, **kwargs):
|
def add(self, **kwargs):
|
||||||
kwargs = self._can_add(**kwargs) or kwargs
|
kwargs = self._can_add(**kwargs) or kwargs
|
||||||
|
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||||
|
request_id_filter = {}
|
||||||
|
if kwargs.get('id_filter'):
|
||||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
rid=kwargs.get('rid'),
|
rid=kwargs.get('rid'),
|
||||||
|
ci_filter=None,
|
||||||
|
attr_filter=None,
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
|
|
||||||
|
for _id, v in (kwargs.get('id_filter') or {}).items():
|
||||||
|
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
|
||||||
|
request_id_filter[key] = v['name']
|
||||||
|
|
||||||
|
else:
|
||||||
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
|
rid=kwargs.get('rid'),
|
||||||
|
id_filter=None,
|
||||||
|
first=True, to_dict=False)
|
||||||
|
|
||||||
|
is_recursive = kwargs.pop('is_recursive', 0)
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
|
if obj.id_filter and isinstance(kwargs.get('id_filter'), dict):
|
||||||
|
obj_id_filter = copy.deepcopy(obj.id_filter)
|
||||||
|
|
||||||
|
for k, v in request_id_filter.items():
|
||||||
|
obj_id_filter[k] = v
|
||||||
|
|
||||||
|
kwargs['id_filter'] = obj_id_filter
|
||||||
|
|
||||||
obj = obj.update(filter_none=False, **kwargs)
|
obj = obj.update(filter_none=False, **kwargs)
|
||||||
if not obj.attr_filter and not obj.ci_filter:
|
|
||||||
|
if not obj.attr_filter and not obj.ci_filter and not obj.id_filter:
|
||||||
if current_app.config.get('USE_ACL'):
|
if current_app.config.get('USE_ACL'):
|
||||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||||
|
|
||||||
obj.soft_delete()
|
obj.soft_delete()
|
||||||
|
|
||||||
return obj
|
if not is_recursive and request_id_filter:
|
||||||
|
self._revoke_children(obj.rid, request_id_filter, rebuild=False)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
|
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter') and not kwargs.get('id_filter'):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if request_id_filter:
|
||||||
|
kwargs['id_filter'] = request_id_filter
|
||||||
|
|
||||||
obj = self.cls.create(**kwargs)
|
obj = self.cls.create(**kwargs)
|
||||||
|
|
||||||
if current_app.config.get('USE_ACL'):
|
if current_app.config.get('USE_ACL'): # new resource
|
||||||
try:
|
try:
|
||||||
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
||||||
except:
|
except:
|
||||||
@@ -140,8 +232,10 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def delete(self, **kwargs):
|
def delete(self, **kwargs):
|
||||||
|
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
rid=kwargs.get('rid'),
|
rid=kwargs.get('rid'),
|
||||||
|
id_filter=None,
|
||||||
first=True, to_dict=False)
|
first=True, to_dict=False)
|
||||||
|
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
@@ -153,6 +247,69 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
|
|
||||||
return resource
|
return resource
|
||||||
|
|
||||||
|
def delete2(self, **kwargs):
|
||||||
|
|
||||||
|
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
|
||||||
|
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||||
|
rid=kwargs.get('rid'),
|
||||||
|
ci_filter=None,
|
||||||
|
attr_filter=None,
|
||||||
|
first=True, to_dict=False)
|
||||||
|
|
||||||
|
request_id_filter = {}
|
||||||
|
for _id, v in (kwargs.get('id_filter') or {}).items():
|
||||||
|
key = ",".join(([v['parent_path']] if v.get('parent_path') else []) + [str(_id)])
|
||||||
|
request_id_filter[key] = v['name']
|
||||||
|
|
||||||
|
resource = None
|
||||||
|
if obj is not None:
|
||||||
|
|
||||||
|
id_filter = {}
|
||||||
|
for k, v in copy.deepcopy(obj.id_filter or {}).items(): # important
|
||||||
|
if k not in request_id_filter:
|
||||||
|
id_filter[k] = v
|
||||||
|
|
||||||
|
if not id_filter and current_app.config.get('USE_ACL'):
|
||||||
|
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER, rebuild=False)
|
||||||
|
obj.soft_delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
obj.update(id_filter=id_filter)
|
||||||
|
|
||||||
|
self._revoke_children(kwargs.get('rid'), request_id_filter, rebuild=False)
|
||||||
|
self._revoke_parent(kwargs.get('rid'), kwargs.get('parent_path'))
|
||||||
|
|
||||||
|
return resource
|
||||||
|
|
||||||
|
def delete_id_filter_by_ci_id(self, ci_id):
|
||||||
|
items = self.cls.get_by(ci_filter=None, attr_filter=None, to_dict=False)
|
||||||
|
|
||||||
|
rebuild_roles = set()
|
||||||
|
for item in items:
|
||||||
|
id_filter = copy.deepcopy(item.id_filter)
|
||||||
|
changed = False
|
||||||
|
for node_path in item.id_filter:
|
||||||
|
if str(ci_id) in node_path:
|
||||||
|
id_filter.pop(node_path)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
rebuild_roles.add(item.rid)
|
||||||
|
if not id_filter:
|
||||||
|
item.soft_delete(commit=False)
|
||||||
|
else:
|
||||||
|
item.update(id_filter=id_filter, commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if rebuild_roles:
|
||||||
|
from api.tasks.acl import role_rebuild
|
||||||
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
|
from api.lib.perm.acl.cache import AppCache
|
||||||
|
for rid in rebuild_roles:
|
||||||
|
role_rebuild.apply_async(args=(rid, AppCache.get('cmdb').id), queue=ACL_QUEUE)
|
||||||
|
|
||||||
|
|
||||||
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||||
def decorator_has_perm(func):
|
def decorator_has_perm(func):
|
||||||
|
@@ -238,11 +238,13 @@ class PreferenceManager(object):
|
|||||||
views = _views
|
views = _views
|
||||||
|
|
||||||
view2cr_ids = dict()
|
view2cr_ids = dict()
|
||||||
|
name2view = dict()
|
||||||
result = dict()
|
result = dict()
|
||||||
name2id = list()
|
name2id = list()
|
||||||
for view in views:
|
for view in views:
|
||||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||||
name2id.append([view['name'], view['id']])
|
name2id.append([view['name'], view['id']])
|
||||||
|
name2view[view['name']] = view
|
||||||
|
|
||||||
id2type = dict()
|
id2type = dict()
|
||||||
for view_name in view2cr_ids:
|
for view_name in view2cr_ids:
|
||||||
@@ -286,6 +288,8 @@ class PreferenceManager(object):
|
|||||||
topo_flatten=topo_flatten,
|
topo_flatten=topo_flatten,
|
||||||
level2constraint=level2constraint,
|
level2constraint=level2constraint,
|
||||||
leaf=leaf,
|
leaf=leaf,
|
||||||
|
option=name2view[view_name]['option'],
|
||||||
|
is_public=name2view[view_name]['is_public'],
|
||||||
leaf2show_types=leaf2show_types,
|
leaf2show_types=leaf2show_types,
|
||||||
node2show_types=node2show_types,
|
node2show_types=node2show_types,
|
||||||
show_types=[CITypeCache.get(j).to_dict()
|
show_types=[CITypeCache.get(j).to_dict()
|
||||||
@@ -297,14 +301,18 @@ class PreferenceManager(object):
|
|||||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_or_update_relation_view(cls, name, cr_ids, is_public=False):
|
def create_or_update_relation_view(cls, name=None, cr_ids=None, _id=None, is_public=False, option=None):
|
||||||
if not cr_ids:
|
if not cr_ids:
|
||||||
return abort(400, ErrFormat.preference_relation_view_node_required)
|
return abort(400, ErrFormat.preference_relation_view_node_required)
|
||||||
|
|
||||||
|
if _id is None:
|
||||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||||
|
else:
|
||||||
|
existed = PreferenceRelationView.get_by_id(_id)
|
||||||
current_app.logger.debug(existed)
|
current_app.logger.debug(existed)
|
||||||
if existed is None:
|
if existed is None:
|
||||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid, is_public=is_public)
|
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid,
|
||||||
|
is_public=is_public, option=option)
|
||||||
|
|
||||||
if current_app.config.get("USE_ACL"):
|
if current_app.config.get("USE_ACL"):
|
||||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||||
@@ -312,6 +320,11 @@ class PreferenceManager(object):
|
|||||||
RoleEnum.CMDB_READ_ALL,
|
RoleEnum.CMDB_READ_ALL,
|
||||||
ResourceTypeEnum.RELATION_VIEW,
|
ResourceTypeEnum.RELATION_VIEW,
|
||||||
permissions=[PermEnum.READ])
|
permissions=[PermEnum.READ])
|
||||||
|
else:
|
||||||
|
if existed.name != name and current_app.config.get("USE_ACL"):
|
||||||
|
ACLManager().update_resource(existed.name, name, ResourceTypeEnum.RELATION_VIEW)
|
||||||
|
|
||||||
|
existed.update(name=name, cr_ids=cr_ids, is_public=is_public, option=option)
|
||||||
|
|
||||||
return cls.get_relation_view()
|
return cls.get_relation_view()
|
||||||
|
|
||||||
|
@@ -96,7 +96,7 @@ class ErrFormat(CommonErrFormat):
|
|||||||
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
# 属性 {} 的值必须是唯一的, 当前值 {} 已存在
|
||||||
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
attribute_value_unique_required = _l("The value of attribute {} must be unique, {} already exists")
|
||||||
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
attribute_value_required = _l("Attribute {} value must exist") # 属性 {} 值必须存在
|
||||||
|
attribute_value_out_of_range = _l("Out of range value, the maximum value is 2147483647")
|
||||||
# 新增或者修改属性值未知错误: {}
|
# 新增或者修改属性值未知错误: {}
|
||||||
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
attribute_value_unknown_error = _l("Unknown error when adding or modifying attribute value: {}")
|
||||||
|
|
||||||
|
@@ -16,10 +16,11 @@ def search(query=None,
|
|||||||
ret_key=RetKey.NAME,
|
ret_key=RetKey.NAME,
|
||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
excludes=None):
|
excludes=None,
|
||||||
|
use_id_filter=False):
|
||||||
if current_app.config.get("USE_ES"):
|
if current_app.config.get("USE_ES"):
|
||||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||||
else:
|
else:
|
||||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
|
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@@ -62,7 +62,7 @@ QUERY_CI_BY_ATTR_NAME = """
|
|||||||
QUERY_CI_BY_ID = """
|
QUERY_CI_BY_ID = """
|
||||||
SELECT c_cis.id as ci_id
|
SELECT c_cis.id as ci_id
|
||||||
FROM c_cis
|
FROM c_cis
|
||||||
WHERE c_cis.id={}
|
WHERE c_cis.id {}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
QUERY_CI_BY_TYPE = """
|
QUERY_CI_BY_TYPE = """
|
||||||
|
@@ -44,7 +44,10 @@ class Search(object):
|
|||||||
count=1,
|
count=1,
|
||||||
sort=None,
|
sort=None,
|
||||||
ci_ids=None,
|
ci_ids=None,
|
||||||
excludes=None):
|
excludes=None,
|
||||||
|
parent_node_perm_passed=False,
|
||||||
|
use_id_filter=False,
|
||||||
|
use_ci_filter=True):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl or []
|
self.fl = fl or []
|
||||||
self.excludes = excludes or []
|
self.excludes = excludes or []
|
||||||
@@ -54,12 +57,17 @@ class Search(object):
|
|||||||
self.count = count
|
self.count = count
|
||||||
self.sort = sort
|
self.sort = sort
|
||||||
self.ci_ids = ci_ids or []
|
self.ci_ids = ci_ids or []
|
||||||
|
self.raw_ci_ids = copy.deepcopy(self.ci_ids)
|
||||||
self.query_sql = ""
|
self.query_sql = ""
|
||||||
self.type_id_list = []
|
self.type_id_list = []
|
||||||
self.only_type_query = False
|
self.only_type_query = False
|
||||||
|
self.parent_node_perm_passed = parent_node_perm_passed
|
||||||
|
self.use_id_filter = use_id_filter
|
||||||
|
self.use_ci_filter = use_ci_filter
|
||||||
|
|
||||||
self.valid_type_names = []
|
self.valid_type_names = []
|
||||||
self.type2filter_perms = dict()
|
self.type2filter_perms = dict()
|
||||||
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _operator_proc(key):
|
def _operator_proc(key):
|
||||||
@@ -106,7 +114,7 @@ class Search(object):
|
|||||||
self.type_id_list.append(str(ci_type.id))
|
self.type_id_list.append(str(ci_type.id))
|
||||||
if ci_type.id in self.type2filter_perms:
|
if ci_type.id in self.type2filter_perms:
|
||||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||||
if ci_filter:
|
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||||
sub = []
|
sub = []
|
||||||
ci_filter = Template(ci_filter).render(user=current_user)
|
ci_filter = Template(ci_filter).render(user=current_user)
|
||||||
for i in ci_filter.split(','):
|
for i in ci_filter.split(','):
|
||||||
@@ -122,6 +130,14 @@ class Search(object):
|
|||||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
else:
|
else:
|
||||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
|
|
||||||
|
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
||||||
|
|
||||||
|
if not self.raw_ci_ids:
|
||||||
|
self.ci_ids = list(self.type2filter_perms[ci_type.id]['id_filter'].keys())
|
||||||
|
|
||||||
|
if self.use_id_filter and not self.ci_ids and not self.is_app_admin:
|
||||||
|
self.raw_ci_ids = [0]
|
||||||
else:
|
else:
|
||||||
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
||||||
else:
|
else:
|
||||||
@@ -138,7 +154,10 @@ class Search(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _id_query_handler(v):
|
def _id_query_handler(v):
|
||||||
return QUERY_CI_BY_ID.format(v)
|
if ";" in v:
|
||||||
|
return QUERY_CI_BY_ID.format("in {}".format(v.replace(';', ',')))
|
||||||
|
else:
|
||||||
|
return QUERY_CI_BY_ID.format("= {}".format(v))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _in_query_handler(attr, v, is_not):
|
def _in_query_handler(attr, v, is_not):
|
||||||
@@ -152,6 +171,7 @@ class Search(object):
|
|||||||
"NOT LIKE" if is_not else "LIKE",
|
"NOT LIKE" if is_not else "LIKE",
|
||||||
_v.replace("*", "%")) for _v in new_v])
|
_v.replace("*", "%")) for _v in new_v])
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -167,6 +187,7 @@ class Search(object):
|
|||||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||||
start.replace("*", "%"), end.replace("*", "%"))
|
start.replace("*", "%"), end.replace("*", "%"))
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -183,6 +204,7 @@ class Search(object):
|
|||||||
|
|
||||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||||
|
|
||||||
return _query_sql
|
return _query_sql
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -194,6 +216,7 @@ class Search(object):
|
|||||||
elif field.startswith("-"):
|
elif field.startswith("-"):
|
||||||
field = field[1:]
|
field = field[1:]
|
||||||
sort_type = "DESC"
|
sort_type = "DESC"
|
||||||
|
|
||||||
return field, sort_type
|
return field, sort_type
|
||||||
|
|
||||||
def __sort_by_id(self, sort_type, query_sql):
|
def __sort_by_id(self, sort_type, query_sql):
|
||||||
@@ -322,6 +345,11 @@ class Search(object):
|
|||||||
|
|
||||||
return numfound, res
|
return numfound, res
|
||||||
|
|
||||||
|
def __get_type2filter_perms(self):
|
||||||
|
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
|
||||||
|
if res2:
|
||||||
|
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||||
|
|
||||||
def __get_types_has_read(self):
|
def __get_types_has_read(self):
|
||||||
"""
|
"""
|
||||||
:return: _type:(type1;type2)
|
:return: _type:(type1;type2)
|
||||||
@@ -331,14 +359,23 @@ class Search(object):
|
|||||||
|
|
||||||
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
||||||
|
|
||||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
self.__get_type2filter_perms()
|
||||||
if res2:
|
|
||||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
for type_id in self.type2filter_perms:
|
||||||
|
ci_type = CITypeCache.get(type_id)
|
||||||
|
if ci_type:
|
||||||
|
if self.type2filter_perms[type_id].get('id_filter'):
|
||||||
|
if self.use_id_filter:
|
||||||
|
self.valid_type_names.add(ci_type.name)
|
||||||
|
elif self.type2filter_perms[type_id].get('ci_filter'):
|
||||||
|
if self.use_ci_filter:
|
||||||
|
self.valid_type_names.add(ci_type.name)
|
||||||
|
else:
|
||||||
|
self.valid_type_names.add(ci_type.name)
|
||||||
|
|
||||||
return "_type:({})".format(";".join(self.valid_type_names))
|
return "_type:({})".format(";".join(self.valid_type_names))
|
||||||
|
|
||||||
def __confirm_type_first(self, queries):
|
def __confirm_type_first(self, queries):
|
||||||
|
|
||||||
has_type = False
|
has_type = False
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@@ -371,8 +408,10 @@ class Search(object):
|
|||||||
else:
|
else:
|
||||||
result.append(q)
|
result.append(q)
|
||||||
|
|
||||||
_is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
if self.parent_node_perm_passed:
|
||||||
if result and not has_type and not _is_app_admin:
|
self.__get_type2filter_perms()
|
||||||
|
self.valid_type_names = "ALL"
|
||||||
|
elif result and not has_type and not self.is_app_admin:
|
||||||
type_q = self.__get_types_has_read()
|
type_q = self.__get_types_has_read()
|
||||||
if id_query:
|
if id_query:
|
||||||
ci = CIManager.get_by_id(id_query)
|
ci = CIManager.get_by_id(id_query)
|
||||||
@@ -381,13 +420,11 @@ class Search(object):
|
|||||||
result.insert(0, "_type:{}".format(ci.type_id))
|
result.insert(0, "_type:{}".format(ci.type_id))
|
||||||
else:
|
else:
|
||||||
result.insert(0, type_q)
|
result.insert(0, type_q)
|
||||||
elif _is_app_admin:
|
elif self.is_app_admin:
|
||||||
self.valid_type_names = "ALL"
|
self.valid_type_names = "ALL"
|
||||||
else:
|
else:
|
||||||
self.__get_types_has_read()
|
self.__get_types_has_read()
|
||||||
|
|
||||||
current_app.logger.warning(result)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __query_by_attr(self, q, queries, alias):
|
def __query_by_attr(self, q, queries, alias):
|
||||||
@@ -479,7 +516,7 @@ class Search(object):
|
|||||||
def _filter_ids(self, query_sql):
|
def _filter_ids(self, query_sql):
|
||||||
if self.ci_ids:
|
if self.ci_ids:
|
||||||
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
return "SELECT * FROM ({0}) AS IN_QUERY WHERE IN_QUERY.ci_id IN ({1})".format(
|
||||||
query_sql, ",".join(list(map(str, self.ci_ids))))
|
query_sql, ",".join(list(set(map(str, self.ci_ids)))))
|
||||||
|
|
||||||
return query_sql
|
return query_sql
|
||||||
|
|
||||||
@@ -511,6 +548,9 @@ class Search(object):
|
|||||||
s = time.time()
|
s = time.time()
|
||||||
if query_sql:
|
if query_sql:
|
||||||
query_sql = self._filter_ids(query_sql)
|
query_sql = self._filter_ids(query_sql)
|
||||||
|
if self.raw_ci_ids and not self.ci_ids:
|
||||||
|
return 0, []
|
||||||
|
|
||||||
self.query_sql = query_sql
|
self.query_sql = query_sql
|
||||||
# current_app.logger.debug(query_sql)
|
# current_app.logger.debug(query_sql)
|
||||||
numfound, res = self._execute_sql(query_sql)
|
numfound, res = self._execute_sql(query_sql)
|
||||||
@@ -569,3 +609,8 @@ class Search(object):
|
|||||||
total = len(response)
|
total = len(response)
|
||||||
|
|
||||||
return response, counter, total, self.page, numfound, facet
|
return response, counter, total, self.page, numfound, facet
|
||||||
|
|
||||||
|
def get_ci_ids(self):
|
||||||
|
_, ci_ids = self._query_build_raw()
|
||||||
|
|
||||||
|
return ci_ids
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
@@ -11,11 +13,14 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
|
|||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
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.cmdb.search.ci.db.search import Search as SearchFromDB
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||||
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
|
||||||
|
|
||||||
|
|
||||||
class Search(object):
|
class Search(object):
|
||||||
@@ -29,7 +34,9 @@ class Search(object):
|
|||||||
sort=None,
|
sort=None,
|
||||||
reverse=False,
|
reverse=False,
|
||||||
ancestor_ids=None,
|
ancestor_ids=None,
|
||||||
has_m2m=None):
|
descendant_ids=None,
|
||||||
|
has_m2m=None,
|
||||||
|
root_parent_path=None):
|
||||||
self.orig_query = query
|
self.orig_query = query
|
||||||
self.fl = fl
|
self.fl = fl
|
||||||
self.facet_field = facet_field
|
self.facet_field = facet_field
|
||||||
@@ -46,6 +53,8 @@ class Search(object):
|
|||||||
level[0] if isinstance(level, list) and level else level)
|
level[0] if isinstance(level, list) and level else level)
|
||||||
|
|
||||||
self.ancestor_ids = ancestor_ids
|
self.ancestor_ids = ancestor_ids
|
||||||
|
self.descendant_ids = descendant_ids
|
||||||
|
self.root_parent_path = root_parent_path
|
||||||
self.has_m2m = has_m2m or False
|
self.has_m2m = has_m2m or False
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
if self.ancestor_ids:
|
if self.ancestor_ids:
|
||||||
@@ -56,27 +65,23 @@ class Search(object):
|
|||||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||||
self.has_m2m = True
|
self.has_m2m = True
|
||||||
|
|
||||||
|
self.type2filter_perms = None
|
||||||
|
|
||||||
|
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||||
|
|
||||||
def _get_ids(self, ids):
|
def _get_ids(self, ids):
|
||||||
if self.level[-1] == 1 and len(ids) == 1:
|
|
||||||
if self.ancestor_ids is None:
|
|
||||||
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
|
|
||||||
|
|
||||||
else:
|
|
||||||
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
|
|
||||||
ancestor_ids=self.ancestor_ids,
|
|
||||||
to_dict=False)}
|
|
||||||
|
|
||||||
return list(seconds)
|
|
||||||
|
|
||||||
merge_ids = []
|
merge_ids = []
|
||||||
key = []
|
key = []
|
||||||
_tmp = []
|
_tmp = []
|
||||||
for level in range(1, sorted(self.level)[-1] + 1):
|
for level in range(1, sorted(self.level)[-1] + 1):
|
||||||
|
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||||
|
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
_tmp = map(lambda x: json.loads(x).keys(),
|
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
|
||||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
|
|
||||||
ids = [j for i in _tmp for j in i]
|
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not self.ancestor_ids:
|
if not self.ancestor_ids:
|
||||||
@@ -92,12 +97,16 @@ class Search(object):
|
|||||||
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
|
||||||
prefix = REDIS_PREFIX_CI_RELATION2
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
|
if not key or id_filter_limit is None:
|
||||||
ids = [j for i in _tmp for j in i]
|
|
||||||
|
|
||||||
if not key:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
|
_tmp = [[i[0] for i in x if (not id_filter_limit or (
|
||||||
|
key[idx] not in id_filter_limit or int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
|
|
||||||
|
ids = [j for i in _tmp for j in i]
|
||||||
|
|
||||||
if level in self.level:
|
if level in self.level:
|
||||||
merge_ids.extend(ids)
|
merge_ids.extend(ids)
|
||||||
|
|
||||||
@@ -120,7 +129,28 @@ class Search(object):
|
|||||||
|
|
||||||
return merge_ids
|
return merge_ids
|
||||||
|
|
||||||
|
def _has_read_perm_from_parent_nodes(self):
|
||||||
|
self.root_parent_path = list(map(str, self.root_parent_path))
|
||||||
|
if str(self.root_id).isdigit() and str(self.root_id) not in self.root_parent_path:
|
||||||
|
self.root_parent_path.append(str(self.root_id))
|
||||||
|
self.root_parent_path = set(self.root_parent_path)
|
||||||
|
|
||||||
|
if self.is_app_admin:
|
||||||
|
self.type2filter_perms = {}
|
||||||
|
return True
|
||||||
|
|
||||||
|
res = ACLManager().get_resources(ResourceTypeEnum.CI_FILTER) or {}
|
||||||
|
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res]))) or {}
|
||||||
|
for _, filters in self.type2filter_perms.items():
|
||||||
|
if set((filters.get('id_filter') or {}).keys()) & self.root_parent_path:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
|
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
||||||
|
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||||
|
|
||||||
@@ -161,42 +191,105 @@ class Search(object):
|
|||||||
page=self.page,
|
page=self.page,
|
||||||
count=self.count,
|
count=self.count,
|
||||||
sort=self.sort,
|
sort=self.sort,
|
||||||
ci_ids=merge_ids).search()
|
ci_ids=merge_ids,
|
||||||
|
parent_node_perm_passed=parent_node_perm_passed,
|
||||||
|
use_ci_filter=use_ci_filter).search()
|
||||||
|
|
||||||
def statistics(self, type_ids):
|
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||||
|
ci_filters = ci_filters or []
|
||||||
|
if ci_filters:
|
||||||
|
result = {}
|
||||||
|
for item in ci_filters:
|
||||||
|
res = SearchFromDB('_type:{},{}'.format(item['type_id'], item['ci_filter']),
|
||||||
|
count=sys.maxsize, parent_node_perm_passed=True).get_ci_ids()
|
||||||
|
if res:
|
||||||
|
result[item['type_id']] = set(res)
|
||||||
|
|
||||||
|
return {}, result if result else None
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
if filter_perms.get('id_filter'):
|
||||||
|
for k in filter_perms['id_filter']:
|
||||||
|
node_path = k.split(',')
|
||||||
|
if len(node_path) == 1:
|
||||||
|
result[int(node_path[0])] = 1
|
||||||
|
elif not self.has_m2m:
|
||||||
|
result.setdefault(node_path[-2], set()).add(int(node_path[-1]))
|
||||||
|
else:
|
||||||
|
result.setdefault(','.join(node_path[:-1]), set()).add(int(node_path[-1]))
|
||||||
|
if result:
|
||||||
|
return result, None
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
return {}, None
|
||||||
|
|
||||||
|
def statistics(self, type_ids, need_filter=True):
|
||||||
self.level = int(self.level)
|
self.level = int(self.level)
|
||||||
|
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
|
||||||
|
type2filter_perms = dict()
|
||||||
|
if not self.is_app_admin:
|
||||||
|
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||||
|
if res2:
|
||||||
|
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||||
|
|
||||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||||
_tmp = []
|
_tmp = []
|
||||||
level2ids = {}
|
level2ids = {}
|
||||||
for lv in range(1, self.level + 1):
|
for lv in range(1, self.level + 1):
|
||||||
level2ids[lv] = []
|
level2ids[lv] = []
|
||||||
|
|
||||||
|
if need_filter:
|
||||||
|
id_filter_limit, ci_filter_limit = None, None
|
||||||
|
if len(self.descendant_ids or []) >= lv and type2filter_perms.get(self.descendant_ids[lv - 1]):
|
||||||
|
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[self.descendant_ids[lv - 1]])
|
||||||
|
elif type_ids and self.level == lv:
|
||||||
|
ci_filters = [type2filter_perms[type_id] for type_id in type_ids if type_id in type2filter_perms]
|
||||||
|
if ci_filters:
|
||||||
|
id_filter_limit, ci_filter_limit = self._get_ci_filter({}, ci_filters=ci_filters)
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
else:
|
||||||
|
id_filter_limit = {}
|
||||||
|
else:
|
||||||
|
id_filter_limit, ci_filter_limit = {}, {}
|
||||||
|
|
||||||
if lv == 1:
|
if lv == 1:
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||||
else:
|
|
||||||
if not self.ancestor_ids:
|
|
||||||
key, prefix = ids, REDIS_PREFIX_CI_RELATION
|
|
||||||
else:
|
else:
|
||||||
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
|
||||||
|
if not self.ancestor_ids:
|
||||||
|
key, prefix = [str(i) for i in ids], REDIS_PREFIX_CI_RELATION
|
||||||
|
else:
|
||||||
prefix = REDIS_PREFIX_CI_RELATION2
|
prefix = REDIS_PREFIX_CI_RELATION2
|
||||||
|
|
||||||
level2ids[lv] = [[i] for i in key]
|
level2ids[lv] = [[i] for i in key]
|
||||||
|
|
||||||
if not key:
|
if not key or id_filter_limit is None:
|
||||||
_tmp = []
|
_tmp = [[]] * len(ids)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
|
_tmp = []
|
||||||
if type_ids and lv == self.level:
|
if type_ids and lv == self.level:
|
||||||
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
|
_tmp = [[i for i in x if i[1] in type_ids and
|
||||||
(map(lambda x: list(json.loads(x).items()),
|
(not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||||
[i or '{}' for i in rd.get(key, prefix) or []]))))
|
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
else:
|
else:
|
||||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
_tmp = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||||
[i or '{}' for i in rd.get(key, prefix) or []]))
|
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
|
|
||||||
|
if ci_filter_limit:
|
||||||
|
_tmp = [[j for j in i if j[1] not in ci_filter_limit or int(j[0]) in ci_filter_limit[j[1]]]
|
||||||
|
for i in _tmp]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
for idx, item in enumerate(_tmp):
|
for idx, item in enumerate(_tmp):
|
||||||
if item:
|
if item:
|
||||||
if not self.has_m2m:
|
if not self.has_m2m:
|
||||||
@@ -208,18 +301,26 @@ class Search(object):
|
|||||||
level2ids[lv].append(key)
|
level2ids[lv].append(key)
|
||||||
|
|
||||||
if key:
|
if key:
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||||
if type_ids and lv == self.level:
|
if type_ids and lv == self.level:
|
||||||
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
__tmp = [[i for i in x if i[1] in type_ids and
|
||||||
if type_id in type_ids],
|
(not id_filter_limit or (
|
||||||
filter(lambda x: x is not None,
|
key[idx] not in id_filter_limit or
|
||||||
rd.get(key, prefix) or []))
|
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
else:
|
else:
|
||||||
__tmp = map(lambda x: list(json.loads(x).items()),
|
__tmp = [[i for i in x if (not id_filter_limit or (
|
||||||
filter(lambda x: x is not None,
|
key[idx] not in id_filter_limit or
|
||||||
rd.get(key, prefix) or []))
|
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||||
|
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||||
|
|
||||||
|
if ci_filter_limit:
|
||||||
|
__tmp = [[j for j in i if j[1] not in ci_filter_limit or
|
||||||
|
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp]
|
||||||
else:
|
else:
|
||||||
__tmp = []
|
__tmp = []
|
||||||
|
|
||||||
|
if __tmp:
|
||||||
_tmp[idx] = [j for i in __tmp for j in i]
|
_tmp[idx] = [j for i in __tmp for j in i]
|
||||||
else:
|
else:
|
||||||
_tmp[idx] = []
|
_tmp[idx] = []
|
||||||
|
@@ -11,12 +11,21 @@ import six
|
|||||||
import api.models.cmdb as model
|
import api.models.cmdb as model
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
|
||||||
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
|
||||||
|
|
||||||
|
|
||||||
|
class ValueDeserializeError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def string2int(x):
|
def string2int(x):
|
||||||
return int(float(x))
|
v = int(float(x))
|
||||||
|
if v > 2147483647:
|
||||||
|
raise ValueDeserializeError(ErrFormat.attribute_value_out_of_range)
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
def str2datetime(x):
|
def str2datetime(x):
|
||||||
|
@@ -5,14 +5,16 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import imp
|
import imp
|
||||||
import jinja2
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
import jinja2
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from jinja2schema import infer
|
from jinja2schema import infer
|
||||||
from jinja2schema import to_json_schema
|
from jinja2schema import to_json_schema
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.cmdb.attribute import AttributeManager
|
from api.lib.cmdb.attribute import AttributeManager
|
||||||
@@ -23,6 +25,7 @@ from api.lib.cmdb.const import ValueTypeEnum
|
|||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.utils import TableMap
|
from api.lib.cmdb.utils import TableMap
|
||||||
|
from api.lib.cmdb.utils import ValueDeserializeError
|
||||||
from api.lib.cmdb.utils import ValueTypeMap
|
from api.lib.cmdb.utils import ValueTypeMap
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
@@ -80,7 +83,7 @@ class AttributeValueManager(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _deserialize_value(value_type, value):
|
def _deserialize_value(alias, value_type, value):
|
||||||
if not value:
|
if not value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -88,6 +91,8 @@ class AttributeValueManager(object):
|
|||||||
try:
|
try:
|
||||||
v = deserialize(value)
|
v = deserialize(value)
|
||||||
return v
|
return v
|
||||||
|
except ValueDeserializeError as e:
|
||||||
|
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||||
|
|
||||||
@@ -124,7 +129,7 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||||
ci = ci or {}
|
ci = ci or {}
|
||||||
v = self._deserialize_value(attr.value_type, value)
|
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||||
|
|
||||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||||
attr.is_unique and self._check_is_unique(
|
attr.is_unique and self._check_is_unique(
|
||||||
@@ -240,6 +245,8 @@ class AttributeValueManager(object):
|
|||||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||||
type_attr=ci_attr2type_attr.get(attr.id))
|
type_attr=ci_attr2type_attr.get(attr.id))
|
||||||
ci_dict[key] = value
|
ci_dict[key] = value
|
||||||
|
except BadRequest as e:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.warning(str(e))
|
current_app.logger.warning(str(e))
|
||||||
|
|
||||||
@@ -302,9 +309,9 @@ class AttributeValueManager(object):
|
|||||||
return self.write_change2(changed)
|
return self.write_change2(changed)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_attr_value(attr_id, ci_id):
|
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||||
attr = AttributeCache.get(attr_id)
|
attr = AttributeCache.get(attr_id)
|
||||||
if attr is not None:
|
if attr is not None:
|
||||||
value_table = TableMap(attr=attr).table
|
value_table = TableMap(attr=attr).table
|
||||||
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
||||||
item.delete()
|
item.delete(commit=commit)
|
||||||
|
@@ -35,3 +35,32 @@ AuthCommonConfigAutoRedirect = 'auto_redirect'
|
|||||||
class TestType(BaseEnum):
|
class TestType(BaseEnum):
|
||||||
Connect = 'connect'
|
Connect = 'connect'
|
||||||
Login = 'login'
|
Login = 'login'
|
||||||
|
|
||||||
|
|
||||||
|
MIMEExtMap = {
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
|
||||||
|
'application/msword': '.doc',
|
||||||
|
'application/vnd.ms-word.document.macroEnabled.12': '.docm',
|
||||||
|
'application/vnd.ms-excel': '.xls',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
||||||
|
'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
|
||||||
|
'application/vnd.ms-powerpoint': '.ppt',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
|
||||||
|
'application/vnd.ms-powerpoint.presentation.macroEnabled.12': '.pptm',
|
||||||
|
'application/zip': '.zip',
|
||||||
|
'application/x-7z-compressed': '.7z',
|
||||||
|
'application/json': '.json',
|
||||||
|
'application/pdf': '.pdf',
|
||||||
|
'image/png': '.png',
|
||||||
|
'image/bmp': '.bmp',
|
||||||
|
'image/prs.btif': '.btif',
|
||||||
|
'image/gif': '.gif',
|
||||||
|
'image/jpeg': '.jpg',
|
||||||
|
'image/tiff': '.tif',
|
||||||
|
'image/vnd.microsoft.icon': '.ico',
|
||||||
|
'image/webp': '.webp',
|
||||||
|
'image/svg+xml': '.svg',
|
||||||
|
'image/vnd.adobe.photoshop': '.psd',
|
||||||
|
'text/plain': '.txt',
|
||||||
|
'text/csv': '.csv',
|
||||||
|
}
|
||||||
|
@@ -470,8 +470,58 @@ class EditDepartmentInACL(object):
|
|||||||
|
|
||||||
return f"edit_department_name_in_acl, rid: {d_rid}, success"
|
return f"edit_department_name_in_acl, rid: {d_rid}, success"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_from_old_department_role(cls, e_list, acl):
|
||||||
|
result = []
|
||||||
|
for employee in e_list:
|
||||||
|
employee_acl_rid = employee.get('e_acl_rid')
|
||||||
|
if employee_acl_rid == 0:
|
||||||
|
result.append(f"employee_acl_rid == 0")
|
||||||
|
continue
|
||||||
|
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def edit_employee_department_in_acl(e_list: list, new_d_id: int, op_uid: int):
|
def remove_single_employee_from_old_department(acl, employee, result):
|
||||||
|
from api.models.acl import Role
|
||||||
|
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
||||||
|
if not old_department:
|
||||||
|
return False
|
||||||
|
|
||||||
|
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
||||||
|
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
||||||
|
if old_d_rid_in_acl == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
||||||
|
payload = {
|
||||||
|
'app_id': 'acl',
|
||||||
|
'parent_id': d_acl_rid,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
|
||||||
|
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
|
||||||
|
except Exception as e:
|
||||||
|
result.append(
|
||||||
|
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result):
|
||||||
|
payload = {
|
||||||
|
'app_id': 'acl',
|
||||||
|
'child_ids': [employee_acl_rid],
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
acl.add_user_to_role(new_department_acl_rid, payload)
|
||||||
|
current_app.logger.info(f"add {employee_acl_rid} to {new_department_acl_rid}")
|
||||||
|
except Exception as e:
|
||||||
|
result.append(
|
||||||
|
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {new_department_acl_rid}, \
|
||||||
|
err: {e}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def edit_employee_department_in_acl(cls, e_list: list, new_d_id: int, op_uid: int):
|
||||||
result = []
|
result = []
|
||||||
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
|
||||||
if not new_department:
|
if not new_department:
|
||||||
@@ -481,7 +531,11 @@ class EditDepartmentInACL(object):
|
|||||||
from api.models.acl import Role
|
from api.models.acl import Role
|
||||||
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
|
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
|
||||||
new_d_rid_in_acl = new_role.get('id') if new_role else 0
|
new_d_rid_in_acl = new_role.get('id') if new_role else 0
|
||||||
|
acl = ACLManager('acl', str(op_uid))
|
||||||
|
|
||||||
if new_d_rid_in_acl == 0:
|
if new_d_rid_in_acl == 0:
|
||||||
|
# only remove from old department role
|
||||||
|
cls.remove_from_old_department_role(e_list, acl)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_d_rid_in_acl != new_department.acl_rid:
|
if new_d_rid_in_acl != new_department.acl_rid:
|
||||||
@@ -491,43 +545,15 @@ class EditDepartmentInACL(object):
|
|||||||
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
|
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
|
||||||
new_d_rid_in_acl
|
new_d_rid_in_acl
|
||||||
|
|
||||||
acl = ACLManager('acl', str(op_uid))
|
|
||||||
for employee in e_list:
|
for employee in e_list:
|
||||||
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
|
|
||||||
if not old_department:
|
|
||||||
continue
|
|
||||||
employee_acl_rid = employee.get('e_acl_rid')
|
employee_acl_rid = employee.get('e_acl_rid')
|
||||||
if employee_acl_rid == 0:
|
if employee_acl_rid == 0:
|
||||||
result.append(f"employee_acl_rid == 0")
|
result.append(f"employee_acl_rid == 0")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
|
cls.remove_single_employee_from_old_department(acl, employee, result)
|
||||||
old_d_rid_in_acl = old_role.get('id') if old_role else 0
|
|
||||||
if old_d_rid_in_acl == 0:
|
|
||||||
return
|
|
||||||
if old_d_rid_in_acl != old_department.acl_rid:
|
|
||||||
old_department.update(
|
|
||||||
acl_rid=old_d_rid_in_acl
|
|
||||||
)
|
|
||||||
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
|
||||||
payload = {
|
|
||||||
'app_id': 'acl',
|
|
||||||
'parent_id': d_acl_rid,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
acl.remove_user_from_role(employee_acl_rid, payload)
|
|
||||||
except Exception as e:
|
|
||||||
result.append(
|
|
||||||
f"remove_user_from_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
|
||||||
|
|
||||||
payload = {
|
# 在新部门中添加员工
|
||||||
'app_id': 'acl',
|
cls.add_employee_to_new_department(acl, employee_acl_rid, new_department_acl_rid, result)
|
||||||
'child_ids': [employee_acl_rid],
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
acl.add_user_to_role(new_department_acl_rid, payload)
|
|
||||||
except Exception as e:
|
|
||||||
result.append(
|
|
||||||
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@@ -148,10 +148,10 @@ class ACLManager(object):
|
|||||||
if group:
|
if group:
|
||||||
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild)
|
||||||
|
|
||||||
def del_resource(self, name, resource_type_name=None):
|
def del_resource(self, name, resource_type_name=None, rebuild=True):
|
||||||
resource = self._get_resource(name, resource_type_name)
|
resource = self._get_resource(name, resource_type_name)
|
||||||
if resource:
|
if resource:
|
||||||
return ResourceCRUD.delete(resource.id)
|
return ResourceCRUD.delete(resource.id, rebuild=rebuild)
|
||||||
|
|
||||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||||
if is_app_admin(self.app_id):
|
if is_app_admin(self.app_id):
|
||||||
|
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
|
import redis_lock
|
||||||
|
|
||||||
from api.extensions import cache
|
from api.extensions import cache
|
||||||
|
from api.extensions import rd
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.utils import Lock
|
|
||||||
from api.models.acl import App
|
from api.models.acl import App
|
||||||
from api.models.acl import Permission
|
from api.models.acl import Permission
|
||||||
from api.models.acl import Resource
|
from api.models.acl import Resource
|
||||||
@@ -136,14 +137,14 @@ class HasResourceRoleCache(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, rid, app_id):
|
def add(cls, rid, app_id):
|
||||||
with Lock('HasResourceRoleCache'):
|
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||||
c = cls.get(app_id)
|
c = cls.get(app_id)
|
||||||
c[rid] = 1
|
c[rid] = 1
|
||||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove(cls, rid, app_id):
|
def remove(cls, rid, app_id):
|
||||||
with Lock('HasResourceRoleCache'):
|
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||||
c = cls.get(app_id)
|
c = cls.get(app_id)
|
||||||
c.pop(rid, None)
|
c.pop(rid, None)
|
||||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||||
|
@@ -309,7 +309,7 @@ class ResourceCRUD(object):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(_id):
|
def delete(_id, rebuild=True):
|
||||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||||
|
|
||||||
origin = resource.to_dict()
|
origin = resource.to_dict()
|
||||||
@@ -322,6 +322,7 @@ class ResourceCRUD(object):
|
|||||||
i.soft_delete()
|
i.soft_delete()
|
||||||
rebuilds.append((i.rid, i.app_id))
|
rebuilds.append((i.rid, i.app_id))
|
||||||
|
|
||||||
|
if rebuild:
|
||||||
for rid, app_id in set(rebuilds):
|
for rid, app_id in set(rebuilds):
|
||||||
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE)
|
||||||
|
|
||||||
|
@@ -194,7 +194,7 @@ def validate(ticket):
|
|||||||
|
|
||||||
def _parse_tag(string, tag):
|
def _parse_tag(string, tag):
|
||||||
"""
|
"""
|
||||||
Used for parsing xml. Search string for the first occurence of
|
Used for parsing xml. Search string for the first occurrence of
|
||||||
<tag>.....</tag> and return text (stripped of leading and tailing
|
<tag>.....</tag> and return text (stripped of leading and tailing
|
||||||
whitespace) between tags. Return "" if tag not found.
|
whitespace) between tags. Return "" if tag not found.
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
import elasticsearch
|
import elasticsearch
|
||||||
@@ -213,52 +211,6 @@ class ESHandler(object):
|
|||||||
return 0, [], {}
|
return 0, [], {}
|
||||||
|
|
||||||
|
|
||||||
class Lock(object):
|
|
||||||
def __init__(self, name, timeout=10, app=None, need_lock=True):
|
|
||||||
self.lock_key = name
|
|
||||||
self.need_lock = need_lock
|
|
||||||
self.timeout = timeout
|
|
||||||
if not app:
|
|
||||||
app = current_app
|
|
||||||
self.app = app
|
|
||||||
try:
|
|
||||||
self.redis = redis.Redis(host=self.app.config.get('CACHE_REDIS_HOST'),
|
|
||||||
port=self.app.config.get('CACHE_REDIS_PORT'),
|
|
||||||
password=self.app.config.get('CACHE_REDIS_PASSWORD'))
|
|
||||||
except:
|
|
||||||
self.app.logger.error("cannot connect redis")
|
|
||||||
raise Exception("cannot connect redis")
|
|
||||||
|
|
||||||
def lock(self, timeout=None):
|
|
||||||
if not timeout:
|
|
||||||
timeout = self.timeout
|
|
||||||
retry = 0
|
|
||||||
while retry < 100:
|
|
||||||
timestamp = time.time() + timeout + 1
|
|
||||||
_lock = self.redis.setnx(self.lock_key, timestamp)
|
|
||||||
if _lock == 1 or (
|
|
||||||
time.time() > float(self.redis.get(self.lock_key) or sys.maxsize) and
|
|
||||||
time.time() > float(self.redis.getset(self.lock_key, timestamp) or sys.maxsize)):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
retry += 1
|
|
||||||
time.sleep(0.6)
|
|
||||||
if retry >= 100:
|
|
||||||
raise Exception("get lock failed...")
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if time.time() < float(self.redis.get(self.lock_key)):
|
|
||||||
self.redis.delete(self.lock_key)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.need_lock:
|
|
||||||
self.lock()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if self.need_lock:
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
|
|
||||||
class AESCrypto(object):
|
class AESCrypto(object):
|
||||||
BLOCK_SIZE = 16 # Bytes
|
BLOCK_SIZE = 16 # Bytes
|
||||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||||
|
@@ -460,6 +460,7 @@ class PreferenceRelationView(Model):
|
|||||||
name = db.Column(db.String(64), index=True, nullable=False)
|
name = db.Column(db.String(64), index=True, nullable=False)
|
||||||
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
cr_ids = db.Column(db.JSON) # [{parent_id: x, child_id: y}]
|
||||||
is_public = db.Column(db.Boolean, default=False)
|
is_public = db.Column(db.Boolean, default=False)
|
||||||
|
option = db.Column(db.JSON)
|
||||||
|
|
||||||
|
|
||||||
class PreferenceSearchOption(Model):
|
class PreferenceSearchOption(Model):
|
||||||
@@ -569,6 +570,7 @@ class CIFilterPerms(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'))
|
||||||
ci_filter = db.Column(db.Text)
|
ci_filter = db.Column(db.Text)
|
||||||
attr_filter = db.Column(db.Text)
|
attr_filter = db.Column(db.Text)
|
||||||
|
id_filter = db.Column(db.JSON) # {node_path: unique_value}
|
||||||
|
|
||||||
rid = db.Column(db.Integer, index=True)
|
rid = db.Column(db.Integer, index=True)
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
|
|
||||||
@@ -17,10 +18,10 @@ 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
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.decorator import flush_db
|
from api.lib.decorator import flush_db
|
||||||
from api.lib.decorator import reconnect_db
|
from api.lib.decorator import reconnect_db
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.utils import Lock
|
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
from api.models.cmdb import CIRelation
|
||||||
@@ -83,6 +84,13 @@ def ci_delete(ci_id):
|
|||||||
current_app.logger.info("{0} delete..........".format(ci_id))
|
current_app.logger.info("{0} delete..........".format(ci_id))
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(name="cmdb.delete_id_filter", queue=CMDB_QUEUE)
|
||||||
|
@reconnect_db
|
||||||
|
def delete_id_filter(ci_id):
|
||||||
|
|
||||||
|
CIFilterPermsCRUD().delete_id_filter_by_ci_id(ci_id)
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||||
@@ -99,7 +107,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
|||||||
@flush_db
|
@flush_db
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||||
if ancestor_ids is None:
|
if ancestor_ids is None:
|
||||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
@@ -177,7 +185,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||||
@reconnect_db
|
@reconnect_db
|
||||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||||
if ancestor_ids is None:
|
if ancestor_ids is None:
|
||||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||||
children = json.loads(children) if children is not None else {}
|
children = json.loads(children) if children is not None else {}
|
||||||
|
@@ -49,8 +49,7 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
old_d_rid_in_acl = role_map.get(old_department.department_name, 0)
|
||||||
if old_d_rid_in_acl == 0:
|
if old_d_rid_in_acl > 0:
|
||||||
return
|
|
||||||
if old_d_rid_in_acl != old_department.acl_rid:
|
if old_d_rid_in_acl != old_department.acl_rid:
|
||||||
old_department.update(
|
old_department.update(
|
||||||
acl_rid=old_d_rid_in_acl
|
acl_rid=old_d_rid_in_acl
|
||||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-03-01 13:49+0800\n"
|
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
|
||||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
@@ -322,7 +322,7 @@ msgstr "无效的属性值: {}"
|
|||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:94
|
#: api/lib/cmdb/resp_format.py:94
|
||||||
msgid "{} Invalid value: {}"
|
msgid "{} Invalid value: {}"
|
||||||
msgstr "无效的值: {}"
|
msgstr "{} 无效的值: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:95
|
#: api/lib/cmdb/resp_format.py:95
|
||||||
msgid "{} is not in the predefined values"
|
msgid "{} is not in the predefined values"
|
||||||
@@ -336,6 +336,10 @@ msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
|||||||
msgid "Attribute {} value must exist"
|
msgid "Attribute {} value must exist"
|
||||||
msgstr "属性 {} 值必须存在"
|
msgstr "属性 {} 值必须存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:99
|
||||||
|
msgid "Out of range value, the maximum value is 2147483647"
|
||||||
|
msgstr "超过最大值限制, 最大值是2147483647"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:101
|
#: api/lib/cmdb/resp_format.py:101
|
||||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||||
msgstr "新增或者修改属性值未知错误: {}"
|
msgstr "新增或者修改属性值未知错误: {}"
|
||||||
|
@@ -38,8 +38,9 @@ class LoginView(APIView):
|
|||||||
username = request.values.get("username") or request.values.get("email")
|
username = request.values.get("username") or request.values.get("email")
|
||||||
password = request.values.get("password")
|
password = request.values.get("password")
|
||||||
_role = None
|
_role = None
|
||||||
|
auth_with_ldap = request.values.get('auth_with_ldap', True)
|
||||||
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
|
||||||
if config.get('enabled') or config.get('enable'):
|
if (config.get('enabled') or config.get('enable')) and auth_with_ldap:
|
||||||
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
from api.lib.perm.authentication.ldap import authenticate_with_ldap
|
||||||
user, authenticated = authenticate_with_ldap(username, password)
|
user, authenticated = authenticate_with_ldap(username, password)
|
||||||
else:
|
else:
|
||||||
|
@@ -11,8 +11,7 @@ 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.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
from api.lib.cmdb.const import ExistPolicy
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
|
||||||
from api.lib.cmdb.const import RetKey
|
from api.lib.cmdb.const import RetKey
|
||||||
from api.lib.cmdb.perms import has_perm_for_ci
|
from api.lib.cmdb.perms import has_perm_for_ci
|
||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
@@ -152,9 +151,10 @@ class CISearchView(APIView):
|
|||||||
ret_key = RetKey.NAME
|
ret_key = RetKey.NAME
|
||||||
facet = handle_arg_list(request.values.get("facet", ""))
|
facet = handle_arg_list(request.values.get("facet", ""))
|
||||||
sort = request.values.get("sort")
|
sort = request.values.get("sort")
|
||||||
|
use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = search(query, fl, facet, page, ret_key, count, sort, excludes)
|
s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
|
||||||
try:
|
try:
|
||||||
response, counter, total, page, numfound, facet = s.search()
|
response, counter, total, page, numfound, facet = s.search()
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
|
@@ -13,7 +13,6 @@ from api.lib.cmdb.resp_format import ErrFormat
|
|||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
from api.lib.cmdb.search.ci_relation.search import Search
|
from api.lib.cmdb.search.ci_relation.search import Search
|
||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.perm.auth import auth_abandoned
|
|
||||||
from api.lib.utils import get_page
|
from api.lib.utils import get_page
|
||||||
from api.lib.utils import get_page_size
|
from api.lib.utils import get_page_size
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
@@ -36,6 +35,8 @@ class CIRelationSearchView(APIView):
|
|||||||
|
|
||||||
root_id = request.values.get('root_id')
|
root_id = request.values.get('root_id')
|
||||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
|
root_parent_path = handle_arg_list(request.values.get('root_parent_path') or '')
|
||||||
|
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||||
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
|
||||||
|
|
||||||
query = request.values.get('q', "")
|
query = request.values.get('q', "")
|
||||||
@@ -47,7 +48,8 @@ class CIRelationSearchView(APIView):
|
|||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse,
|
||||||
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
ancestor_ids=ancestor_ids, has_m2m=has_m2m, root_parent_path=root_parent_path,
|
||||||
|
descendant_ids=descendant_ids)
|
||||||
try:
|
try:
|
||||||
response, counter, total, page, numfound, facet = s.search()
|
response, counter, total, page, numfound, facet = s.search()
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
@@ -65,16 +67,16 @@ class CIRelationSearchView(APIView):
|
|||||||
class CIRelationStatisticsView(APIView):
|
class CIRelationStatisticsView(APIView):
|
||||||
url_prefix = "/ci_relations/statistics"
|
url_prefix = "/ci_relations/statistics"
|
||||||
|
|
||||||
@auth_abandoned
|
|
||||||
def get(self):
|
def get(self):
|
||||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||||
level = request.values.get('level', 1)
|
level = request.values.get('level', 1)
|
||||||
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||||
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
|
||||||
|
descendant_ids = list(map(int, handle_arg_list(request.values.get('descendant_ids', []))))
|
||||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m)
|
s = Search(root_ids, level, ancestor_ids=ancestor_ids, descendant_ids=descendant_ids, has_m2m=has_m2m)
|
||||||
try:
|
try:
|
||||||
result = s.statistics(type_ids)
|
result = s.statistics(type_ids)
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
|
@@ -38,9 +38,13 @@ from api.resource import APIView
|
|||||||
|
|
||||||
|
|
||||||
class CITypeView(APIView):
|
class CITypeView(APIView):
|
||||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>")
|
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||||
|
"/ci_types/icons")
|
||||||
|
|
||||||
def get(self, type_id=None, type_name=None):
|
def get(self, type_id=None, type_name=None):
|
||||||
|
if request.url.endswith("icons"):
|
||||||
|
return self.jsonify(CITypeManager().get_icons())
|
||||||
|
|
||||||
q = request.args.get("type_name")
|
q = request.args.get("type_name")
|
||||||
|
|
||||||
if type_id is not None:
|
if type_id is not None:
|
||||||
@@ -490,13 +494,14 @@ class CITypeGrantView(APIView):
|
|||||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||||
|
|
||||||
|
if perms and not request.values.get('id_filter'):
|
||||||
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||||
|
|
||||||
resource = None
|
new_resource = None
|
||||||
if 'ci_filter' in request.values or 'attr_filter' in request.values:
|
if 'ci_filter' in request.values or 'attr_filter' in request.values or 'id_filter' in request.values:
|
||||||
resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
new_resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
|
||||||
|
|
||||||
if not resource:
|
if not new_resource:
|
||||||
from api.tasks.acl import role_rebuild
|
from api.tasks.acl import role_rebuild
|
||||||
from api.lib.perm.acl.const import ACL_QUEUE
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
|
|
||||||
@@ -522,10 +527,18 @@ class CITypeRevokeView(APIView):
|
|||||||
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
if not acl.has_permission(type_name, ResourceTypeEnum.CI_TYPE, PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||||
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
|
||||||
|
|
||||||
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
|
||||||
|
|
||||||
app_id = AppCache.get('cmdb').id
|
app_id = AppCache.get('cmdb').id
|
||||||
resource = None
|
resource = None
|
||||||
|
|
||||||
|
if request.values.get('id_filter'):
|
||||||
|
CIFilterPermsCRUD().delete2(
|
||||||
|
type_id=type_id, rid=rid, id_filter=request.values['id_filter'],
|
||||||
|
parent_path=request.values.get('parent_path'))
|
||||||
|
|
||||||
|
return self.jsonify(type_id=type_id, rid=rid)
|
||||||
|
|
||||||
|
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False)
|
||||||
|
|
||||||
if PermEnum.READ in perms or not perms:
|
if PermEnum.READ in perms or not perms:
|
||||||
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
|
||||||
|
|
||||||
|
@@ -97,7 +97,7 @@ class PreferenceTreeApiView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class PreferenceRelationApiView(APIView):
|
class PreferenceRelationApiView(APIView):
|
||||||
url_prefix = "/preference/relation/view"
|
url_prefix = ("/preference/relation/view", "/preference/relation/view/<int:_id>")
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
views, id2type, name2id = PreferenceManager.get_relation_view()
|
views, id2type, name2id = PreferenceManager.get_relation_view()
|
||||||
@@ -110,14 +110,20 @@ class PreferenceRelationApiView(APIView):
|
|||||||
@args_validate(PreferenceManager.pref_rel_cls)
|
@args_validate(PreferenceManager.pref_rel_cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
name = request.values.get("name")
|
name = request.values.get("name")
|
||||||
|
is_public = request.values.get("is_public") in current_app.config.get('BOOL_TRUE')
|
||||||
cr_ids = request.values.get("cr_ids")
|
cr_ids = request.values.get("cr_ids")
|
||||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids)
|
option = request.values.get("option") or None
|
||||||
|
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(name, cr_ids, is_public=is_public,
|
||||||
|
option=option)
|
||||||
|
|
||||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||||
|
|
||||||
@role_required(RoleEnum.CONFIG)
|
@role_required(RoleEnum.CONFIG)
|
||||||
def put(self):
|
@args_required("name")
|
||||||
return self.post()
|
def put(self, _id):
|
||||||
|
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||||
|
|
||||||
@role_required(RoleEnum.CONFIG)
|
@role_required(RoleEnum.CONFIG)
|
||||||
@args_required("name")
|
@args_required("name")
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import os
|
from flask import request, abort, current_app
|
||||||
|
|
||||||
from flask import request, abort, current_app, send_from_directory
|
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import lz4.frame
|
import lz4.frame
|
||||||
|
import magic
|
||||||
|
|
||||||
|
from api.lib.common_setting.const import MIMEExtMap
|
||||||
from api.lib.common_setting.resp_format import ErrFormat
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
@@ -45,18 +45,23 @@ class PostFileView(APIView):
|
|||||||
|
|
||||||
if not file:
|
if not file:
|
||||||
abort(400, ErrFormat.file_is_required)
|
abort(400, ErrFormat.file_is_required)
|
||||||
extension = file.mimetype.split('/')[-1]
|
|
||||||
if '+' in extension:
|
|
||||||
extension = file.filename.split('.')[-1]
|
|
||||||
if file.filename == '':
|
|
||||||
filename = f'.{extension}'
|
|
||||||
else:
|
|
||||||
if extension not in file.filename:
|
|
||||||
filename = file.filename + f".{extension}"
|
|
||||||
else:
|
|
||||||
filename = file.filename
|
|
||||||
|
|
||||||
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
|
m_type = magic.from_buffer(file.read(2048), mime=True)
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
if m_type == 'application/octet-stream':
|
||||||
|
m_type = file.mimetype
|
||||||
|
elif m_type == 'text/plain':
|
||||||
|
# https://github.com/ahupp/python-magic/issues/193
|
||||||
|
m_type = m_type if file.mimetype == m_type else file.mimetype
|
||||||
|
|
||||||
|
extension = MIMEExtMap.get(m_type, None)
|
||||||
|
|
||||||
|
if extension is None:
|
||||||
|
abort(400, f"不支持的文件类型: {m_type}")
|
||||||
|
|
||||||
|
filename = file.filename if file.filename and file.filename.endswith(extension) else file.filename + extension
|
||||||
|
|
||||||
new_filename = generate_new_file_name(filename)
|
new_filename = generate_new_file_name(filename)
|
||||||
new_filename = secure_filename(new_filename)
|
new_filename = secure_filename(new_filename)
|
||||||
file_content = file.read()
|
file_content = file.read()
|
||||||
@@ -72,5 +77,3 @@ class PostFileView(APIView):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
abort(400, ErrFormat.upload_failed.format(e))
|
abort(400, ErrFormat.upload_failed.format(e))
|
||||||
|
|
||||||
abort(400, ErrFormat.file_type_not_allowed.format(filename))
|
|
||||||
|
@@ -37,6 +37,7 @@ PyMySQL==1.1.0
|
|||||||
ldap3==2.9.1
|
ldap3==2.9.1
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
redis==4.6.0
|
redis==4.6.0
|
||||||
|
python-redis-lock==4.0.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
requests_oauthlib==1.3.1
|
requests_oauthlib==1.3.1
|
||||||
markdownify==0.11.6
|
markdownify==0.11.6
|
||||||
@@ -52,3 +53,4 @@ shamir~=17.12.0
|
|||||||
pycryptodomex>=3.19.0
|
pycryptodomex>=3.19.0
|
||||||
colorama>=0.4.6
|
colorama>=0.4.6
|
||||||
lz4>=4.3.2
|
lz4>=4.3.2
|
||||||
|
python-magic==0.4.27
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3857903 */
|
font-family: "iconfont"; /* Project id 3857903 */
|
||||||
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
|
src: url('iconfont.woff2?t=1711618200417') format('woff2'),
|
||||||
url('iconfont.woff?t=1702544951995') format('woff'),
|
url('iconfont.woff?t=1711618200417') format('woff'),
|
||||||
url('iconfont.ttf?t=1702544951995') format('truetype');
|
url('iconfont.ttf?t=1711618200417') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,222 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Google_Cloud_Platform:before {
|
||||||
|
content: "\e90b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Ctyun:before {
|
||||||
|
content: "\e90c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Alibaba_Cloud:before {
|
||||||
|
content: "\e90d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Azure:before {
|
||||||
|
content: "\e90e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ZStack:before {
|
||||||
|
content: "\e904";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tencent_Cloud:before {
|
||||||
|
content: "\e905";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Nutanix:before {
|
||||||
|
content: "\e906";
|
||||||
|
}
|
||||||
|
|
||||||
|
.OpenStack:before {
|
||||||
|
content: "\e907";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Huawei_Cloud:before {
|
||||||
|
content: "\e908";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Bytecloud:before {
|
||||||
|
content: "\e909";
|
||||||
|
}
|
||||||
|
|
||||||
|
.UCloud:before {
|
||||||
|
content: "\e90a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.AWS:before {
|
||||||
|
content: "\e901";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ECloud:before {
|
||||||
|
content: "\e902";
|
||||||
|
}
|
||||||
|
|
||||||
|
.JDCloud:before {
|
||||||
|
content: "\e903";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-more:before {
|
||||||
|
content: "\e900";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-date:before {
|
||||||
|
content: "\e8ff";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-shishu:before {
|
||||||
|
content: "\e8fd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-wenben:before {
|
||||||
|
content: "\e8fe";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-json:before {
|
||||||
|
content: "\e8f7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-fudianshu:before {
|
||||||
|
content: "\e8f8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-time:before {
|
||||||
|
content: "\e8f9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-password:before {
|
||||||
|
content: "\e8fa";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-link:before {
|
||||||
|
content: "\e8fb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.duose-datetime:before {
|
||||||
|
content: "\e8fc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-setting2:before {
|
||||||
|
content: "\e8f6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-search:before {
|
||||||
|
content: "\e8f5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-delete:before {
|
||||||
|
content: "\e8f4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-refresh:before {
|
||||||
|
content: "\e8f3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-filter:before {
|
||||||
|
content: "\e8f2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-reduce:before {
|
||||||
|
content: "\e8ed";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-increase:before {
|
||||||
|
content: "\e8ee";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-configuration_table:before {
|
||||||
|
content: "\e8ef";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-copy:before {
|
||||||
|
content: "\e8f0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-save:before {
|
||||||
|
content: "\e8f1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-setting:before {
|
||||||
|
content: "\e8ec";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-default_avatar:before {
|
||||||
|
content: "\e8ea";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-notice:before {
|
||||||
|
content: "\e8eb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-quickStart:before {
|
||||||
|
content: "\e8e9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-associatedWith:before {
|
||||||
|
content: "\e8e8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-folder:before {
|
||||||
|
content: "\e8e7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.report:before {
|
||||||
|
content: "\e8e5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder:before {
|
||||||
|
content: "\e8e6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-refresh:before {
|
||||||
|
content: "\e8e4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-add_table:before {
|
||||||
|
content: "\e8e2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-delete_page:before {
|
||||||
|
content: "\e8e3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-secret_key:before {
|
||||||
|
content: "\e8e0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-password:before {
|
||||||
|
content: "\e8e1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-sla_timeout_not_handled:before {
|
||||||
|
content: "\e8dd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-sla_not_timeout:before {
|
||||||
|
content: "\e8de";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-SLA:before {
|
||||||
|
content: "\e8df";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-sla_timeout_handled:before {
|
||||||
|
content: "\e8dc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-sla_all:before {
|
||||||
|
content: "\e8da";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-generate_by_node_id:before {
|
||||||
|
content: "\e8db";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmdb-MySQL:before {
|
||||||
|
content: "\e8d9";
|
||||||
|
}
|
||||||
|
|
||||||
.OAUTH2:before {
|
.OAUTH2:before {
|
||||||
content: "\e8d8";
|
content: "\e8d8";
|
||||||
}
|
}
|
||||||
@@ -33,7 +249,7 @@
|
|||||||
content: "\e8d4";
|
content: "\e8d4";
|
||||||
}
|
}
|
||||||
|
|
||||||
.a-itsm-knowledge2:before {
|
.itsm-knowledge2:before {
|
||||||
content: "\e8d2";
|
content: "\e8d2";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,384 @@
|
|||||||
"css_prefix_text": "",
|
"css_prefix_text": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "39729980",
|
||||||
|
"name": "Google Cloud Platform",
|
||||||
|
"font_class": "Google_Cloud_Platform",
|
||||||
|
"unicode": "e90b",
|
||||||
|
"unicode_decimal": 59659
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729978",
|
||||||
|
"name": "Ctyun",
|
||||||
|
"font_class": "Ctyun",
|
||||||
|
"unicode": "e90c",
|
||||||
|
"unicode_decimal": 59660
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729977",
|
||||||
|
"name": "Alibaba Cloud",
|
||||||
|
"font_class": "Alibaba_Cloud",
|
||||||
|
"unicode": "e90d",
|
||||||
|
"unicode_decimal": 59661
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729976",
|
||||||
|
"name": "Azure",
|
||||||
|
"font_class": "Azure",
|
||||||
|
"unicode": "e90e",
|
||||||
|
"unicode_decimal": 59662
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729985",
|
||||||
|
"name": "ZStack",
|
||||||
|
"font_class": "ZStack",
|
||||||
|
"unicode": "e904",
|
||||||
|
"unicode_decimal": 59652
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729986",
|
||||||
|
"name": "Tencent Cloud",
|
||||||
|
"font_class": "Tencent_Cloud",
|
||||||
|
"unicode": "e905",
|
||||||
|
"unicode_decimal": 59653
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729981",
|
||||||
|
"name": "Nutanix",
|
||||||
|
"font_class": "Nutanix",
|
||||||
|
"unicode": "e906",
|
||||||
|
"unicode_decimal": 59654
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729983",
|
||||||
|
"name": "OpenStack",
|
||||||
|
"font_class": "OpenStack",
|
||||||
|
"unicode": "e907",
|
||||||
|
"unicode_decimal": 59655
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729982",
|
||||||
|
"name": "Huawei Cloud",
|
||||||
|
"font_class": "Huawei_Cloud",
|
||||||
|
"unicode": "e908",
|
||||||
|
"unicode_decimal": 59656
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729979",
|
||||||
|
"name": "Bytecloud",
|
||||||
|
"font_class": "Bytecloud",
|
||||||
|
"unicode": "e909",
|
||||||
|
"unicode_decimal": 59657
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729984",
|
||||||
|
"name": "UCloud",
|
||||||
|
"font_class": "UCloud",
|
||||||
|
"unicode": "e90a",
|
||||||
|
"unicode_decimal": 59658
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729988",
|
||||||
|
"name": "AWS",
|
||||||
|
"font_class": "AWS",
|
||||||
|
"unicode": "e901",
|
||||||
|
"unicode_decimal": 59649
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729989",
|
||||||
|
"name": "ECloud",
|
||||||
|
"font_class": "ECloud",
|
||||||
|
"unicode": "e902",
|
||||||
|
"unicode_decimal": 59650
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39729987",
|
||||||
|
"name": "JDCloud",
|
||||||
|
"font_class": "JDCloud",
|
||||||
|
"unicode": "e903",
|
||||||
|
"unicode_decimal": 59651
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39713390",
|
||||||
|
"name": "veops-more",
|
||||||
|
"font_class": "veops-more",
|
||||||
|
"unicode": "e900",
|
||||||
|
"unicode_decimal": 59648
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712276",
|
||||||
|
"name": "duose-date",
|
||||||
|
"font_class": "duose-date",
|
||||||
|
"unicode": "e8ff",
|
||||||
|
"unicode_decimal": 59647
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712289",
|
||||||
|
"name": "duose-shishu",
|
||||||
|
"font_class": "duose-shishu",
|
||||||
|
"unicode": "e8fd",
|
||||||
|
"unicode_decimal": 59645
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712286",
|
||||||
|
"name": "duose-wenben",
|
||||||
|
"font_class": "duose-wenben",
|
||||||
|
"unicode": "e8fe",
|
||||||
|
"unicode_decimal": 59646
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712314",
|
||||||
|
"name": "duose-json",
|
||||||
|
"font_class": "duose-json",
|
||||||
|
"unicode": "e8f7",
|
||||||
|
"unicode_decimal": 59639
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712315",
|
||||||
|
"name": "duose-fudianshu",
|
||||||
|
"font_class": "duose-fudianshu",
|
||||||
|
"unicode": "e8f8",
|
||||||
|
"unicode_decimal": 59640
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712312",
|
||||||
|
"name": "duose-time",
|
||||||
|
"font_class": "duose-time",
|
||||||
|
"unicode": "e8f9",
|
||||||
|
"unicode_decimal": 59641
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712313",
|
||||||
|
"name": "duose-password",
|
||||||
|
"font_class": "duose-password",
|
||||||
|
"unicode": "e8fa",
|
||||||
|
"unicode_decimal": 59642
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712311",
|
||||||
|
"name": "duose-link",
|
||||||
|
"font_class": "duose-link",
|
||||||
|
"unicode": "e8fb",
|
||||||
|
"unicode_decimal": 59643
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39712310",
|
||||||
|
"name": "duose-datetime",
|
||||||
|
"font_class": "duose-datetime",
|
||||||
|
"unicode": "e8fc",
|
||||||
|
"unicode_decimal": 59644
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39705895",
|
||||||
|
"name": "veops-setting2",
|
||||||
|
"font_class": "veops-setting2",
|
||||||
|
"unicode": "e8f6",
|
||||||
|
"unicode_decimal": 59638
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39692404",
|
||||||
|
"name": "veops-search",
|
||||||
|
"font_class": "veops-search",
|
||||||
|
"unicode": "e8f5",
|
||||||
|
"unicode_decimal": 59637
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39680289",
|
||||||
|
"name": "veops-delete",
|
||||||
|
"font_class": "veops-delete",
|
||||||
|
"unicode": "e8f4",
|
||||||
|
"unicode_decimal": 59636
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677378",
|
||||||
|
"name": "veops-refresh",
|
||||||
|
"font_class": "veops-refresh",
|
||||||
|
"unicode": "e8f3",
|
||||||
|
"unicode_decimal": 59635
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677152",
|
||||||
|
"name": "veops-filter",
|
||||||
|
"font_class": "veops-filter",
|
||||||
|
"unicode": "e8f2",
|
||||||
|
"unicode_decimal": 59634
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677216",
|
||||||
|
"name": "veops-reduce",
|
||||||
|
"font_class": "veops-reduce",
|
||||||
|
"unicode": "e8ed",
|
||||||
|
"unicode_decimal": 59629
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677215",
|
||||||
|
"name": "veops-increase",
|
||||||
|
"font_class": "veops-increase",
|
||||||
|
"unicode": "e8ee",
|
||||||
|
"unicode_decimal": 59630
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677211",
|
||||||
|
"name": "veops-configuration_table",
|
||||||
|
"font_class": "veops-configuration_table",
|
||||||
|
"unicode": "e8ef",
|
||||||
|
"unicode_decimal": 59631
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677210",
|
||||||
|
"name": "veops-copy",
|
||||||
|
"font_class": "veops-copy",
|
||||||
|
"unicode": "e8f0",
|
||||||
|
"unicode_decimal": 59632
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39677207",
|
||||||
|
"name": "veops-save",
|
||||||
|
"font_class": "veops-save",
|
||||||
|
"unicode": "e8f1",
|
||||||
|
"unicode_decimal": 59633
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39664281",
|
||||||
|
"name": "veops-setting",
|
||||||
|
"font_class": "veops-setting",
|
||||||
|
"unicode": "e8ec",
|
||||||
|
"unicode_decimal": 59628
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39664295",
|
||||||
|
"name": "veops-default_avatar",
|
||||||
|
"font_class": "veops-default_avatar",
|
||||||
|
"unicode": "e8ea",
|
||||||
|
"unicode_decimal": 59626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39664288",
|
||||||
|
"name": "veops-notice",
|
||||||
|
"font_class": "veops-notice",
|
||||||
|
"unicode": "e8eb",
|
||||||
|
"unicode_decimal": 59627
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39603135",
|
||||||
|
"name": "quickly_initiate",
|
||||||
|
"font_class": "itsm-quickStart",
|
||||||
|
"unicode": "e8e9",
|
||||||
|
"unicode_decimal": 59625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39588554",
|
||||||
|
"name": "itsm-associated",
|
||||||
|
"font_class": "itsm-associatedWith",
|
||||||
|
"unicode": "e8e8",
|
||||||
|
"unicode_decimal": 59624
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39517726",
|
||||||
|
"name": "itsm-folder",
|
||||||
|
"font_class": "itsm-folder",
|
||||||
|
"unicode": "e8e7",
|
||||||
|
"unicode_decimal": 59623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39517542",
|
||||||
|
"name": "report",
|
||||||
|
"font_class": "report",
|
||||||
|
"unicode": "e8e5",
|
||||||
|
"unicode_decimal": 59621
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39517539",
|
||||||
|
"name": "folder",
|
||||||
|
"font_class": "folder",
|
||||||
|
"unicode": "e8e6",
|
||||||
|
"unicode_decimal": 59622
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39123642",
|
||||||
|
"name": "itsm-refresh (1)",
|
||||||
|
"font_class": "itsm-refresh",
|
||||||
|
"unicode": "e8e4",
|
||||||
|
"unicode_decimal": 59620
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39123405",
|
||||||
|
"name": "itsm-add_table (1)",
|
||||||
|
"font_class": "itsm-add_table",
|
||||||
|
"unicode": "e8e2",
|
||||||
|
"unicode_decimal": 59618
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39123409",
|
||||||
|
"name": "itsm-delete_page",
|
||||||
|
"font_class": "itsm-delete_page",
|
||||||
|
"unicode": "e8e3",
|
||||||
|
"unicode_decimal": 59619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39117681",
|
||||||
|
"name": "oneterm-secret_key",
|
||||||
|
"font_class": "oneterm-secret_key",
|
||||||
|
"unicode": "e8e0",
|
||||||
|
"unicode_decimal": 59616
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39117679",
|
||||||
|
"name": "oneterm-password",
|
||||||
|
"font_class": "oneterm-password",
|
||||||
|
"unicode": "e8e1",
|
||||||
|
"unicode_decimal": 59617
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39079529",
|
||||||
|
"name": "itsm-unprocessed",
|
||||||
|
"font_class": "itsm-sla_timeout_not_handled",
|
||||||
|
"unicode": "e8dd",
|
||||||
|
"unicode_decimal": 59613
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39079522",
|
||||||
|
"name": "itsm-not_timeout",
|
||||||
|
"font_class": "itsm-sla_not_timeout",
|
||||||
|
"unicode": "e8de",
|
||||||
|
"unicode_decimal": 59614
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39079520",
|
||||||
|
"name": "itsm-SLA",
|
||||||
|
"font_class": "itsm-SLA",
|
||||||
|
"unicode": "e8df",
|
||||||
|
"unicode_decimal": 59615
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39079538",
|
||||||
|
"name": "itsm-processed",
|
||||||
|
"font_class": "itsm-sla_timeout_handled",
|
||||||
|
"unicode": "e8dc",
|
||||||
|
"unicode_decimal": 59612
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39079519",
|
||||||
|
"name": "itsm-all_SLA",
|
||||||
|
"font_class": "itsm-sla_all",
|
||||||
|
"unicode": "e8da",
|
||||||
|
"unicode_decimal": 59610
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "38970103",
|
||||||
|
"name": "itsm-generate_by_node_id",
|
||||||
|
"font_class": "itsm-generate_by_node_id",
|
||||||
|
"unicode": "e8db",
|
||||||
|
"unicode_decimal": 59611
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "38806676",
|
||||||
|
"name": "cmdb-MySQL",
|
||||||
|
"font_class": "cmdb-MySQL",
|
||||||
|
"unicode": "e8d9",
|
||||||
|
"unicode_decimal": 59609
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "38566548",
|
"icon_id": "38566548",
|
||||||
"name": "OAuth2.0",
|
"name": "OAuth2.0",
|
||||||
@@ -43,7 +421,7 @@
|
|||||||
{
|
{
|
||||||
"icon_id": "38533133",
|
"icon_id": "38533133",
|
||||||
"name": "itsm-knowledge (2)",
|
"name": "itsm-knowledge (2)",
|
||||||
"font_class": "a-itsm-knowledge2",
|
"font_class": "itsm-knowledge2",
|
||||||
"unicode": "e8d2",
|
"unicode": "e8d2",
|
||||||
"unicode_decimal": 59602
|
"unicode_decimal": 59602
|
||||||
},
|
},
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -30,9 +30,9 @@ export function getAuthDataEnable() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testLDAP(test_type, data) {
|
export function testLDAP(data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
|
url: `/common-setting/v1/auth_config/LDAP/test`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -20,6 +20,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:title="node.label"
|
:title="node.label"
|
||||||
@@ -80,6 +82,7 @@
|
|||||||
@select="(value) => handleChangeExp(value, item, index)"
|
@select="(value) => handleChangeExp(value, item, index)"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
<treeselect
|
<treeselect
|
||||||
@@ -103,6 +106,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:title="node.label"
|
:title="node.label"
|
||||||
@@ -125,6 +129,7 @@
|
|||||||
v-model="item.min"
|
v-model="item.min"
|
||||||
:style="{ width: '78px' }"
|
:style="{ width: '78px' }"
|
||||||
:placeholder="$t('min')"
|
:placeholder="$t('min')"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
~
|
~
|
||||||
<a-input
|
<a-input
|
||||||
@@ -133,6 +138,7 @@
|
|||||||
v-model="item.max"
|
v-model="item.max"
|
||||||
:style="{ width: '78px' }"
|
:style="{ width: '78px' }"
|
||||||
:placeholder="$t('max')"
|
:placeholder="$t('max')"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||||
@@ -155,6 +161,7 @@
|
|||||||
"
|
"
|
||||||
appendToBody
|
appendToBody
|
||||||
:zIndex="1050"
|
:zIndex="1050"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</treeselect>
|
</treeselect>
|
||||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||||
@@ -166,10 +173,12 @@
|
|||||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||||
class="ops-input"
|
class="ops-input"
|
||||||
:style="{ width: '175px' }"
|
:style="{ width: '175px' }"
|
||||||
|
:disabled="disabled"
|
||||||
></a-input>
|
></a-input>
|
||||||
<div v-else :style="{ width: '175px' }"></div>
|
<div v-else :style="{ width: '175px' }"></div>
|
||||||
|
<template v-if="!disabled">
|
||||||
<a-tooltip :title="$t('copy')">
|
<a-tooltip :title="$t('copy')">
|
||||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip :title="$t('delete')">
|
<a-tooltip :title="$t('delete')">
|
||||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||||
@@ -177,8 +186,9 @@
|
|||||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
</a-space>
|
</a-space>
|
||||||
<div class="table-filter-add">
|
<div class="table-filter-add" v-if="!disabled">
|
||||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,6 +221,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
:needAddHere="needAddHere"
|
:needAddHere="needAddHere"
|
||||||
v-model="ruleList"
|
v-model="ruleList"
|
||||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
<a-divider :style="{ margin: '10px 0' }" />
|
<a-divider :style="{ margin: '10px 0' }" />
|
||||||
<div style="width:554px">
|
<div style="width:554px">
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
v-else
|
v-else
|
||||||
v-model="ruleList"
|
v-model="ruleList"
|
||||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||||
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -69,6 +71,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -17,25 +17,29 @@ export default {
|
|||||||
getPropertyIcon(attr) {
|
getPropertyIcon(attr) {
|
||||||
switch (attr.value_type) {
|
switch (attr.value_type) {
|
||||||
case '0':
|
case '0':
|
||||||
return 'icon-xianxing-shishu'
|
return 'duose-shishu'
|
||||||
case '1':
|
case '1':
|
||||||
return 'icon-xianxing-fudianshu'
|
return 'duose-fudianshu'
|
||||||
case '2':
|
case '2':
|
||||||
if (attr.is_password) {
|
if (attr.is_password) {
|
||||||
return 'icon-xianxing-password'
|
return 'duose-password'
|
||||||
}
|
}
|
||||||
if (attr.is_link) {
|
if (attr.is_link) {
|
||||||
return 'icon-xianxing-link'
|
return 'duose-link'
|
||||||
}
|
}
|
||||||
return 'icon-xianxing-wenben'
|
return 'duose-wenben'
|
||||||
case '3':
|
case '3':
|
||||||
return 'icon-xianxing-datetime'
|
return 'duose-datetime'
|
||||||
case '4':
|
case '4':
|
||||||
return 'icon-xianxing-date'
|
return 'duose-date'
|
||||||
case '5':
|
case '5':
|
||||||
return 'icon-xianxing-time'
|
return 'duose-time'
|
||||||
case '6':
|
case '6':
|
||||||
return 'icon-xianxing-json'
|
return 'duose-json'
|
||||||
|
case '7':
|
||||||
|
return 'duose-password'
|
||||||
|
case '8':
|
||||||
|
return 'duose-link'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -759,6 +759,52 @@ export const multicolorIconList = [
|
|||||||
value: 'caise-redis',
|
value: 'caise-redis',
|
||||||
label: 'redis'
|
label: 'redis'
|
||||||
}]
|
}]
|
||||||
|
}, {
|
||||||
|
value: 'cloud',
|
||||||
|
label: '云',
|
||||||
|
list: [{
|
||||||
|
value: 'AWS',
|
||||||
|
label: 'AWS'
|
||||||
|
}, {
|
||||||
|
value: 'Azure',
|
||||||
|
label: 'Azure'
|
||||||
|
}, {
|
||||||
|
value: 'Google_Cloud_Platform',
|
||||||
|
label: 'Google Cloud Platform'
|
||||||
|
}, {
|
||||||
|
value: 'Alibaba_Cloud',
|
||||||
|
label: '阿里云'
|
||||||
|
}, {
|
||||||
|
value: 'Huawei_Cloud',
|
||||||
|
label: '华为云'
|
||||||
|
}, {
|
||||||
|
value: 'Tencent_Cloud',
|
||||||
|
label: '腾讯云'
|
||||||
|
}, {
|
||||||
|
value: 'UCloud',
|
||||||
|
label: 'UCloud'
|
||||||
|
}, {
|
||||||
|
value: 'Ctyun',
|
||||||
|
label: '天翼云'
|
||||||
|
}, {
|
||||||
|
value: 'ECloud',
|
||||||
|
label: '移动云'
|
||||||
|
}, {
|
||||||
|
value: 'JDCloud',
|
||||||
|
label: '京东云'
|
||||||
|
}, {
|
||||||
|
value: 'Bytecloud',
|
||||||
|
label: '字节云'
|
||||||
|
}, {
|
||||||
|
value: 'OpenStack',
|
||||||
|
label: 'OpenStack'
|
||||||
|
}, {
|
||||||
|
value: 'ZStack',
|
||||||
|
label: 'ZStack'
|
||||||
|
}, {
|
||||||
|
value: 'Nutanix',
|
||||||
|
label: 'Nutanix'
|
||||||
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'system',
|
value: 'system',
|
||||||
label: '操作系统',
|
label: '操作系统',
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||||
width="200px"
|
width="220px"
|
||||||
:collapsible="collapsible"
|
:collapsible="collapsible"
|
||||||
v-model="collapsed"
|
v-model="collapsed"
|
||||||
:trigger="null"
|
:trigger="null"
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
style="padding: 16px 0px;"
|
style="padding: 16px 0px;"
|
||||||
></s-menu>
|
></s-menu>
|
||||||
|
<!-- <OpsDocs :collapsed="collapsed" /> -->
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -22,10 +23,13 @@
|
|||||||
import Logo from '@/components/tools/Logo'
|
import Logo from '@/components/tools/Logo'
|
||||||
import SMenu from './index'
|
import SMenu from './index'
|
||||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||||
|
// import OpsDocs from '@/modules/docs/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SideMenu',
|
name: 'SideMenu',
|
||||||
components: { Logo, SMenu },
|
components: { Logo, SMenu,
|
||||||
|
// OpsDocs
|
||||||
|
},
|
||||||
mixins: [mixin, mixinDevice],
|
mixins: [mixin, mixinDevice],
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<template #empty>
|
<template #empty>
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<div :style="{ paddingTop: '10px' }">
|
<div :style="{ paddingTop: '10px' }">
|
||||||
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||||
<div>{{ $t('noData') }}</div>
|
<div>{{ $t('noData') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
@@ -64,7 +64,7 @@ export default {
|
|||||||
},
|
},
|
||||||
triggerColor: {
|
triggerColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#f0f2f5',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -113,6 +113,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||||
|
if (paneLengthPixel) {
|
||||||
|
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||||
|
}
|
||||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||||
if (this.isExpanded) {
|
if (this.isExpanded) {
|
||||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||||
|
@@ -35,7 +35,7 @@ export default {
|
|||||||
},
|
},
|
||||||
triggerColor: {
|
triggerColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#F0F5FF',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -52,22 +52,22 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.two-column-layout {
|
.two-column-layout {
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.two-column-layout-sidebar {
|
.two-column-layout-sidebar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 15px 7px;
|
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #fff;
|
|
||||||
}
|
}
|
||||||
.two-column-layout-main {
|
.two-column-layout-main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -43,6 +43,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
import { gridSvg, top_agent, top_acl } from '@/core/icons'
|
||||||
|
import { getPreference } from '@/modules/cmdb/api/preference'
|
||||||
export default {
|
export default {
|
||||||
name: 'TopMenu',
|
name: 'TopMenu',
|
||||||
components: { gridSvg, top_agent, top_acl },
|
components: { gridSvg, top_agent, top_acl },
|
||||||
@@ -78,10 +79,20 @@ export default {
|
|||||||
this.current = this.$route.matched[0].name
|
this.current = this.$route.matched[0].name
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick(route) {
|
async handleClick(route) {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
if (route.name !== this.current) {
|
if (route.name !== this.current) {
|
||||||
|
if (route.name === 'cmdb') {
|
||||||
|
const preference = await getPreference()
|
||||||
|
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||||
|
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
|
||||||
|
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||||
|
} else {
|
||||||
|
this.$router.push('/cmdb/dashboard')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.$router.push(route.redirect)
|
this.$router.push(route.redirect)
|
||||||
|
}
|
||||||
// this.current = route.name
|
// this.current = route.name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -110,33 +121,21 @@ export default {
|
|||||||
line-height: @layout-header-icon-height;
|
line-height: @layout-header-icon-height;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
}
|
}
|
||||||
> span {
|
> span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
border-radius: 4px;
|
|
||||||
color: @layout-header-font-color;
|
color: @layout-header-font-color;
|
||||||
height: @layout-header-height;
|
height: @layout-header-height;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
|
||||||
color: @layout-header-font-selected-color;
|
|
||||||
border-radius: 3px 3px 0px 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
> span:hover,
|
||||||
.top-menu-selected {
|
.top-menu-selected {
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
font-weight: bold;
|
||||||
color: @layout-header-font-selected-color;
|
color: @layout-header-font-selected-color;
|
||||||
border-radius: 3px 3px 0px 0px;
|
|
||||||
border-bottom: 3px solid @layout-header-font-selected-color;
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
|
|
||||||
color: @layout-header-font-selected-color;
|
|
||||||
border-radius: 3px 3px 0px 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@ export default {
|
|||||||
deleting: 'Deleting',
|
deleting: 'Deleting',
|
||||||
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
||||||
grant: 'Grant',
|
grant: 'Grant',
|
||||||
|
revoke: 'Revoke',
|
||||||
login_at: 'Login At',
|
login_at: 'Login At',
|
||||||
logout_at: 'Logout At',
|
logout_at: 'Logout At',
|
||||||
createSuccess: 'Create Success',
|
createSuccess: 'Create Success',
|
||||||
|
@@ -25,6 +25,7 @@ export default {
|
|||||||
deleting: '正在删除',
|
deleting: '正在删除',
|
||||||
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||||
grant: '授权',
|
grant: '授权',
|
||||||
|
revoke: '回收',
|
||||||
login_at: '登录时间',
|
login_at: '登录时间',
|
||||||
logout_at: '登出时间',
|
logout_at: '登出时间',
|
||||||
createSuccess: '创建成功',
|
createSuccess: '创建成功',
|
||||||
|
@@ -87,21 +87,21 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
// 动态主路由
|
// 动态主路由
|
||||||
mainMenu: state => state.routes.appRoutes,
|
mainMenu: (state) => state.routes.appRoutes,
|
||||||
}),
|
}),
|
||||||
contentPaddingLeft() {
|
contentPaddingLeft() {
|
||||||
if (!this.fixSidebar || this.isMobile()) {
|
if (!this.fixSidebar || this.isMobile()) {
|
||||||
return '0'
|
return '0'
|
||||||
}
|
}
|
||||||
if (this.sidebarOpened) {
|
if (this.sidebarOpened) {
|
||||||
return '200px'
|
return '220px'
|
||||||
}
|
}
|
||||||
return '80px'
|
return '80px'
|
||||||
},
|
},
|
||||||
sideBarMenu() {
|
sideBarMenu() {
|
||||||
const sideMenus = this.mainMenu.filter(item => this.$route.path.startsWith(item.path))
|
const sideMenus = this.mainMenu.filter((item) => this.$route.path.startsWith(item.path))
|
||||||
if (sideMenus.length > 1) {
|
if (sideMenus.length > 1) {
|
||||||
return sideMenus.find(item => item.path !== '/').children
|
return sideMenus.find((item) => item.path !== '/').children
|
||||||
} else {
|
} else {
|
||||||
return sideMenus[0].children
|
return sideMenus[0].children
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,9 @@ export default {
|
|||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
reloadBoard: this.reload,
|
reloadBoard: this.reload,
|
||||||
|
collapsed: () => {
|
||||||
|
return this.collapsed
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -146,15 +149,6 @@ export default {
|
|||||||
this.alive = true
|
this.alive = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
paddingCalc() {
|
|
||||||
let left = ''
|
|
||||||
if (this.sidebarOpened) {
|
|
||||||
left = this.isDesktop() ? '200px' : '80px'
|
|
||||||
} else {
|
|
||||||
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
|
|
||||||
}
|
|
||||||
return left
|
|
||||||
},
|
|
||||||
menuSelect() {
|
menuSelect() {
|
||||||
if (!this.isDesktop()) {
|
if (!this.isDesktop()) {
|
||||||
this.collapsed = false
|
this.collapsed = false
|
||||||
|
@@ -233,8 +233,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-history {
|
.acl-history {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
@@ -290,7 +290,6 @@ export default {
|
|||||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||||
item.description += str
|
item.description += str
|
||||||
} else {
|
} else {
|
||||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
|
||||||
item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -241,7 +241,6 @@ export default {
|
|||||||
const str = ` 【 ${key} : -> ${newVal} 】 `
|
const str = ` 【 ${key} : -> ${newVal} 】 `
|
||||||
item.changeDescription += str
|
item.changeDescription += str
|
||||||
} else {
|
} else {
|
||||||
const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
|
||||||
item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,8 +32,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-operation-history {
|
.acl-operation-history {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
@@ -189,8 +189,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-resource-types {
|
.acl-resource-types {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -352,8 +352,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-resources {
|
.acl-resources {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -285,8 +285,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-roles {
|
.acl-roles {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -88,10 +88,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-secret-key {
|
.acl-secret-key {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
height: calc(100% + 24px);
|
height: calc(100% + 24px);
|
||||||
.ant-input[disabled] {
|
.ant-input[disabled] {
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
@@ -320,8 +320,10 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-trigger {
|
.acl-trigger {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -188,8 +188,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.acl-users {
|
.acl-users {
|
||||||
border-radius: 15px;
|
border-radius: @border-radius-box;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: calc(100vh - 64px);
|
height: calc(100vh - 64px);
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -223,3 +223,10 @@ export function deleteCiTypeInheritance(data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCITypeIcons() {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ci_types/icons',
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -71,6 +71,14 @@ export function subscribeRelationView(payload) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function putRelationView(id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/preference/relation/view/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 用户保存条件过滤选项
|
// 用户保存条件过滤选项
|
||||||
export function getPreferenceSearch(payload) {
|
export function getPreferenceSearch(payload) {
|
||||||
// 参数有prv_id: 关系视图的id, ptv_id: 层级视图的id, type_id: 模型id
|
// 参数有prv_id: 关系视图的id, ptv_id: 层级视图的id, type_id: 模型id
|
||||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/unique_card.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/unique_card.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||||
<CiTypeGrant
|
<CiTypeGrant
|
||||||
@@ -314,10 +314,10 @@ export default {
|
|||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
.cmdb-grant {
|
.cmdb-grant {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 24px 24px 0 24px;
|
padding: 0 20px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
.cmdb-grant-title {
|
.cmdb-grant-title {
|
||||||
border-left: 4px solid #custom_colors[color_1];
|
border-left: 4px solid @primary-color;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,18 +325,19 @@ export default {
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-grant {
|
.cmdb-grant {
|
||||||
.grant-button {
|
.grant-button {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
background-color: #custom_colors[color_2];
|
background-color: @primary-color_5;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 2px 3px 4px #custom_colors[color_2];
|
box-shadow: 2px 3px 4px @primary-color_5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,9 +14,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||||
import RoleTransfer from '@/components/RoleTransfer'
|
import RoleTransfer from '@/components/RoleTransfer'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GrantModal',
|
name: 'GrantModal',
|
||||||
components: { EmployeeTransfer, RoleTransfer },
|
components: { EmployeeTransfer, RoleTransfer },
|
||||||
|
props: {
|
||||||
|
customTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -25,6 +32,9 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
|
if (this.customTitle) {
|
||||||
|
return this.customTitle
|
||||||
|
}
|
||||||
if (this.type === 'depart') {
|
if (this.type === 'depart') {
|
||||||
return this.$t('cmdb.components.grantUser')
|
return this.$t('cmdb.components.grantUser')
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
|
<a-modal
|
||||||
|
width="800px"
|
||||||
|
:visible="visible"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
:bodyStyle="{ padding: 0, paddingTop: '20px' }"
|
||||||
|
>
|
||||||
<GrantComp
|
<GrantComp
|
||||||
:resourceType="resourceType"
|
:resourceType="resourceType"
|
||||||
:app_id="app_id"
|
:app_id="app_id"
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
||||||
{ value: 3, label: $t('cmdb.components.none') },
|
{ value: 3, label: $t('cmdb.components.none') },
|
||||||
]"
|
]"
|
||||||
v-model="radioValue"
|
:value="radioValue"
|
||||||
|
@change="changeRadioValue"
|
||||||
>
|
>
|
||||||
<template slot="extra_2" v-if="radioValue === 2">
|
<template slot="extra_2" v-if="radioValue === 2">
|
||||||
<treeselect
|
<treeselect
|
||||||
@@ -128,6 +129,9 @@ export default {
|
|||||||
this.visible = true
|
this.visible = true
|
||||||
this.colType = colType
|
this.colType = colType
|
||||||
this.row = row
|
this.row = row
|
||||||
|
this.form = {
|
||||||
|
name: '',
|
||||||
|
}
|
||||||
if (this.colType === 'read_ci') {
|
if (this.colType === 'read_ci') {
|
||||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||||
@@ -149,10 +153,6 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.form = {
|
|
||||||
name: '',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleOk() {
|
async handleOk() {
|
||||||
@@ -198,6 +198,13 @@ export default {
|
|||||||
}
|
}
|
||||||
this.expression = expression
|
this.expression = expression
|
||||||
},
|
},
|
||||||
|
changeRadioValue(value) {
|
||||||
|
if (this.id_filter) {
|
||||||
|
this.$message.warning(this.$t('cmdb.serviceTree.grantedByServiceTreeTips'))
|
||||||
|
} else {
|
||||||
|
this.radioValue = value
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
122
cmdb-ui/src/modules/cmdb/components/cmdbGrant/revokeModal.vue
Normal file
122
cmdb-ui/src/modules/cmdb/components/cmdbGrant/revokeModal.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal :visible="visible" @cancel="handleCancel" @ok="handleOK" :title="$t('revoke')">
|
||||||
|
<a-form-model :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
|
||||||
|
<a-form-model-item :label="$t('user')">
|
||||||
|
<EmployeeTreeSelect
|
||||||
|
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||||
|
:style="{
|
||||||
|
'--custom-height': '32px',
|
||||||
|
lineHeight: '32px',
|
||||||
|
'--custom-bg-color': '#fff',
|
||||||
|
'--custom-border': '1px solid #d9d9d9',
|
||||||
|
'--custom-multiple-lineHeight': '18px',
|
||||||
|
}"
|
||||||
|
:multiple="true"
|
||||||
|
v-model="form.users"
|
||||||
|
:placeholder="$t('cmdb.serviceTree.userPlaceholder')"
|
||||||
|
:idType="2"
|
||||||
|
departmentKey="acl_rid"
|
||||||
|
employeeKey="acl_rid"
|
||||||
|
/>
|
||||||
|
</a-form-model-item>
|
||||||
|
<a-form-model-item :label="$t('role')">
|
||||||
|
<treeselect
|
||||||
|
v-model="form.roles"
|
||||||
|
:multiple="true"
|
||||||
|
:options="filterAllRoles"
|
||||||
|
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||||
|
:style="{
|
||||||
|
'--custom-height': '32px',
|
||||||
|
lineHeight: '32px',
|
||||||
|
'--custom-bg-color': '#fff',
|
||||||
|
'--custom-border': '1px solid #d9d9d9',
|
||||||
|
'--custom-multiple-lineHeight': '18px',
|
||||||
|
}"
|
||||||
|
:limit="10"
|
||||||
|
:limitText="(count) => `+ ${count}`"
|
||||||
|
:normalizer="
|
||||||
|
(node) => {
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
label: node.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
appendToBody
|
||||||
|
zIndex="1050"
|
||||||
|
:placeholder="$t('cmdb.serviceTree.rolePlaceholder')"
|
||||||
|
@search-change="searchRole"
|
||||||
|
/>
|
||||||
|
</a-form-model-item>
|
||||||
|
</a-form-model>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vue'
|
||||||
|
import { getAllDepAndEmployee } from '@/api/company'
|
||||||
|
import { searchRole } from '@/modules/acl/api/role'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RevokeModal',
|
||||||
|
components: { EmployeeTreeSelect },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
form: {
|
||||||
|
users: undefined,
|
||||||
|
roles: undefined,
|
||||||
|
},
|
||||||
|
allTreeDepAndEmp: [],
|
||||||
|
allRoles: [],
|
||||||
|
filterAllRoles: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
provide_allTreeDepAndEmp: () => {
|
||||||
|
return this.allTreeDepAndEmp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getAllDepAndEmployee()
|
||||||
|
this.loadRoles()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadRoles() {
|
||||||
|
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
|
||||||
|
this.allRoles = res.roles
|
||||||
|
this.filterAllRoles = this.allRoles.slice(0, 100)
|
||||||
|
},
|
||||||
|
getAllDepAndEmployee() {
|
||||||
|
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||||
|
this.allTreeDepAndEmp = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
this.visible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.form = {
|
||||||
|
users: undefined,
|
||||||
|
roles: undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
searchRole(searchQuery) {
|
||||||
|
this.filterAllRoles = this.allRoles
|
||||||
|
.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||||
|
.slice(0, 100)
|
||||||
|
},
|
||||||
|
handleOK() {
|
||||||
|
this.$emit('handleRevoke', this.form)
|
||||||
|
this.handleCancel()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
@@ -11,7 +11,7 @@
|
|||||||
@blur="handleInputConfirm"
|
@blur="handleInputConfirm"
|
||||||
@keyup.enter="handleInputConfirm"
|
@keyup.enter="handleInputConfirm"
|
||||||
/>
|
/>
|
||||||
<a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button>
|
<a v-else @click="showInput"> {{ $t('cmdb.components.saveQuery') }}</a>
|
||||||
</span>
|
</span>
|
||||||
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
|
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
|
||||||
<span
|
<span
|
||||||
@@ -178,10 +178,10 @@ export default {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.preference-search-tag {
|
.preference-search-tag {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 2px;
|
||||||
border: none;
|
border: 1px solid #d9d9d9;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 7px;
|
padding: 2px 7px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
> span {
|
> span {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
@@ -5,8 +5,15 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<treeselect
|
<treeselect
|
||||||
v-if="type === 'resourceSearch'"
|
v-if="type === 'resourceSearch'"
|
||||||
class="custom-treeselect"
|
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||||
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }"
|
:style="{
|
||||||
|
width: '200px',
|
||||||
|
marginRight: '10px',
|
||||||
|
'--custom-height': '32px',
|
||||||
|
'--custom-bg-color': '#fff',
|
||||||
|
'--custom-border': '1px solid #d9d9d9',
|
||||||
|
'--custom-multiple-lineHeight': '16px',
|
||||||
|
}"
|
||||||
v-model="currenCiType"
|
v-model="currenCiType"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:clearable="true"
|
:clearable="true"
|
||||||
@@ -41,24 +48,26 @@
|
|||||||
</treeselect>
|
</treeselect>
|
||||||
<a-input
|
<a-input
|
||||||
v-model="fuzzySearch"
|
v-model="fuzzySearch"
|
||||||
:style="{ display: 'inline-block', width: '244px' }"
|
:style="{ display: 'inline-block', width: '200px' }"
|
||||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||||
@pressEnter="emitRefresh"
|
@pressEnter="emitRefresh"
|
||||||
class="ops-input ops-input-radius"
|
|
||||||
>
|
>
|
||||||
<a-icon
|
<a-icon
|
||||||
type="search"
|
type="search"
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
:style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
|
||||||
@click="emitRefresh"
|
@click="emitRefresh"
|
||||||
/>
|
/>
|
||||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
|
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ $t('cmdb.components.ciSearchTips') }}
|
{{ $t('cmdb.components.ciSearchTips') }}
|
||||||
</template>
|
</template>
|
||||||
<a><a-icon type="question-circle"/></a>
|
<a><a-icon type="question-circle"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
<a-tooltip :title="$t('reset')">
|
||||||
|
<a-button @click="reset">{{ $t('reset') }}</a-button>
|
||||||
|
</a-tooltip>
|
||||||
<FilterComp
|
<FilterComp
|
||||||
ref="filterComp"
|
ref="filterComp"
|
||||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||||
@@ -69,7 +78,7 @@
|
|||||||
<div slot="popover_item" class="search-form-bar-filter">
|
<div slot="popover_item" class="search-form-bar-filter">
|
||||||
<a-icon class="search-form-bar-filter-icon" type="filter" />
|
<a-icon class="search-form-bar-filter-icon" type="filter" />
|
||||||
{{ $t('cmdb.components.conditionFilter') }}
|
{{ $t('cmdb.components.conditionFilter') }}
|
||||||
<a-icon class="search-form-bar-filter-icon" type="down" />
|
<a-icon class="search-form-bar-filter-icon" type="down" :style="{ color: '#d9d9d9' }" />
|
||||||
</div>
|
</div>
|
||||||
</FilterComp>
|
</FilterComp>
|
||||||
<a-input
|
<a-input
|
||||||
@@ -91,13 +100,13 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@keyup.enter="emitRefresh"
|
@keyup.enter="emitRefresh"
|
||||||
>
|
>
|
||||||
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" />
|
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
|
||||||
</a-input>
|
</a-input>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
<slot name="extraContent"></slot>
|
||||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||||
<a
|
<a
|
||||||
@click="
|
@click="
|
||||||
@@ -191,6 +200,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// toggleAdvanced() {
|
||||||
|
// this.advanced = !this.advanced
|
||||||
|
// },
|
||||||
getCITypeGroups() {
|
getCITypeGroups() {
|
||||||
getCITypeGroups({ need_other: true }).then((res) => {
|
getCITypeGroups({ need_other: true }).then((res) => {
|
||||||
this.ciTypeGroup = res
|
this.ciTypeGroup = res
|
||||||
@@ -233,7 +245,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputCiTypeGroup(value) {
|
inputCiTypeGroup(value) {
|
||||||
console.log(value)
|
|
||||||
if (!value || !value.length) {
|
if (!value || !value.length) {
|
||||||
this.$emit('updateAllAttributesList', value)
|
this.$emit('updateAllAttributesList', value)
|
||||||
}
|
}
|
||||||
@@ -253,6 +264,7 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
@import '../../views/index.less';
|
@import '../../views/index.less';
|
||||||
.ci-searchform-expression {
|
.ci-searchform-expression {
|
||||||
> input {
|
> input {
|
||||||
@@ -262,14 +274,14 @@ export default {
|
|||||||
border-right: none;
|
border-right: none;
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
border-bottom: 2px solid #2f54eb;
|
border-bottom: 2px solid @primary-color;
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 2px 2px -2px #1f78d133;
|
box-shadow: 0 2px 2px -2px #1f78d133;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-input-suffix {
|
.ant-input-suffix {
|
||||||
color: #2f54eb;
|
color: #d9d9d9;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,14 +298,15 @@ export default {
|
|||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.search-form-bar {
|
.search-form-bar {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
.search-form-bar-filter {
|
.search-form-bar-filter {
|
||||||
.ops_display_wrapper();
|
.ops_display_wrapper(transparent);
|
||||||
.search-form-bar-filter-icon {
|
.search-form-bar-filter-icon {
|
||||||
color: #custom_colors[color_1];
|
color: @primary-color;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
const cmdb_en = {
|
const cmdb_en = {
|
||||||
relation: 'Relation',
|
relation: 'Relation',
|
||||||
attribute: 'Attributes',
|
attribute: 'Attributes',
|
||||||
|
configTable: 'Config Table',
|
||||||
menu: {
|
menu: {
|
||||||
views: 'Views',
|
views: 'Views',
|
||||||
config: 'Configuration',
|
config: 'Configuration',
|
||||||
@@ -46,8 +47,9 @@ const cmdb_en = {
|
|||||||
selectDefaultOrderAttr: 'Select default sorting attributes',
|
selectDefaultOrderAttr: 'Select default sorting attributes',
|
||||||
asec: 'Forward order',
|
asec: 'Forward order',
|
||||||
desc: 'Reverse order',
|
desc: 'Reverse order',
|
||||||
uniqueKey: 'Uniquely Identifies',
|
uniqueKey: 'Unique Identifies',
|
||||||
uniqueKeySelect: 'Please select a unique identifier',
|
uniqueKeySelect: 'Please select a unique identifier',
|
||||||
|
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies',
|
||||||
notfound: 'Can\'t find what you want?',
|
notfound: 'Can\'t find what you want?',
|
||||||
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
|
||||||
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
|
||||||
@@ -181,7 +183,11 @@ const cmdb_en = {
|
|||||||
inheritType: 'Inherit Type',
|
inheritType: 'Inherit Type',
|
||||||
inheritTypePlaceholder: 'Please select inherit types',
|
inheritTypePlaceholder: 'Please select inherit types',
|
||||||
inheritFrom: 'inherit from {name}',
|
inheritFrom: 'inherit from {name}',
|
||||||
groupInheritFrom: 'Please go to the {name} for modification'
|
groupInheritFrom: 'Please go to the {name} for modification',
|
||||||
|
downloadType: 'Download CIType',
|
||||||
|
deleteCIType: 'Delete CIType',
|
||||||
|
otherGroupTips: 'Non sortable within the other group',
|
||||||
|
filterTips: 'click to show {name}'
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
unselectAttributes: 'Unselected',
|
unselectAttributes: 'Unselected',
|
||||||
@@ -223,7 +229,7 @@ const cmdb_en = {
|
|||||||
pleaseSearch: 'Please search',
|
pleaseSearch: 'Please search',
|
||||||
conditionFilter: 'Conditional filtering',
|
conditionFilter: 'Conditional filtering',
|
||||||
attributeDesc: 'Attribute Description',
|
attributeDesc: 'Attribute Description',
|
||||||
ciSearchTips: '1. JSON attributes cannot be searched<br />2. If the search content includes commas, they need to be escaped,<br />3. Only index attributes are searched, non-index attributes use conditional filtering',
|
ciSearchTips: '1. JSON/password/link attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering',
|
||||||
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
|
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
|
||||||
subCIType: 'Subscription CIType',
|
subCIType: 'Subscription CIType',
|
||||||
already: 'already',
|
already: 'already',
|
||||||
@@ -244,8 +250,10 @@ const cmdb_en = {
|
|||||||
unselectCIType: 'No CIType selected yet',
|
unselectCIType: 'No CIType selected yet',
|
||||||
pleaseUploadFile: 'Please upload files',
|
pleaseUploadFile: 'Please upload files',
|
||||||
batchUploadCanceled: 'Batch upload canceled',
|
batchUploadCanceled: 'Batch upload canceled',
|
||||||
selectCITypeTips: 'Please select CIType',
|
selectCIType: 'Select CIType',
|
||||||
|
selectCITypeTips: 'Please select a CIType and then download',
|
||||||
downloadTemplate: 'Download Template',
|
downloadTemplate: 'Download Template',
|
||||||
|
clickDownload: 'Click to Download',
|
||||||
drawTips: 'Click or drag files here to upload!',
|
drawTips: 'Click or drag files here to upload!',
|
||||||
supportFileTypes: 'Supported file types: xls, xlsx',
|
supportFileTypes: 'Supported file types: xls, xlsx',
|
||||||
uploadResult: 'Upload results',
|
uploadResult: 'Upload results',
|
||||||
@@ -256,6 +264,16 @@ const cmdb_en = {
|
|||||||
errorTips: 'Error message',
|
errorTips: 'Error message',
|
||||||
requestFailedTips: 'An error occurred with the request, please try again later',
|
requestFailedTips: 'An error occurred with the request, please try again later',
|
||||||
requestSuccessTips: 'Upload completed',
|
requestSuccessTips: 'Upload completed',
|
||||||
|
uploadFile: 'Upload File',
|
||||||
|
drawTips1: 'Please <span class="cmdb-batch-upload-tips">select a CIType</span>, and then <span class="cmdb-batch-upload-tips">download</span> ,',
|
||||||
|
drawTips2: '<span class="cmdb-batch-upload-tips">click or drag file</span> to upload',
|
||||||
|
dataPreview: 'Preview data and upload',
|
||||||
|
tips1: 'Kind Reminder :',
|
||||||
|
tips2: '1. Click to download the template, and users can customize the header of the template file, including model properties and model associations',
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
tips3: '2. The red color in the template file represents the model relationship, such as the $Product. Product Name (${Model Name}. {Attribute Name}) column, which establishes the relationship with the product.',
|
||||||
|
tips4: '3. In the download template Excel file, the predefined values of attributes will be set as dropdown options. Please note that due to the limitations of Excel itself, a single dropdown box is limited to a maximum of 255 characters. If it exceeds 255 characters, we will not set the dropdown options for this attribute',
|
||||||
|
tips5: '4. When using Excel templates, please ensure that a single file does not exceed 5000 lines.',
|
||||||
},
|
},
|
||||||
preference: {
|
preference: {
|
||||||
mySub: 'My Subscription',
|
mySub: 'My Subscription',
|
||||||
@@ -273,6 +291,7 @@ const cmdb_en = {
|
|||||||
monthsAgo: 'month ago',
|
monthsAgo: 'month ago',
|
||||||
yearsAgo: 'years ago',
|
yearsAgo: 'years ago',
|
||||||
just: 'just now',
|
just: 'just now',
|
||||||
|
searchPlaceholder: 'Please search CIType',
|
||||||
},
|
},
|
||||||
custom_dashboard: {
|
custom_dashboard: {
|
||||||
charts: 'Chart',
|
charts: 'Chart',
|
||||||
@@ -313,13 +332,22 @@ const cmdb_en = {
|
|||||||
},
|
},
|
||||||
preference_relation: {
|
preference_relation: {
|
||||||
newServiceTree: 'Add Service Tree',
|
newServiceTree: 'Add Service Tree',
|
||||||
|
editServiceTree: 'Edit Service Tree',
|
||||||
serviceTreeName: 'Name',
|
serviceTreeName: 'Name',
|
||||||
|
serviceTreeNamePlaceholder: 'Please enter the service tree name',
|
||||||
public: 'Public',
|
public: 'Public',
|
||||||
saveLayout: 'Save Layout',
|
saveLayout: 'Save Layout',
|
||||||
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
|
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
|
||||||
tips1: 'Cannot form a view with the currently selected node, please select again!',
|
tips1: 'Cannot form a view with the currently selected node, please select again!',
|
||||||
tips2: 'Please enter the new serviceTree name!',
|
tips2: 'Please enter the new serviceTree name!',
|
||||||
tips3: 'Please select at least two nodes!',
|
tips3: 'Please select at least two nodes!',
|
||||||
|
tips4: 'Leaf node must be selected',
|
||||||
|
tips5: 'Select the tree directory node and display the service tree sub nodes as a Table',
|
||||||
|
showLeafNode: 'Show Leaf Node',
|
||||||
|
showTreeNode: 'Show Tree Node',
|
||||||
|
sort: 'Sort',
|
||||||
|
sort1: 'Leaf node information comes first',
|
||||||
|
sort2: 'Tree node information comes first'
|
||||||
},
|
},
|
||||||
history: {
|
history: {
|
||||||
ciChange: 'CI',
|
ciChange: 'CI',
|
||||||
@@ -466,7 +494,7 @@ const cmdb_en = {
|
|||||||
tips3: 'Please select the fields that need to be modified',
|
tips3: 'Please select the fields that need to be modified',
|
||||||
tips4: 'At least one field must be selected',
|
tips4: 'At least one field must be selected',
|
||||||
tips5: 'Search name | alias',
|
tips5: 'Search name | alias',
|
||||||
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
|
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json/link/password currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
|
||||||
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
|
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
|
||||||
tips8: 'Multiple values, such as intranet IP',
|
tips8: 'Multiple values, such as intranet IP',
|
||||||
tips9: 'For front-end only',
|
tips9: 'For front-end only',
|
||||||
@@ -477,12 +505,25 @@ const cmdb_en = {
|
|||||||
noPermission: 'No Permission'
|
noPermission: 'No Permission'
|
||||||
},
|
},
|
||||||
serviceTree: {
|
serviceTree: {
|
||||||
deleteNode: 'Delete Node',
|
remove: 'Remove',
|
||||||
|
deleteNode: 'Delete {name}',
|
||||||
tips1: 'For example: q=os_version:centos&sort=os_version',
|
tips1: 'For example: q=os_version:centos&sort=os_version',
|
||||||
tips2: 'Expression search',
|
tips2: 'Expression search',
|
||||||
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
|
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
|
||||||
copyFailed: 'Copy failed',
|
copyFailed: 'Copy failed',
|
||||||
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
|
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
|
||||||
|
batch: 'Batch',
|
||||||
|
editNode: 'Edit Node',
|
||||||
|
editNodeName: 'Edit Node Name',
|
||||||
|
grantTitle: 'Grant(read)',
|
||||||
|
userPlaceholder: 'Please select users',
|
||||||
|
rolePlaceholder: 'Please select roles',
|
||||||
|
grantedByServiceTree: 'Granted By Service Tree:',
|
||||||
|
grantedByServiceTreeTips: 'Please delete id_filter in Servive Tree',
|
||||||
|
peopleHasRead: 'Personnel authorized to read:',
|
||||||
|
authorizationPolicy: 'CI Authorization Policy:',
|
||||||
|
idAuthorizationPolicy: 'Authorized by node:',
|
||||||
|
view: 'View permissions'
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
tips1: 'Please go to Preference page first to complete your subscription!',
|
tips1: 'Please go to Preference page first to complete your subscription!',
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
const cmdb_zh = {
|
const cmdb_zh = {
|
||||||
relation: '关系',
|
relation: '关系',
|
||||||
attribute: '属性',
|
attribute: '属性',
|
||||||
|
configTable: '配置表格',
|
||||||
menu: {
|
menu: {
|
||||||
views: '视图',
|
views: '视图',
|
||||||
config: '配置',
|
config: '配置',
|
||||||
@@ -35,7 +36,7 @@ const cmdb_zh = {
|
|||||||
attributeLibray: '属性库',
|
attributeLibray: '属性库',
|
||||||
addCITypeInGroup: '在该组中新增CI模型',
|
addCITypeInGroup: '在该组中新增CI模型',
|
||||||
addCIType: '新增CI模型',
|
addCIType: '新增CI模型',
|
||||||
editGroupName: '编辑组名称',
|
editGroupName: '重命名分组',
|
||||||
deleteGroup: '删除该组',
|
deleteGroup: '删除该组',
|
||||||
CITypeName: '模型名(英文)',
|
CITypeName: '模型名(英文)',
|
||||||
English: '英文',
|
English: '英文',
|
||||||
@@ -48,6 +49,7 @@ const cmdb_zh = {
|
|||||||
desc: '倒序',
|
desc: '倒序',
|
||||||
uniqueKey: '唯一标识',
|
uniqueKey: '唯一标识',
|
||||||
uniqueKeySelect: '请选择唯一标识',
|
uniqueKeySelect: '请选择唯一标识',
|
||||||
|
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识',
|
||||||
notfound: '找不到想要的?',
|
notfound: '找不到想要的?',
|
||||||
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
|
||||||
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
|
||||||
@@ -181,7 +183,11 @@ const cmdb_zh = {
|
|||||||
inheritType: '继承模型',
|
inheritType: '继承模型',
|
||||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||||
inheritFrom: '属性继承自{name}',
|
inheritFrom: '属性继承自{name}',
|
||||||
groupInheritFrom: '请至{name}进行修改'
|
groupInheritFrom: '请至{name}进行修改',
|
||||||
|
downloadType: '下载模型',
|
||||||
|
deleteCIType: '删除模型',
|
||||||
|
otherGroupTips: '其他分组属性不可排序',
|
||||||
|
filterTips: '点击可仅查看{name}属性'
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
unselectAttributes: '未选属性',
|
unselectAttributes: '未选属性',
|
||||||
@@ -219,11 +225,11 @@ const cmdb_zh = {
|
|||||||
beforeChange: '变更前',
|
beforeChange: '变更前',
|
||||||
afterChange: '变更后',
|
afterChange: '变更后',
|
||||||
noticeContentTips: '请输入通知内容',
|
noticeContentTips: '请输入通知内容',
|
||||||
saveQuery: '保存筛选条件',
|
saveQuery: '保存条件',
|
||||||
pleaseSearch: '请查找',
|
pleaseSearch: '请查找',
|
||||||
conditionFilter: '条件过滤',
|
conditionFilter: '条件过滤',
|
||||||
attributeDesc: '属性说明',
|
attributeDesc: '属性说明',
|
||||||
ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
|
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||||
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
|
||||||
subCIType: '订阅模型',
|
subCIType: '订阅模型',
|
||||||
already: '已',
|
already: '已',
|
||||||
@@ -244,9 +250,10 @@ const cmdb_zh = {
|
|||||||
unselectCIType: '尚未选择模板类型',
|
unselectCIType: '尚未选择模板类型',
|
||||||
pleaseUploadFile: '请上传文件',
|
pleaseUploadFile: '请上传文件',
|
||||||
batchUploadCanceled: '批量上传已取消',
|
batchUploadCanceled: '批量上传已取消',
|
||||||
selectCITypeTips: '请选择模板类型',
|
selectCIType: '选择模型',
|
||||||
|
selectCITypeTips: '请选择模型后下载模板',
|
||||||
downloadTemplate: '下载模板',
|
downloadTemplate: '下载模板',
|
||||||
drawTips: '点击或拖拽文件至此上传!',
|
clickDownload: '点击下载',
|
||||||
supportFileTypes: '支持文件类型:xls,xlsx',
|
supportFileTypes: '支持文件类型:xls,xlsx',
|
||||||
uploadResult: '上传结果',
|
uploadResult: '上传结果',
|
||||||
total: '共',
|
total: '共',
|
||||||
@@ -256,6 +263,16 @@ const cmdb_zh = {
|
|||||||
errorTips: '错误信息',
|
errorTips: '错误信息',
|
||||||
requestFailedTips: '请求出现错误,请稍后再试',
|
requestFailedTips: '请求出现错误,请稍后再试',
|
||||||
requestSuccessTips: '批量上传已完成',
|
requestSuccessTips: '批量上传已完成',
|
||||||
|
uploadFile: '文件上传',
|
||||||
|
drawTips1: '请先<span class="cmdb-batch-upload-tips">选择模型</span>,<span class="cmdb-batch-upload-tips">下载模板</span>后',
|
||||||
|
drawTips2: '<span class="cmdb-batch-upload-tips">点击或拖拽文件</span>至此上传',
|
||||||
|
dataPreview: '数据预览并导入',
|
||||||
|
tips1: '温馨提示:',
|
||||||
|
tips2: '1. 点击下载模板,用户可以自定义模板文件的表头,包括模型属性、模型关联',
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
tips3: '2. 模板文件中红色为模型关系,如$产品.产品名(${模型名}.{属性名})这一列就可建立和产品之间的关系',
|
||||||
|
tips4: '3. 下载模板excel文件中会将属性的预定义值置为下拉选项,请注意,受excel本身的限制,单个下拉框限制了最多255个字符,如果超过255个字符,我们不会设置该属性的下拉选项',
|
||||||
|
tips5: '4. 在使用excel模板时,请确保单个文件不超过5000行',
|
||||||
},
|
},
|
||||||
preference: {
|
preference: {
|
||||||
mySub: '我的订阅',
|
mySub: '我的订阅',
|
||||||
@@ -273,6 +290,7 @@ const cmdb_zh = {
|
|||||||
monthsAgo: '月前',
|
monthsAgo: '月前',
|
||||||
yearsAgo: '年前',
|
yearsAgo: '年前',
|
||||||
just: '刚刚',
|
just: '刚刚',
|
||||||
|
searchPlaceholder: '请搜索模型',
|
||||||
},
|
},
|
||||||
custom_dashboard: {
|
custom_dashboard: {
|
||||||
charts: '图表',
|
charts: '图表',
|
||||||
@@ -313,13 +331,23 @@ const cmdb_zh = {
|
|||||||
},
|
},
|
||||||
preference_relation: {
|
preference_relation: {
|
||||||
newServiceTree: '新增服务树',
|
newServiceTree: '新增服务树',
|
||||||
|
editServiceTree: '编辑服务树',
|
||||||
serviceTreeName: '服务树名',
|
serviceTreeName: '服务树名',
|
||||||
|
serviceTreeNamePlaceholder: '请输入服务树名',
|
||||||
public: '公开',
|
public: '公开',
|
||||||
saveLayout: '保存布局',
|
saveLayout: '保存布局',
|
||||||
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
|
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
|
||||||
tips1: '不能与当前选中节点形成视图,请重新选择!',
|
tips1: '不能与当前选中节点形成视图,请重新选择!',
|
||||||
tips2: '请输入新增服务树名!',
|
tips2: '请输入新增服务树名!',
|
||||||
tips3: '请选择至少两个节点!',
|
tips3: '请选择至少两个节点!',
|
||||||
|
tips4: '树子节点为必选',
|
||||||
|
tips5: '选中树目录节点,服务树子节点展示成Table',
|
||||||
|
showLeafNode: '树的子节点展示成Table',
|
||||||
|
showTreeNode: '展示树节点信息',
|
||||||
|
sort: '顺序',
|
||||||
|
sort1: '树子节点信息在前',
|
||||||
|
sort2: '树节点信息在前'
|
||||||
|
|
||||||
},
|
},
|
||||||
history: {
|
history: {
|
||||||
ciChange: 'CI变更',
|
ciChange: 'CI变更',
|
||||||
@@ -400,20 +428,20 @@ const cmdb_zh = {
|
|||||||
def unique_key(self):
|
def unique_key(self):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:return: 返回唯一属性的名字
|
:return: Returns the name of a unique attribute
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attributes():
|
def attributes():
|
||||||
"""
|
"""
|
||||||
定义属性字段
|
Define attribute fields
|
||||||
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文
|
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
|
||||||
类型: String Integer Float Date DateTime Time JSON
|
type: String Integer Float Date DateTime Time JSON
|
||||||
例如:
|
For example:
|
||||||
return [
|
return [
|
||||||
("ci_type", "String", "模型名称"),
|
("ci_type", "String", "CIType name"),
|
||||||
("private_ip", "String", "内网IP, 多值逗号分隔")
|
("private_ip", "String", "Internal IP, multiple values separated by commas")
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
@@ -421,9 +449,10 @@ const cmdb_zh = {
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def run():
|
def run():
|
||||||
"""
|
"""
|
||||||
执行入口, 返回采集的属性值
|
Execution entry, returns collected attribute values
|
||||||
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值
|
:return:
|
||||||
例如:
|
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
|
||||||
|
For example:
|
||||||
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
return [dict(ci_type="server", private_ip="192.168.1.1")]
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
@@ -434,7 +463,7 @@ const cmdb_zh = {
|
|||||||
if isinstance(result, list):
|
if isinstance(result, list):
|
||||||
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
|
||||||
else:
|
else:
|
||||||
print("ERROR: 采集返回必须是列表")
|
print("ERROR: The collection return must be a list")
|
||||||
`,
|
`,
|
||||||
server: '物理机',
|
server: '物理机',
|
||||||
vserver: '虚拟机',
|
vserver: '虚拟机',
|
||||||
@@ -442,7 +471,7 @@ const cmdb_zh = {
|
|||||||
disk: '硬盘',
|
disk: '硬盘',
|
||||||
},
|
},
|
||||||
ci: {
|
ci: {
|
||||||
attributeDesc: '属性说明',
|
attributeDesc: '查看属性配置',
|
||||||
selectRows: '选取:{rows} 项',
|
selectRows: '选取:{rows} 项',
|
||||||
addRelation: '添加关系',
|
addRelation: '添加关系',
|
||||||
all: '全部',
|
all: '全部',
|
||||||
@@ -465,7 +494,7 @@ const cmdb_zh = {
|
|||||||
tips3: '请选择需要修改的字段',
|
tips3: '请选择需要修改的字段',
|
||||||
tips4: '必须至少选择一个字段',
|
tips4: '必须至少选择一个字段',
|
||||||
tips5: '搜索 名称 | 别名',
|
tips5: '搜索 名称 | 别名',
|
||||||
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json、链接、密码目前不支持建索引 \n\n文本字符长度超过190不能建索引',
|
||||||
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
tips7: '表现形式是下拉框, 值必须在预定义值里',
|
||||||
tips8: '多值, 比如内网IP',
|
tips8: '多值, 比如内网IP',
|
||||||
tips9: '仅针对前端',
|
tips9: '仅针对前端',
|
||||||
@@ -476,12 +505,25 @@ const cmdb_zh = {
|
|||||||
noPermission: '暂无权限'
|
noPermission: '暂无权限'
|
||||||
},
|
},
|
||||||
serviceTree: {
|
serviceTree: {
|
||||||
deleteNode: '删除节点',
|
remove: '移除',
|
||||||
|
deleteNode: '移除 {name}',
|
||||||
tips1: '例:q=os_version:centos&sort=os_version',
|
tips1: '例:q=os_version:centos&sort=os_version',
|
||||||
tips2: '表达式搜索',
|
tips2: '表达式搜索',
|
||||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||||
copyFailed: '复制失败',
|
copyFailed: '复制失败',
|
||||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||||
|
batch: '批量操作',
|
||||||
|
editNode: '编辑节点',
|
||||||
|
editNodeName: '修改节点名',
|
||||||
|
grantTitle: '授权(查看权限)',
|
||||||
|
userPlaceholder: '请选择用户',
|
||||||
|
rolePlaceholder: '请选择角色',
|
||||||
|
grantedByServiceTree: '服务树授权:',
|
||||||
|
grantedByServiceTreeTips: '请先在服务树里删掉节点授权',
|
||||||
|
peopleHasRead: '当前有查看权限的人员:',
|
||||||
|
authorizationPolicy: '实例授权策略:',
|
||||||
|
idAuthorizationPolicy: '按节点授权的:',
|
||||||
|
view: '查看权限'
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||||
|
@@ -156,8 +156,8 @@ const genCmdbRoutes = async () => {
|
|||||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||||
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
|
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
|
||||||
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
|
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
|
||||||
} else if (routes.children[2].children.length > 0) {
|
} else if (routes.children[2]?.children?.length > 0) {
|
||||||
routes.redirect = routes.children[2].children.find(item => !item.hidden).path
|
routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
|
||||||
} else {
|
} else {
|
||||||
routes.redirect = '/cmdb/dashboard'
|
routes.redirect = '/cmdb/dashboard'
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,6 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
|||||||
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
|
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
|
||||||
const columns = []
|
const columns = []
|
||||||
for (let attr of _attrList) {
|
for (let attr of _attrList) {
|
||||||
|
|
||||||
const editRender = { name: 'input' }
|
const editRender = { name: 'input' }
|
||||||
switch (attr.value_type) {
|
switch (attr.value_type) {
|
||||||
case '0':
|
case '0':
|
||||||
@@ -135,6 +134,35 @@ export const getPropertyStyle = (attr) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getPropertyIcon = (attr) => {
|
||||||
|
switch (attr.value_type) {
|
||||||
|
case '0':
|
||||||
|
return 'duose-shishu'
|
||||||
|
case '1':
|
||||||
|
return 'duose-fudianshu'
|
||||||
|
case '2':
|
||||||
|
if (attr.is_password) {
|
||||||
|
return 'duose-password'
|
||||||
|
}
|
||||||
|
if (attr.is_link) {
|
||||||
|
return 'duose-link'
|
||||||
|
}
|
||||||
|
return 'duose-wenben'
|
||||||
|
case '3':
|
||||||
|
return 'duose-datetime'
|
||||||
|
case '4':
|
||||||
|
return 'duose-date'
|
||||||
|
case '5':
|
||||||
|
return 'duose-time'
|
||||||
|
case '6':
|
||||||
|
return 'duose-json'
|
||||||
|
case '7':
|
||||||
|
return 'duose-password'
|
||||||
|
case '8':
|
||||||
|
return 'duose-link'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
|
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
|
||||||
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
|
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
|
||||||
if (!_tempData.length) {
|
if (!_tempData.length) {
|
||||||
|
@@ -1,29 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
|
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
|
||||||
<div id="title">
|
<div class="cmdb-views-header">
|
||||||
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
|
<span>
|
||||||
|
<span class="cmdb-views-header-title">{{ $t('cmdb.menu.batchUpload') }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a-row>
|
<CiTypeChoice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
|
||||||
<a-col :span="12">
|
<p class="cmdb-batch-upload-label"><span>*</span>3. {{ $t('cmdb.batch.uploadFile') }}</p>
|
||||||
<upload-file-form
|
<UploadFileForm
|
||||||
:isUploading="isUploading"
|
:isUploading="isUploading"
|
||||||
:ciType="ciType"
|
:ciType="ciType"
|
||||||
ref="uploadFileForm"
|
ref="uploadFileForm"
|
||||||
@uploadDone="uploadDone"
|
@uploadDone="uploadDone"
|
||||||
></upload-file-form>
|
></UploadFileForm>
|
||||||
</a-col>
|
<p class="cmdb-batch-upload-label">4. {{ $t('cmdb.batch.dataPreview') }}</p>
|
||||||
<a-col :span="24" v-if="ciType && uploadData.length">
|
|
||||||
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
|
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
|
||||||
<div class="cmdb-batch-upload-action">
|
<div class="cmdb-batch-upload-action">
|
||||||
<a-space size="large">
|
<a-space size="large">
|
||||||
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
|
<a-button :disabled="!(ciType && uploadData.length)" @click="handleUpload" type="primary">{{
|
||||||
<a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button>
|
$t('upload')
|
||||||
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button>
|
}}</a-button>
|
||||||
|
<a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||||
|
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{
|
||||||
|
$t('cmdb.batch.downloadFailed')
|
||||||
|
}}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
<UploadResult
|
||||||
<a-col :span="24" v-if="ciType">
|
v-if="ciType"
|
||||||
<upload-result
|
|
||||||
ref="uploadResult"
|
ref="uploadResult"
|
||||||
:upLoadData="uploadData"
|
:upLoadData="uploadData"
|
||||||
:ciType="ciType"
|
:ciType="ciType"
|
||||||
@@ -31,9 +35,7 @@
|
|||||||
:isUploading="isUploading"
|
:isUploading="isUploading"
|
||||||
@uploadResultDone="uploadResultDone"
|
@uploadResultDone="uploadResultDone"
|
||||||
@uploadResultError="uploadResultError"
|
@uploadResultError="uploadResultError"
|
||||||
></upload-result>
|
></UploadResult>
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ export default {
|
|||||||
handleCancel() {
|
handleCancel() {
|
||||||
if (!this.isUploading) {
|
if (!this.isUploading) {
|
||||||
this.showCiType(null)
|
this.showCiType(null)
|
||||||
this.$refs.ciTypeChoice.selectNum = null
|
this.$refs.ciTypeChoice.selectNum = undefined
|
||||||
this.hasError = false
|
this.hasError = false
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
|
this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
|
||||||
@@ -144,16 +146,29 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
@import '../index.less';
|
||||||
|
.cmdb-batch-upload-label {
|
||||||
|
color: @text-color_1;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: pre;
|
||||||
|
> span {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-batch-upload {
|
.cmdb-batch-upload {
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
padding: 24px;
|
padding: 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 20px;
|
border-radius: @border-radius-box;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
.cmdb-batch-upload-action {
|
.cmdb-batch-upload-action {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
text-align: center;
|
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-space>
|
<div>
|
||||||
<span>{{ $t('cmdb.ciType.ciType') }}: </span>
|
<p class="cmdb-batch-upload-label"><span>*</span>1. {{ $t('cmdb.batch.selectCIType') }}</p>
|
||||||
<a-select
|
<a-select
|
||||||
showSearch
|
showSearch
|
||||||
:placeholder="$t('cmdb.batch.selectCITypeTips')"
|
:placeholder="$t('cmdb.batch.selectCITypeTips')"
|
||||||
@change="selectCiType"
|
@change="selectCiType"
|
||||||
:style="{ width: '300px' }"
|
:style="{ width: '50%', marginBottom: '1em' }"
|
||||||
class="ops-select"
|
class="ops-select"
|
||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
v-model="selectNum"
|
v-model="selectNum"
|
||||||
@@ -14,13 +14,16 @@
|
|||||||
ciType.alias
|
ciType.alias
|
||||||
}}</a-select-option>
|
}}</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
<p class="cmdb-batch-upload-label"> 2. {{ $t('cmdb.batch.downloadTemplate') }}</p>
|
||||||
<a-button
|
<a-button
|
||||||
|
:style="{ marginBottom: '1em' }"
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
:disabled="!selectNum"
|
:disabled="!selectNum"
|
||||||
type="primary"
|
type="primary"
|
||||||
class="ops-button-primary"
|
ghost
|
||||||
|
class="ops-button-ghost"
|
||||||
icon="download"
|
icon="download"
|
||||||
>{{ $t('cmdb.batch.downloadTemplate') }}</a-button
|
>{{ $t('cmdb.batch.clickDownload') }}</a-button
|
||||||
>
|
>
|
||||||
<a-modal
|
<a-modal
|
||||||
:bodyStyle="{ paddingTop: 0 }"
|
:bodyStyle="{ paddingTop: 0 }"
|
||||||
@@ -88,7 +91,7 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</a-space>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -107,7 +110,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
ciTypeList: [],
|
ciTypeList: [],
|
||||||
ciTypeName: '',
|
ciTypeName: '',
|
||||||
selectNum: null,
|
selectNum: undefined,
|
||||||
selectCiTypeAttrList: [],
|
selectCiTypeAttrList: [],
|
||||||
visible: false,
|
visible: false,
|
||||||
checkedAttrs: [],
|
checkedAttrs: [],
|
||||||
@@ -238,6 +241,7 @@ export default {
|
|||||||
for (let row = 2; row < 5000; row++) {
|
for (let row = 2; row < 5000; row++) {
|
||||||
Object.keys(choice_value_obj).forEach((key) => {
|
Object.keys(choice_value_obj).forEach((key) => {
|
||||||
const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"`
|
const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"`
|
||||||
|
console.log(formulae)
|
||||||
if (formulae.length <= 255) {
|
if (formulae.length <= 255) {
|
||||||
ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = {
|
ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = {
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cmdb-batch-upload-table">
|
<div class="cmdb-batch-upload-table">
|
||||||
<vxe-table
|
<vxe-table
|
||||||
|
v-if="uploadData && uploadData.length"
|
||||||
ref="xTable"
|
ref="xTable"
|
||||||
stripe
|
stripe
|
||||||
show-header-overflow
|
show-header-overflow
|
||||||
show-overflow=""
|
show-overflow=""
|
||||||
size="small"
|
size="small"
|
||||||
class="ops-stripe-table"
|
class="ops-stripe-table"
|
||||||
:max-height="200"
|
height="auto"
|
||||||
:data="dataSource"
|
:data="dataSource"
|
||||||
resizable
|
resizable
|
||||||
:row-style="rowStyle"
|
:row-style="rowStyle"
|
||||||
@@ -21,6 +22,19 @@
|
|||||||
:min-width="100"
|
:min-width="100"
|
||||||
></vxe-column>
|
></vxe-column>
|
||||||
</vxe-table>
|
</vxe-table>
|
||||||
|
<a-empty
|
||||||
|
v-else
|
||||||
|
:image-style="{
|
||||||
|
height: '80px',
|
||||||
|
marginTop: '10px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||||
|
<template slot="description">
|
||||||
|
<p>{{ $t('noData') }}</p>
|
||||||
|
<p>{{ $t('cmdb.batch.pleaseUploadFile') }}</p>
|
||||||
|
</template>
|
||||||
|
</a-empty>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -99,7 +113,21 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-batch-upload-table {
|
.cmdb-batch-upload-table {
|
||||||
overflow: auto;
|
height: 200px;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||||
|
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||||
|
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||||
|
.ant-empty-description {
|
||||||
|
p:last-child {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -9,14 +9,22 @@
|
|||||||
:fileList="fileList"
|
:fileList="fileList"
|
||||||
:disabled="!ciType || isUploading"
|
:disabled="!ciType || isUploading"
|
||||||
>
|
>
|
||||||
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
<ops-icon type="itsm-folder" />
|
||||||
<p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p>
|
|
||||||
<p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
|
<p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
|
||||||
</a-upload-dragger>
|
<p v-html="$t('cmdb.batch.drawTips1')"></p>
|
||||||
|
<p v-html="$t('cmdb.batch.drawTips2')"></p>
|
||||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
||||||
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||||
<a-progress :status="progressStatus" :percent="percent" />
|
<a-progress :status="progressStatus" :percent="percent" />
|
||||||
</div>
|
</div>
|
||||||
|
</a-upload-dragger>
|
||||||
|
<div class="cmdb-batch-upload-tips">
|
||||||
|
<p>{{ $t('cmdb.batch.tips1') }}</p>
|
||||||
|
<div>{{ $t('cmdb.batch.tips2') }}</div>
|
||||||
|
<div>{{ $t('cmdb.batch.tips3') }}</div>
|
||||||
|
<div>{{ $t('cmdb.batch.tips4') }}</div>
|
||||||
|
<div>{{ $t('cmdb.batch.tips5') }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -46,15 +54,13 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
ciType: {
|
ciType: {
|
||||||
handler(newValue) {
|
handler() {
|
||||||
if (!newValue) {
|
|
||||||
this.ciItemNum = 0
|
this.ciItemNum = 0
|
||||||
this.fileList = []
|
this.fileList = []
|
||||||
this.dataList = []
|
this.dataList = []
|
||||||
this.progressStatus = 'active'
|
this.progressStatus = 'active'
|
||||||
this.percent = 0
|
this.percent = 0
|
||||||
this.$emit('uploadDone', this.dataList)
|
this.$emit('uploadDone', this.dataList)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -77,12 +83,28 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-batch-upload-dragger {
|
.cmdb-batch-upload-dragger {
|
||||||
height: 220px;
|
height: auto;
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
|
.ant-upload p {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
.ant-upload.ant-upload-drag {
|
.ant-upload.ant-upload-drag {
|
||||||
background: rgba(240, 245, 255, 0.35);
|
|
||||||
border: none;
|
border: none;
|
||||||
|
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||||
|
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||||
|
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||||
|
.ant-upload-drag-container > i {
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
.cmdb-batch-upload-tips {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ant-upload.ant-upload-drag .ant-upload-drag-container {
|
.ant-upload.ant-upload-drag .ant-upload-drag-container {
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
@@ -90,23 +112,37 @@ export default {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-batch-upload-dragger {
|
.cmdb-batch-upload-dragger {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
.cmdb-batch-upload-dragger-file {
|
.cmdb-batch-upload-dragger-file {
|
||||||
background-color: #fff;
|
background-color: @primary-color_7;
|
||||||
box-shadow: 0px 2px 5px rgba(78, 94, 160, 0.2);
|
border-radius: 2px;
|
||||||
border-radius: 4px;
|
|
||||||
position: absolute;
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
left: 50%;
|
|
||||||
bottom: 24px;
|
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
transform: translate(-50%);
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
> span {
|
> span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.cmdb-batch-upload-tips {
|
||||||
|
width: 50%;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: @text-color_3;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
p:first-child {
|
||||||
|
color: @text-color_1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cmdb-batch-upload-result" v-if="visible">
|
<div class="cmdb-batch-upload-result" v-if="visible">
|
||||||
<h3 class="cmdb-batch-upload-result-title">{{ $t('cmdb.batch.uploadResult') }}</h3>
|
<p class="cmdb-batch-upload-label">5. {{ $t('cmdb.batch.uploadResult') }}</p>
|
||||||
<div class="cmdb-batch-upload-result-content">
|
<div class="cmdb-batch-upload-result-content">
|
||||||
<h4>
|
<h4>
|
||||||
{{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }}
|
{{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span>
|
||||||
<span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
|
{{ $t('cmdb.batch.successItems') }} <span style="color: lightgreen">{{ success }}</span>
|
||||||
|
{{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
|
||||||
</h4>
|
</h4>
|
||||||
<div>
|
<div>
|
||||||
<span>{{ $t('cmdb.batch.errorTips') }}: </span>
|
<span>{{ $t('cmdb.batch.errorTips') }}: </span>
|
||||||
@@ -39,7 +40,7 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: function() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
complete: 0,
|
complete: 0,
|
||||||
@@ -54,6 +55,13 @@ export default {
|
|||||||
return this.upLoadData.length || 0
|
return this.upLoadData.length || 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
ciType: {
|
||||||
|
handler() {
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async upload2Server() {
|
async upload2Server() {
|
||||||
this.visible = true
|
this.visible = true
|
||||||
@@ -96,10 +104,6 @@ export default {
|
|||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.cmdb-batch-upload-result {
|
.cmdb-batch-upload-result {
|
||||||
.cmdb-batch-upload-result-title {
|
|
||||||
border-left: 4px solid #custom_colors[color_1];
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
.cmdb-batch-upload-result-content {
|
.cmdb-batch-upload-result-content {
|
||||||
background-color: rgba(240, 245, 255, 0.35);
|
background-color: rgba(240, 245, 255, 0.35);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@@ -15,21 +15,35 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button size="small" icon="plus" type="primary" @click="$refs.create.handleOpen(true, 'create')">
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost"
|
||||||
|
ghost
|
||||||
|
@click="$refs.create.handleOpen(true, 'create')"
|
||||||
|
><ops-icon type="veops-increase" />
|
||||||
{{ $t('create') }}
|
{{ $t('create') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
|
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs">
|
||||||
<a-popconfirm
|
<a-button
|
||||||
:title="
|
type="primary"
|
||||||
$t('cmdb.preference.confirmcancelSub2', { name: `${this.$route.meta.title || this.$route.meta.name}` })
|
ghost
|
||||||
"
|
class="ops-button-ghost"
|
||||||
:ok-text="$t('confirm')"
|
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
|
||||||
:cancel-text="$t('cancel')"
|
|
||||||
@confirm="unsubscribe"
|
|
||||||
placement="bottomRight"
|
|
||||||
>
|
>
|
||||||
<a-button size="small" icon="star" type="primary" ghost>{{ $t('cmdb.preference.cancelSub') }}</a-button>
|
</EditAttrsPopover>
|
||||||
</a-popconfirm>
|
<a-dropdown v-model="visible">
|
||||||
|
<a-button type="primary" ghost class="ops-button-ghost">···</a-button>
|
||||||
|
<a-menu slot="overlay" @click="handleMenuClick">
|
||||||
|
<a-menu-item @click="handlePerm" key="grant">
|
||||||
|
<a-icon type="user-add" />
|
||||||
|
{{ $t('grant') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="cancelSub" @click="unsubscribe">
|
||||||
|
<a-icon type="star" />
|
||||||
|
{{ $t('cmdb.preference.cancelSub') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="cmdb-ci-main">
|
<div class="cmdb-ci-main">
|
||||||
@@ -86,7 +100,6 @@
|
|||||||
:scroll-y="{ enabled: true, gt: 20 }"
|
:scroll-y="{ enabled: true, gt: 20 }"
|
||||||
:scroll-x="{ enabled: true, gt: 0 }"
|
:scroll-x="{ enabled: true, gt: 0 }"
|
||||||
class="ops-unstripe-table"
|
class="ops-unstripe-table"
|
||||||
:style="{ margin: '0 -12px' }"
|
|
||||||
:custom-config="{ storage: true }"
|
:custom-config="{ storage: true }"
|
||||||
>
|
>
|
||||||
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
|
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
|
||||||
@@ -219,11 +232,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</vxe-table-column>
|
</vxe-table-column>
|
||||||
<vxe-column align="left" field="operate" fixed="right" width="120">
|
<vxe-column align="left" field="operate" fixed="right" width="80">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>{{ $t('operation') }}</span>
|
<span>{{ $t('operation') }}</span>
|
||||||
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" />
|
|
||||||
<!-- <a-icon class="operation-icon" type="control" /> -->
|
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<a-space>
|
<a-space>
|
||||||
@@ -342,7 +353,7 @@ export default {
|
|||||||
// if (this.selectedRowKeys && this.selectedRowKeys.length) {
|
// if (this.selectedRowKeys && this.selectedRowKeys.length) {
|
||||||
// return this.windowHeight - 246
|
// return this.windowHeight - 246
|
||||||
// }
|
// }
|
||||||
return this.windowHeight - 210
|
return this.windowHeight - 240
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -377,6 +388,7 @@ export default {
|
|||||||
passwordValue: {},
|
passwordValue: {},
|
||||||
lastEditCiId: null,
|
lastEditCiId: null,
|
||||||
isContinueCloseEdit: true,
|
isContinueCloseEdit: true,
|
||||||
|
visible: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -916,15 +928,24 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
unsubscribe(ciType, type = 'all') {
|
unsubscribe(ciType, type = 'all') {
|
||||||
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')]
|
const that = this
|
||||||
|
this.$confirm({
|
||||||
|
title: that.$t('warning'),
|
||||||
|
content: that.$t('cmdb.preference.confirmcancelSub2', {
|
||||||
|
name: `${that.$route.meta.title || that.$route.meta.name}`,
|
||||||
|
}),
|
||||||
|
onOk() {
|
||||||
|
const promises = [subscribeCIType(that.typeId, ''), subscribeTreeView(that.typeId, '')]
|
||||||
Promise.all(promises).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||||
if (Number(ciType) === Number(lastTypeId)) {
|
if (Number(ciType) === Number(lastTypeId)) {
|
||||||
localStorage.setItem('ops_ci_typeid', '')
|
localStorage.setItem('ops_ci_typeid', '')
|
||||||
}
|
}
|
||||||
this.$message.success(this.$t('cmdb.preference.cancelSubSuccess'))
|
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
|
||||||
this.resetRoute()
|
that.resetRoute()
|
||||||
this.$router.push('/cmdb/preference')
|
that.$router.push('/cmdb/preference')
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resetRoute() {
|
resetRoute() {
|
||||||
@@ -957,6 +978,11 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
handleMenuClick(e) {
|
||||||
|
if (e.key === 'grant') {
|
||||||
|
this.visible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -968,11 +994,11 @@ export default {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
.cmdb-ci {
|
.cmdb-ci {
|
||||||
margin-bottom: -24px;
|
|
||||||
.cmdb-ci-main {
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 15px;
|
padding: 20px;
|
||||||
padding: 12px;
|
border-radius: @border-radius-box;
|
||||||
}
|
height: calc(100vh - 64px);
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: -24px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -106,9 +106,9 @@
|
|||||||
<a-date-picker
|
<a-date-picker
|
||||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||||
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
|
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||||
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
|
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
|
||||||
/>
|
/>
|
||||||
<a-input
|
<a-input
|
||||||
v-if="getFieldType(list.name) === 'input'"
|
v-if="getFieldType(list.name) === 'input'"
|
||||||
@@ -220,6 +220,7 @@ export default {
|
|||||||
if (otherGroupAttr.length) {
|
if (otherGroupAttr.length) {
|
||||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||||
}
|
}
|
||||||
|
console.log(otherGroupAttr, _attributesByGroup)
|
||||||
this.attributesByGroup = _attributesByGroup
|
this.attributesByGroup = _attributesByGroup
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -296,6 +297,38 @@ export default {
|
|||||||
_this.$emit('reload', { ci_id: res.ci_id })
|
_this.$emit('reload', { ci_id: res.ci_id })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this.form.validateFields((err, values) => {
|
||||||
|
// if (err) {
|
||||||
|
// _this.$message.error('字段填写不符合要求!')
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// Object.keys(values).forEach((k) => {
|
||||||
|
// if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) {
|
||||||
|
// values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
// }
|
||||||
|
// const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||||
|
// if (_tempFind.value_type === '6') {
|
||||||
|
// values[k] = values[k] ? JSON.parse(values[k]) : undefined
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (_this.action === 'update') {
|
||||||
|
// _this.$emit('submit', values)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// values.ci_type = _this.typeId
|
||||||
|
// console.log(values)
|
||||||
|
// this.attributesByGroup.forEach((group) => {
|
||||||
|
// this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData()
|
||||||
|
// })
|
||||||
|
// console.log(1111)
|
||||||
|
// // addCI(values).then((res) => {
|
||||||
|
// // _this.$message.success('新增成功!')
|
||||||
|
// // _this.visible = false
|
||||||
|
// // _this.$emit('reload')
|
||||||
|
// // })
|
||||||
|
// })
|
||||||
},
|
},
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
@@ -340,7 +373,7 @@ export default {
|
|||||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||||
return 'input_number'
|
return 'input_number'
|
||||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||||
return this.valueTypeMap[_find.value_type]
|
return _find.value_type
|
||||||
} else {
|
} else {
|
||||||
return 'input'
|
return 'input'
|
||||||
}
|
}
|
||||||
@@ -363,6 +396,9 @@ export default {
|
|||||||
this.batchUpdateLists.splice(_idx, 1)
|
this.batchUpdateLists.splice(_idx, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// filterOption(input, option) {
|
||||||
|
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
// },
|
||||||
handleFocusInput(e, attr) {
|
handleFocusInput(e, attr) {
|
||||||
console.log(attr)
|
console.log(attr)
|
||||||
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
|
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tab_2">
|
<a-tab-pane key="tab_2">
|
||||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||||
<div :style="{ height: '100%', padding: '24px' }">
|
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||||
</div>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
@@ -11,9 +11,11 @@
|
|||||||
@setFixedList="setFixedList"
|
@setFixedList="setFixedList"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<slot>
|
||||||
<div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }">
|
<div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }">
|
||||||
<a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" />
|
<a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" />
|
||||||
</div>
|
</div>
|
||||||
|
</slot>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }">
|
<div class="attr-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||||
<div v-if="adCITypeList && adCITypeList.length">
|
<div v-if="adCITypeList && adCITypeList.length">
|
||||||
<a-tabs size="small" v-model="currentTab">
|
<a-tabs size="small" v-model="currentTab">
|
||||||
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
|
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
|
||||||
@@ -207,7 +207,7 @@ export default {
|
|||||||
@import '~@/style/static.less';
|
@import '~@/style/static.less';
|
||||||
.attr-ad {
|
.attr-ad {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 12px;
|
padding: 0 20px;
|
||||||
.attr-ad-header {
|
.attr-ad-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }">
|
<div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }">
|
||||||
<a
|
<a
|
||||||
v-if="!adrIsInner"
|
v-if="!adrIsInner"
|
||||||
:style="{ position: 'absolute', right: 0, top: 0 }"
|
:style="{ position: 'absolute', right: 0, top: 0 }"
|
||||||
|
@@ -1,11 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
|
<div
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
if (isAdd) {
|
||||||
|
$emit('add')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
|
||||||
|
>
|
||||||
|
<div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
|
||||||
|
<template v-if="!isAdd">
|
||||||
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
|
||||||
<div class="attribute-card-content">
|
<div class="attribute-card-content">
|
||||||
<div
|
<div :class="{ 'attribute-card-value-type-icon': true, handle: !inherited }">
|
||||||
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
|
|
||||||
:style="{ ...getPropertyStyle(property) }"
|
|
||||||
>
|
|
||||||
<ValueTypeIcon :attr="property" />
|
<ValueTypeIcon :attr="property" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
|
||||||
@@ -38,21 +46,20 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<a-descriptions layout="horizontal" bordered size="small" :column="2">
|
<a-descriptions layout="horizontal" bordered size="small" :column="2">
|
||||||
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
|
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
|
||||||
<components
|
<ops-icon
|
||||||
:is="`ops_${item.property}`"
|
:style="{ color: property[item.property] ? '#7f97fa' : '', fontSize: '10px' }"
|
||||||
v-if="property[item.property]"
|
:type="`ops-${item.property}-disabled`"
|
||||||
:style="{ width: '1em', height: '1em' }"
|
|
||||||
/>
|
/>
|
||||||
<ops-icon v-else :type="`ops-${item.property}-disabled`" />
|
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label></a-descriptions-item>
|
<a-descriptions-item label></a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
</div>
|
</div>
|
||||||
<a-space :style="{ cursor: 'pointer' }">
|
<a-space :style="{ cursor: 'pointer' }">
|
||||||
<components
|
<ops-icon
|
||||||
v-for="item in propertyList.filter((p) => property[p.property])"
|
v-for="item in propertyList.filter((p) => property[p.property])"
|
||||||
:key="item.property"
|
:key="item.property"
|
||||||
:is="`ops_${item.property}`"
|
:style="{ color: '#7f97fa', fontSize: '10px' }"
|
||||||
|
:type="`ops-${item.property}-disabled`"
|
||||||
/>
|
/>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
@@ -62,10 +69,15 @@
|
|||||||
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
|
||||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
|
<a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a><a-icon type="plus"/></a>
|
||||||
|
<div>{{ $t('cmdb.ciType.addAttribute') }}</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -82,10 +94,15 @@ import {
|
|||||||
ops_is_unique,
|
ops_is_unique,
|
||||||
} from '@/core/icons'
|
} from '@/core/icons'
|
||||||
import { valueTypeMap } from '../../utils/const'
|
import { valueTypeMap } from '../../utils/const'
|
||||||
import { getPropertyStyle } from '../../utils/helper'
|
|
||||||
import TriggerForm from './triggerForm.vue'
|
import TriggerForm from './triggerForm.vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'AttributeCard',
|
name: 'AttributeCard',
|
||||||
|
inject: {
|
||||||
|
unique: {
|
||||||
|
from: 'unique',
|
||||||
|
default: () => undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
ValueTypeIcon,
|
ValueTypeIcon,
|
||||||
TriggerForm,
|
TriggerForm,
|
||||||
@@ -114,11 +131,21 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
isAdd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isUnique() {
|
||||||
|
if (this.unique) {
|
||||||
|
return this.property?.name === this.unique()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
valueTypeMap() {
|
valueTypeMap() {
|
||||||
return valueTypeMap()
|
return valueTypeMap()
|
||||||
},
|
},
|
||||||
@@ -147,11 +174,10 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
inherited() {
|
inherited() {
|
||||||
return this.property.inherited || false
|
return this.property?.inherited || false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyStyle,
|
|
||||||
handleEdit() {
|
handleEdit() {
|
||||||
this.$emit('edit')
|
this.$emit('edit')
|
||||||
},
|
},
|
||||||
@@ -196,11 +222,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
.attribute-card {
|
.attribute-card {
|
||||||
width: 182px;
|
width: 182px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
background: #f8faff;
|
background: @primary-color_6;
|
||||||
border-radius: 5px;
|
border-radius: 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
@@ -219,7 +246,7 @@ export default {
|
|||||||
.attribute-card-value-type-icon {
|
.attribute-card-value-type-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@@ -243,7 +270,7 @@ export default {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.attribute-card-name-default-show {
|
.attribute-card-name-default-show {
|
||||||
color: #2f54eb;
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
.attribute-card_value-type {
|
.attribute-card_value-type {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@@ -270,20 +297,67 @@ export default {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%);
|
background: @primary-color_5;
|
||||||
border-radius: 0px 0px 5px 5px;
|
border-radius: 0px 0px 2px 2px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
border-top: 1px solid @primary-color_3;
|
||||||
.attribute-card-operation {
|
.attribute-card-operation {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.attribute-card-uniqueKey {
|
||||||
|
position: absolute;
|
||||||
|
right: -12px;
|
||||||
|
top: 0;
|
||||||
|
color: @func-color_2;
|
||||||
|
background: url('../../assets/unique_card.png');
|
||||||
|
font-size: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
min-width: 55px;
|
||||||
|
padding: 2px 0 2px 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.attribute-card-inherited {
|
.attribute-card-inherited {
|
||||||
background: #f3f4f7;
|
background: @primary-color_7;
|
||||||
.attribute-card-footer {
|
.attribute-card-footer {
|
||||||
background: #eaedf3;
|
background: @text-color_7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-card-add {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
background-color: inherit;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: @primary-color_6;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||||
|
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||||
|
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||||
|
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color: @text-color_4;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -172,8 +172,6 @@ export default {
|
|||||||
margin-right: 60px;
|
margin-right: 60px;
|
||||||
.ant-input-group.ant-input-group-compact > *:first-child,
|
.ant-input-group.ant-input-group-compact > *:first-child,
|
||||||
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
|
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
|
||||||
border-top-left-radius: 20px !important;
|
|
||||||
border-bottom-left-radius: 20px !important;
|
|
||||||
background-color: #custom_colors[color_1];
|
background-color: #custom_colors[color_1];
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -202,7 +200,6 @@ export default {
|
|||||||
.ant-input {
|
.ant-input {
|
||||||
background-color: #f0f5ff;
|
background-color: #f0f5ff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 20px;
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@@ -12,14 +12,28 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</span>
|
</span>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
<div class="ci-types-attributes" :style="{ height: `${windowHeight - 130}px` }">
|
||||||
<a-space style="margin-bottom: 10px">
|
<a-space style="margin-bottom: 10px">
|
||||||
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
|
<a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</a-button>
|
||||||
$t('cmdb.ciType.group')
|
<a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
|
||||||
}}</a-button>
|
<div>
|
||||||
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
|
<a-tooltip
|
||||||
$t('cmdb.ciType.uniqueConstraint')
|
v-for="type in Object.keys(valueTypeMap)"
|
||||||
}}</a-button>
|
:key="type"
|
||||||
|
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[type] })"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
@click="handleFilterType(type)"
|
||||||
|
:class="{
|
||||||
|
'ci-types-attributes-filter': true,
|
||||||
|
'ci-types-attributes-filter-selected': attrTypeFilter.includes(type),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<ops-icon :type="getPropertyIcon({ value_type: type })" />
|
||||||
|
{{ valueTypeMap[type] }}
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
</a-space>
|
</a-space>
|
||||||
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
|
||||||
<div>
|
<div>
|
||||||
@@ -29,13 +43,6 @@
|
|||||||
>
|
>
|
||||||
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
|
||||||
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
|
||||||
|
|
||||||
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
|
|
||||||
<a-icon type="edit" />
|
|
||||||
</a>
|
|
||||||
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
|
|
||||||
<a-icon type="edit" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span>
|
<span>
|
||||||
@@ -64,21 +71,24 @@
|
|||||||
@click="handleMoveGroup(index, index + 1)"
|
@click="handleMoveGroup(index, index + 1)"
|
||||||
/></a>
|
/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
<a-dropdown>
|
||||||
<a-tooltip>
|
<a><ops-icon type="veops-more"/></a>
|
||||||
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template>
|
<a-menu slot="overlay">
|
||||||
<a><a-icon type="plus" @click="handleAddGroupAttr(index)"/></a>
|
<a-menu-item @click="handleAddGroupAttr(index)">
|
||||||
</a-tooltip>
|
<template slot="title"></template>
|
||||||
<a-tooltip>
|
<a-icon type="plus" />
|
||||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
{{ $t('cmdb.ciType.addAttribute') }}
|
||||||
<a
|
</a-menu-item>
|
||||||
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
|
<a-menu-item @click="handleEditGroupName(index, CITypeGroup)" :disabled="CITypeGroup.inherited">
|
||||||
:disabled="CITypeGroup.inherited"
|
<a-icon type="edit" />
|
||||||
><a-icon
|
{{ $t('cmdb.ciType.editGroupName') }}
|
||||||
type="delete"
|
</a-menu-item>
|
||||||
@click="handleDeleteGroup(CITypeGroup)"
|
<a-menu-item @click="handleDeleteGroup(CITypeGroup)" :disabled="CITypeGroup.inherited">
|
||||||
/></a>
|
<a-icon type="delete" />
|
||||||
</a-tooltip>
|
{{ $t('cmdb.ciType.deleteGroup') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="ci-types-attributes-wrapper">
|
<div class="ci-types-attributes-wrapper">
|
||||||
@@ -98,7 +108,7 @@
|
|||||||
handle=".handle"
|
handle=".handle"
|
||||||
>
|
>
|
||||||
<AttributeCard
|
<AttributeCard
|
||||||
v-for="item in CITypeGroup.attributes"
|
v-for="item in filterValueType(CITypeGroup.attributes)"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@edit="handleEditProperty(item)"
|
@edit="handleEditProperty(item)"
|
||||||
:property="item"
|
:property="item"
|
||||||
@@ -106,6 +116,7 @@
|
|||||||
:CITypeId="CITypeId"
|
:CITypeId="CITypeId"
|
||||||
:attributes="attributes"
|
:attributes="attributes"
|
||||||
/>
|
/>
|
||||||
|
<AttributeCard isAdd @add="handleAddGroupAttr(index)" />
|
||||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,10 +125,11 @@
|
|||||||
<div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }">
|
<div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }">
|
||||||
<span style="font-weight:700">{{ $t('other') }}</span>
|
<span style="font-weight:700">{{ $t('other') }}</span>
|
||||||
<span style="color: #c3cdd7;margin-left:5px;">({{ otherGroupAttributes.length }})</span>
|
<span style="color: #c3cdd7;margin-left:5px;">({{ otherGroupAttributes.length }})</span>
|
||||||
|
<span style="color: #c3cdd7;margin-left:5px;font-size:10px;">{{ $t('cmdb.ciType.otherGroupTips') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template>
|
<template slot="title">{{ $t('cmdb.ciType.addAttribute') }}</template>
|
||||||
<a @click="handleAddGroupAttr(undefined)"><a-icon type="plus"/></a>
|
<a @click="handleAddGroupAttr(undefined)"><a-icon type="plus"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +150,7 @@
|
|||||||
handle=".handle"
|
handle=".handle"
|
||||||
>
|
>
|
||||||
<AttributeCard
|
<AttributeCard
|
||||||
v-for="item in otherGroupAttributes"
|
v-for="item in filterValueType(otherGroupAttributes)"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@edit="handleEditProperty(item)"
|
@edit="handleEditProperty(item)"
|
||||||
:property="item"
|
:property="item"
|
||||||
@@ -146,6 +158,7 @@
|
|||||||
:CITypeId="CITypeId"
|
:CITypeId="CITypeId"
|
||||||
:attributes="attributes"
|
:attributes="attributes"
|
||||||
/>
|
/>
|
||||||
|
<AttributeCard isAdd @add="handleAddGroupAttr(undefined)" />
|
||||||
<i></i> <i></i> <i></i> <i></i> <i></i>
|
<i></i> <i></i> <i></i> <i></i> <i></i>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,6 +198,8 @@ import AttributeCard from './attributeCard.vue'
|
|||||||
import AttributeEditForm from './attributeEditForm.vue'
|
import AttributeEditForm from './attributeEditForm.vue'
|
||||||
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
|
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
|
||||||
import UniqueConstraint from './uniqueConstraint.vue'
|
import UniqueConstraint from './uniqueConstraint.vue'
|
||||||
|
import { valueTypeMap } from '../../utils/const'
|
||||||
|
import { getPropertyIcon } from '../../utils/helper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AttributesTable',
|
name: 'AttributesTable',
|
||||||
@@ -214,6 +229,8 @@ export default {
|
|||||||
otherGroupAttributes: [],
|
otherGroupAttributes: [],
|
||||||
addGroupModal: false,
|
addGroupModal: false,
|
||||||
newGroupName: '',
|
newGroupName: '',
|
||||||
|
attrTypeFilter: [],
|
||||||
|
unique: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -223,9 +240,17 @@ export default {
|
|||||||
windowHeight() {
|
windowHeight() {
|
||||||
return this.$store.state.windowHeight
|
return this.$store.state.windowHeight
|
||||||
},
|
},
|
||||||
|
valueTypeMap() {
|
||||||
|
return valueTypeMap()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return { refresh: this.getCITypeGroupData }
|
return {
|
||||||
|
refresh: this.getCITypeGroupData,
|
||||||
|
unique: () => {
|
||||||
|
return this.unique
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeCreate() {},
|
beforeCreate() {},
|
||||||
created() {},
|
created() {},
|
||||||
@@ -233,6 +258,7 @@ export default {
|
|||||||
this.getCITypeGroupData()
|
this.getCITypeGroupData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getPropertyIcon,
|
||||||
handleEditProperty(property) {
|
handleEditProperty(property) {
|
||||||
this.$refs.attributeEditForm.handleEdit(property, this.attributes)
|
this.$refs.attributeEditForm.handleEdit(property, this.attributes)
|
||||||
},
|
},
|
||||||
@@ -260,6 +286,7 @@ export default {
|
|||||||
group.editable = false
|
group.editable = false
|
||||||
group.originOrder = group.order
|
group.originOrder = group.order
|
||||||
group.originName = group.name
|
group.originName = group.name
|
||||||
|
// group.attributes = group.attributes.sort((a, b) => a.order - b.order)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.otherGroupAttributes = this.attributes
|
this.otherGroupAttributes = this.attributes
|
||||||
@@ -282,6 +309,7 @@ export default {
|
|||||||
Promise.all(promises).then((values) => {
|
Promise.all(promises).then((values) => {
|
||||||
console.log(values)
|
console.log(values)
|
||||||
this.attributes = values[0].attributes
|
this.attributes = values[0].attributes
|
||||||
|
this.unique = values[0].unique
|
||||||
const temp = {}
|
const temp = {}
|
||||||
this.attributes.forEach((attr) => {
|
this.attributes.forEach((attr) => {
|
||||||
temp[attr.id] = attr
|
temp[attr.id] = attr
|
||||||
@@ -387,6 +415,7 @@ export default {
|
|||||||
group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id))
|
group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// this.CITypeGroups = this.CITypeGroups
|
||||||
|
|
||||||
this.otherGroupAttributes.forEach((attributes) => {
|
this.otherGroupAttributes.forEach((attributes) => {
|
||||||
if (values.groupId === null) {
|
if (values.groupId === null) {
|
||||||
@@ -523,20 +552,66 @@ export default {
|
|||||||
handleOpenUniqueConstraint() {
|
handleOpenUniqueConstraint() {
|
||||||
this.$refs.uniqueConstraint.open(this.attributes)
|
this.$refs.uniqueConstraint.open(this.attributes)
|
||||||
},
|
},
|
||||||
|
handleFilterType(type) {
|
||||||
|
const _idx = this.attrTypeFilter.findIndex((item) => item === type)
|
||||||
|
if (_idx > -1) {
|
||||||
|
this.attrTypeFilter.splice(_idx, 1)
|
||||||
|
} else {
|
||||||
|
this.attrTypeFilter.push(type)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filterValueType(array) {
|
||||||
|
const { attrTypeFilter } = this
|
||||||
|
return array.filter((attr) => {
|
||||||
|
if (!attrTypeFilter.length) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if (attrTypeFilter.includes('7') && attr.is_password) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (attrTypeFilter.includes('8') && attr.is_link) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
attrTypeFilter.includes(attr.value_type) &&
|
||||||
|
attr.value_type === '2' &&
|
||||||
|
(attr.is_password || attr.is_link)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (attrTypeFilter.includes(attr.value_type)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.fold {
|
.fold {
|
||||||
width: calc(100% - 216px);
|
width: calc(100% - 216px);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ci-types-attributes {
|
.ci-types-attributes {
|
||||||
padding: 16px 24px 24px;
|
padding: 0 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
.ci-types-attributes-filter {
|
||||||
|
color: @text-color_4;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.ci-types-attributes-filter:hover,
|
||||||
|
.ci-types-attributes-filter-selected {
|
||||||
|
background-color: @primary-color_5;
|
||||||
|
}
|
||||||
.property-item-empty {
|
.property-item-empty {
|
||||||
color: #40a9ff;
|
color: #40a9ff;
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :bordered="false" :bodyStyle="{ padding: '0' }">
|
<a-card :bordered="false" :bodyStyle="{ padding: '0' }">
|
||||||
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card">
|
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab">
|
||||||
<a-tab-pane key="1" :tab="$t('cmdb.ciType.attributes')">
|
<a-tab-pane key="1" :tab="$t('cmdb.ciType.attributes')">
|
||||||
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
|
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
|
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
|
||||||
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
||||||
|
<div class="citype-detail-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||||
|
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -74,4 +76,13 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
|
.citype-detail-title {
|
||||||
|
border-left: 4px solid @primary-color;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -8,11 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<SplitPane
|
<SplitPane
|
||||||
v-else
|
v-else
|
||||||
:min="280"
|
:min="220"
|
||||||
:max="500"
|
:max="500"
|
||||||
:paneLengthPixel.sync="paneLengthPixel"
|
:paneLengthPixel.sync="paneLengthPixel"
|
||||||
appName="cmdb-ci-types"
|
appName="cmdb-ci-types"
|
||||||
triggerColor="#F0F5FF"
|
|
||||||
:triggerLength="18"
|
:triggerLength="18"
|
||||||
>
|
>
|
||||||
<template #one>
|
<template #one>
|
||||||
@@ -22,22 +21,23 @@
|
|||||||
:disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')"
|
:disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
icon="plus"
|
ghost
|
||||||
@click="handleClickAddGroup"
|
@click="handleClickAddGroup"
|
||||||
class="ops-button-primary"
|
class="ops-button-ghost"
|
||||||
>{{ $t('cmdb.ciType.group') }}</a-button
|
><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button
|
||||||
>
|
>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a
|
<span
|
||||||
|
:style="{ cursor: 'pointer' }"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.attributeStore.open()
|
$refs.attributeStore.open()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>{{ $t('cmdb.ciType.attributeLibray') }}</a
|
>{{ $t('cmdb.ciType.attributeLibray') }}
|
||||||
>
|
</span>
|
||||||
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
|
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
|
||||||
<a><ops-icon type="ops-menu"/></a>
|
<ops-icon type="ops-menu" :style="{ cursor: 'pointer' }" />
|
||||||
<a-menu slot="overlay">
|
<a-menu slot="overlay">
|
||||||
<a-menu-item key="0">
|
<a-menu-item key="0">
|
||||||
<a-upload
|
<a-upload
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
|
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
|
||||||
<a><a-icon type="plus" @click="handleCreate(g)"/></a>
|
<a><ops-icon type="veops-increase" @click="handleCreate(g)"/></a>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<template v-if="g.id !== -1">
|
<template v-if="g.id !== -1">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<OpsMoveIcon
|
<OpsMoveIcon
|
||||||
style="width: 17px; height: 17px; display: none; position: absolute; left: 15px; top: 5px"
|
style="width: 17px; height: 17px; display: none; position: absolute; left: -1px; top: 8px"
|
||||||
/>
|
/>
|
||||||
<span class="ci-types-left-detail-icon">
|
<span class="ci-types-left-detail-icon">
|
||||||
<template v-if="ci.icon">
|
<template v-if="ci.icon">
|
||||||
@@ -134,18 +134,33 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
|
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
|
||||||
<a-space class="ci-types-left-detail-action">
|
<a-dropdown :getPopupContainer="(trigger) => trigger">
|
||||||
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
|
<a class="ci-types-left-detail-action">
|
||||||
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
|
<ops-icon type="veops-more" />
|
||||||
<a
|
</a>
|
||||||
|
<a-menu slot="overlay">
|
||||||
|
<a-menu-item @click="(e) => handlePerm(e, ci)">
|
||||||
|
<a-icon type="user-add" />
|
||||||
|
{{ $t('grant') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="(e) => handleEdit(e, ci)">
|
||||||
|
<a-icon type="edit" />
|
||||||
|
{{ $t('cmdb.ciType.editCIType') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||||
:disabled="ci.inherited"
|
:disabled="ci.inherited"
|
||||||
@click="(e) => handleDownloadCiType(e, ci)"
|
@click="(e) => handleDownloadCiType(e, ci)"
|
||||||
>
|
>
|
||||||
<a-icon type="download" />
|
<a-icon type="download" />
|
||||||
</a>
|
{{ $t('cmdb.ciType.downloadType') }}
|
||||||
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
|
</a-menu-item>
|
||||||
</a-space>
|
<a-menu-item @click="(e) => handleDelete(e, ci)">
|
||||||
|
<a-icon type="delete" />
|
||||||
|
{{ $t('cmdb.ciType.deleteCIType') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,7 +285,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-select>
|
</el-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('cmdb.ciType.uniqueKey')">
|
<a-form-item :help="$t('cmdb.ciType.uniqueKeyTips')" :label="$t('cmdb.ciType.uniqueKey')">
|
||||||
<el-select
|
<el-select
|
||||||
size="small"
|
size="small"
|
||||||
filterable
|
filterable
|
||||||
@@ -446,7 +461,6 @@ export default {
|
|||||||
},
|
},
|
||||||
currentCName() {
|
currentCName() {
|
||||||
if (this.currentId) {
|
if (this.currentId) {
|
||||||
console.log(this.currentId)
|
|
||||||
if (this.currentId.split('%')[2] !== 'null') {
|
if (this.currentId.split('%')[2] !== 'null') {
|
||||||
return this.currentId.split('%')[2]
|
return this.currentId.split('%')[2]
|
||||||
}
|
}
|
||||||
@@ -807,8 +821,8 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDelete(e, record) {
|
handleDelete(e, record) {
|
||||||
e.preventDefault()
|
e.domEvent.preventDefault()
|
||||||
e.stopPropagation()
|
e.domEvent.stopPropagation()
|
||||||
const that = this
|
const that = this
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: that.$t('warning'),
|
title: that.$t('warning'),
|
||||||
@@ -823,8 +837,8 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDownloadCiType(e, ci) {
|
handleDownloadCiType(e, ci) {
|
||||||
e.preventDefault()
|
e.domEvent.preventDefault()
|
||||||
e.stopPropagation()
|
e.domEvent.stopPropagation()
|
||||||
const x = new XMLHttpRequest()
|
const x = new XMLHttpRequest()
|
||||||
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
|
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
|
||||||
x.responseType = 'blob'
|
x.responseType = 'blob'
|
||||||
@@ -845,8 +859,8 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async handleEdit(e, record) {
|
async handleEdit(e, record) {
|
||||||
e.preventDefault()
|
e.domEvent.preventDefault()
|
||||||
e.stopPropagation()
|
e.domEvent.stopPropagation()
|
||||||
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
|
||||||
this.drawerVisible = true
|
this.drawerVisible = true
|
||||||
await getCITypeAttributesById(record.id).then((res) => {
|
await getCITypeAttributesById(record.id).then((res) => {
|
||||||
@@ -899,8 +913,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handlePerm(e, ci) {
|
handlePerm(e, ci) {
|
||||||
e.preventDefault()
|
e.domEvent.preventDefault()
|
||||||
e.stopPropagation()
|
e.domEvent.stopPropagation()
|
||||||
roleHasPermissionToGrant({
|
roleHasPermissionToGrant({
|
||||||
app_id: 'cmdb',
|
app_id: 'cmdb',
|
||||||
resource_type_name: 'CIType',
|
resource_type_name: 'CIType',
|
||||||
@@ -932,6 +946,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
.ci-types-wrap {
|
.ci-types-wrap {
|
||||||
margin: 0 0 -24px 0;
|
margin: 0 0 -24px 0;
|
||||||
.ci-types-empty {
|
.ci-types-empty {
|
||||||
@@ -945,21 +961,24 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
float: left;
|
float: left;
|
||||||
border-top-left-radius: 20px;
|
|
||||||
border-top-right-radius: 20px;
|
|
||||||
.ci-types-left-content {
|
.ci-types-left-content {
|
||||||
max-height: calc(100% - 45px);
|
max-height: calc(100% - 45px);
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.ci-types-left-title {
|
.ci-types-left-title {
|
||||||
padding: 10px 15px;
|
padding: 10px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
color: @text-color_3;
|
||||||
}
|
}
|
||||||
.ci-types-left-group {
|
.ci-types-left-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 8px 15px;
|
padding: 8px 0 8px 14px;
|
||||||
color: rgb(99, 99, 99);
|
color: rgb(99, 99, 99);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -972,7 +991,7 @@ export default {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #e1efff;
|
background-color: @primary-color_3;
|
||||||
> div:nth-child(2) {
|
> div:nth-child(2) {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
@@ -982,17 +1001,25 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ci-types-left-detail {
|
.ci-types-left-detail {
|
||||||
padding: 3px 14px 3px 36px;
|
padding: 3px 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
.ci-types-left-detail-action {
|
.ci-types-left-detail-action {
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
.ci-types-left-detail-title {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
.ci-types-left-detail-icon {
|
.ci-types-left-detail-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1009,7 +1036,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #e1efff;
|
background-color: @primary-color_3;
|
||||||
svg {
|
svg {
|
||||||
display: inline !important;
|
display: inline !important;
|
||||||
}
|
}
|
||||||
@@ -1019,7 +1046,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.selected {
|
.selected {
|
||||||
background-color: #e1efff;
|
background-color: @primary-color_3;
|
||||||
.ci-types-left-detail-title {
|
.ci-types-left-detail-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1055,7 @@ export default {
|
|||||||
.ci-types-right {
|
.ci-types-right {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
.ci-types-right-empty {
|
.ci-types-right-empty {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -1039,7 +1067,6 @@ export default {
|
|||||||
.ci-types-left,
|
.ci-types-left,
|
||||||
.ci-types-right {
|
.ci-types-right {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #fff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relation-ad" :style="{ height: `${windowHeight - 104}px` }">
|
<div class="relation-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||||
<div class="relation-ad-item" v-for="item in relationList" :key="item.id">
|
<div class="relation-ad-item" v-for="item in relationList" :key="item.id">
|
||||||
<treeselect
|
<treeselect
|
||||||
class="custom-treeselect"
|
class="custom-treeselect"
|
||||||
@@ -253,7 +253,7 @@ export default {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.relation-ad {
|
.relation-ad {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 24px;
|
padding: 0 20px;
|
||||||
.relation-ad-item {
|
.relation-ad-item {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="{ padding: '16px 24px 24px' }">
|
<div :style="{ padding: '0 20px 20px' }">
|
||||||
<a-button
|
<a-button
|
||||||
|
v-if="!isInGrantComp"
|
||||||
style="margin-bottom: 10px"
|
style="margin-bottom: 10px"
|
||||||
@click="handleCreate"
|
@click="handleCreate"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
<template #default="{row}">
|
<template #default="{row}">
|
||||||
<a-space v-if="!row.isParent && row.source_ci_type_id">
|
<a-space v-if="!row.isParent && row.source_ci_type_id">
|
||||||
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
|
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
|
||||||
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
|
<a-popconfirm v-if="!isInGrantComp" :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
|
||||||
<a style="color: red;"><a-icon type="delete"/></a>
|
<a style="color: red;"><a-icon type="delete"/></a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-space>
|
</a-space>
|
||||||
@@ -148,6 +149,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
isInGrantComp: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -178,7 +183,9 @@ export default {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
this.getCITypes()
|
this.getCITypes()
|
||||||
this.getRelationTypes()
|
this.getRelationTypes()
|
||||||
|
if (!this.isInGrantComp) {
|
||||||
await this.getCITypeParent()
|
await this.getCITypeParent()
|
||||||
|
}
|
||||||
this.getData()
|
this.getData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
:visible="visible"
|
:visible="visible"
|
||||||
@close="handleCancel"
|
@close="handleCancel"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<div class="custom-drawer-bottom-action">
|
<div class="custom-drawer-bottom-action">
|
||||||
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
|
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||||
|
@@ -134,6 +134,6 @@ export default {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.ci-types-triggers {
|
.ci-types-triggers {
|
||||||
padding: 16px 24px 24px;
|
padding: 0 20px 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user