Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
65ef58dea9 | ||
|
0a2e7aa99f | ||
|
8875e75883 | ||
|
2f03639c57 | ||
|
49bc5d94a9 | ||
|
39354e1293 | ||
|
d3714f3ecf | ||
|
729a616282 | ||
|
2d3a290aa3 | ||
|
9e885a5b12 | ||
|
f5822d7cba | ||
|
21ea553e74 | ||
|
e63038d1b6 | ||
|
d56806f511 | ||
|
7ac7fdc08e | ||
|
ba11707146 | ||
|
d49dc8a067 | ||
|
6bfb34fe2a | ||
|
2c7ed8c32d | ||
|
5b275af54e | ||
|
dde7ec6246 | ||
|
9181817e96 | ||
|
46b54bb7f2 | ||
|
fe63310c4e | ||
|
27c733aa2c | ||
|
2a8e9e684e | ||
|
095190a785 | ||
|
ef25c94b5d | ||
|
06ae1bcf13 | ||
|
9ead4e7d8d | ||
|
994a28dd25 | ||
|
74b587e46c | ||
|
091cd882bd | ||
|
73093db467 | ||
|
66e268ce68 | ||
|
a41d1a5e97 | ||
|
b4b728fe28 | ||
|
d16462d8b7 | ||
|
de7d98c0b4 | ||
|
51332c7236 | ||
|
bf1076fe4a | ||
|
3454a98cfb | ||
|
506dcbb40e | ||
|
5ac4517187 | ||
|
761e98884b | ||
|
073654624e | ||
|
df54244ff1 | ||
|
27e9919198 | ||
|
dc8b1a5de2 | ||
|
d8a7728f1d | ||
|
82881965fb | ||
|
1bb62022f1 | ||
|
ed445a8d82 | ||
|
3626b1a97e | ||
|
32529fba9b | ||
|
a042b4fe39 | ||
|
a0631414dc |
6
.env
Normal file
@@ -0,0 +1,6 @@
|
||||
MYSQL_ROOT_PASSWORD='123456'
|
||||
MYSQL_HOST='mysql'
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER='cmdb'
|
||||
MYSQL_DATABASE='cmdb'
|
||||
MYSQL_PASSWORD='123456'
|
@@ -87,7 +87,7 @@ docker compose up -d
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
|
@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
|
||||
environs = "==4.2.0"
|
||||
marshmallow = "==2.20.2"
|
||||
# async tasks
|
||||
celery = ">=5.3.1"
|
||||
celery = "==5.3.1"
|
||||
celery_once = "==3.0.1"
|
||||
more-itertools = "==5.0.0"
|
||||
kombu = ">=5.3.1"
|
||||
|
@@ -55,9 +55,12 @@ def cmdb_init_cache():
|
||||
for cr in ci_relations:
|
||||
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
if cr.ancestor_ids:
|
||||
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
|
||||
relations2.setdefault('{},{}'.format(cr.ancestor_ids, cr.first_ci_id), {}).update(
|
||||
{cr.second_ci_id: cr.second_ci.type_id})
|
||||
for i in relations:
|
||||
relations[i] = json.dumps(relations[i])
|
||||
for i in relations2:
|
||||
relations2[i] = json.dumps(relations2[i])
|
||||
if relations:
|
||||
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
|
||||
if relations2:
|
||||
@@ -125,7 +128,7 @@ def cmdb_init_acl():
|
||||
perms = [PermEnum.READ]
|
||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
||||
elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
|
||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||
@@ -323,7 +326,7 @@ def cmdb_inner_secrets_init(address):
|
||||
"""
|
||||
init inner secrets for password feature
|
||||
"""
|
||||
res, ok = KeyManage(backend=InnerKVManger).init()
|
||||
res, ok = KeyManage(backend=InnerKVManger()).init()
|
||||
if not ok:
|
||||
if res.get("status") == "failed":
|
||||
KeyManage.print_response(res)
|
||||
@@ -357,13 +360,13 @@ def cmdb_inner_secrets_unseal(address):
|
||||
"""
|
||||
unseal the secrets feature
|
||||
"""
|
||||
if not valid_address(address):
|
||||
return
|
||||
# if not valid_address(address):
|
||||
# return
|
||||
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
|
||||
for i in range(global_key_threshold):
|
||||
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
|
||||
assert token is not None
|
||||
resp = requests.post(address, headers={"Unseal-Token": token})
|
||||
resp = requests.post(address, headers={"Unseal-Token": token}, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
KeyManage.print_response(resp.json())
|
||||
if resp.json().get("status") in ["success", "skip"]:
|
||||
|
@@ -6,6 +6,7 @@ from werkzeug.datastructures import MultiDict
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.utils import CheckNewColumn
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
|
||||
@@ -209,57 +210,7 @@ def common_check_new_columns():
|
||||
"""
|
||||
add new columns to tables
|
||||
"""
|
||||
from api.extensions import db
|
||||
from sqlalchemy import inspect, text
|
||||
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def add_new_column(target_table_name, new_column):
|
||||
column_type = new_column.type.compile(engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
|
||||
engine = db.get_engine()
|
||||
inspector = inspect(engine)
|
||||
table_names = inspector.get_table_names()
|
||||
for table_name in table_names:
|
||||
existed_columns = inspector.get_columns(table_name)
|
||||
existed_column_name_list = [c['name'] for c in existed_columns]
|
||||
|
||||
model = get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
continue
|
||||
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
try:
|
||||
add_new_column(table_name, column)
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
|
||||
current_app.logger.error(e)
|
||||
CheckNewColumn().run()
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@@ -264,9 +264,11 @@ class CIManager(object):
|
||||
for attr_id in constraint.attr_ids:
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id,
|
||||
to_dict=False,
|
||||
value=ci_dict.get(id2name[attr_id]) or None)])
|
||||
values = value_table.get_by(attr_id=attr_id,
|
||||
value=ci_dict.get(id2name[attr_id]) or None,
|
||||
only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
|
||||
_ci_ids = set([i.ci_id for i in values])
|
||||
if ci_ids is None:
|
||||
ci_ids = _ci_ids
|
||||
else:
|
||||
@@ -437,14 +439,18 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
|
||||
current_app.logger.info((ci_id, ci_dict, __sync))
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
|
||||
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
@@ -498,11 +504,17 @@ class CIManager(object):
|
||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
if not __sync:
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_cache((ci_id, OperateType.UPDATE, record_id))
|
||||
|
||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||
if ref_ci_dict:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
if not __sync:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_add((ref_ci_dict, ci.id))
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
@@ -825,6 +837,149 @@ class CIManager(object):
|
||||
|
||||
return data.get('v')
|
||||
|
||||
def baseline(self, ci_ids, before_date):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
|
||||
if not ci_list:
|
||||
return dict()
|
||||
|
||||
ci2changed = dict()
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
ci2changed.setdefault(change['ci_id'], {})
|
||||
item = (change['old'],
|
||||
change['new'],
|
||||
change.get('is_list'),
|
||||
change.get('value_type'),
|
||||
change['operate_type'])
|
||||
if change.get('is_list'):
|
||||
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
|
||||
else:
|
||||
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
|
||||
|
||||
type2show_name = {}
|
||||
result = []
|
||||
for ci in ci_list:
|
||||
list_attr2item = {}
|
||||
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
|
||||
if not alias_name:
|
||||
continue
|
||||
if alias_name == ci.get('unique_alias'):
|
||||
continue
|
||||
|
||||
if ci.get('_type') not in type2show_name:
|
||||
ci_type = CITypeCache.get(ci.get('_type'))
|
||||
show_id = ci_type.show_id or ci_type.unique_id
|
||||
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
|
||||
|
||||
if isinstance(v, list):
|
||||
for old, new, is_list, value_type, operate_type in v:
|
||||
if alias_name not in list_attr2item:
|
||||
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
is_list=is_list,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=copy.deepcopy(ci.get(alias_name)),
|
||||
to=ci.get(alias_name) or [])
|
||||
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD:
|
||||
list_attr2item[alias_name]['to'].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
|
||||
list_attr2item[alias_name]['to'].append(old)
|
||||
continue
|
||||
|
||||
old, value_type = v[0], v[3]
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
if isinstance(old, (datetime.datetime, datetime.date)):
|
||||
old = str(old)
|
||||
if ci.get(alias_name) != old:
|
||||
item = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=ci.get(alias_name),
|
||||
to=old)
|
||||
result.append(item)
|
||||
|
||||
for alias_name, item in list_attr2item.items():
|
||||
if sorted(item['cur'] or []) != sorted(item['to'] or []):
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
def baseline_cis(self, ci_ids, before_date, fl=None):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:param fl:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, fields=fl)
|
||||
if not ci_list:
|
||||
return []
|
||||
|
||||
id2ci = {ci['_id']: ci for ci in ci_list}
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
if change['is_list']:
|
||||
old, new, value_type, operate_type, ci_id, attr_name = (
|
||||
change['old'], change['new'], change['value_type'], change['operate_type'],
|
||||
change['ci_id'], change['attr_name'])
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD and new in (id2ci[ci_id][attr_name] or []):
|
||||
id2ci[ci_id][attr_name].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in id2ci[ci_id][attr_name]:
|
||||
id2ci[ci_id][attr_name].append(old)
|
||||
else:
|
||||
id2ci[change['ci_id']][change['attr_name']] = change['old']
|
||||
|
||||
return list(id2ci.values())
|
||||
|
||||
def rollback(self, ci_id, before_date):
|
||||
baseline_ci = self.baseline([ci_id], before_date)
|
||||
|
||||
payload = dict()
|
||||
for item in baseline_ci:
|
||||
payload[item.get('attr_name')] = item.get('to')
|
||||
|
||||
if payload:
|
||||
payload['ci_type'] = baseline_ci[0]['ci_type']
|
||||
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
|
||||
|
||||
self.update(ci_id, **payload)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
@@ -935,6 +1090,18 @@ class CIRelationManager(object):
|
||||
|
||||
return ci_ids, level2ids
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, ci_ids):
|
||||
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
|
||||
CI, CI.id == CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
|
||||
result = {}
|
||||
for ci in cis:
|
||||
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.commit()
|
||||
@@ -985,7 +1152,7 @@ class CIRelationManager(object):
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||
|
||||
if current_app.config.get('USE_ACL') and valid:
|
||||
if current_app.config.get('USE_ACL') and valid and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -1019,7 +1186,7 @@ class CIRelationManager(object):
|
||||
def delete(cr_id):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
@@ -1053,6 +1220,21 @@ class CIRelationManager(object):
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def delete_3(cls, first_ci_id, second_ci_id):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
if cr is not None:
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
cls.delete(cr.id)
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
|
@@ -5,7 +5,6 @@ import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
from werkzeug.exceptions import BadRequest
|
||||
@@ -28,9 +27,11 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
@@ -48,6 +49,7 @@ from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
@@ -92,6 +94,9 @@ class CITypeManager(object):
|
||||
for type_dict in ci_types:
|
||||
attr = AttributeCache.get(type_dict["unique_id"])
|
||||
type_dict["unique_key"] = attr and attr.name
|
||||
if type_dict.get('show_id'):
|
||||
attr = AttributeCache.get(type_dict["show_id"])
|
||||
type_dict["show_name"] = attr and attr.name
|
||||
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
|
||||
if resources is None or type_dict['name'] in resources:
|
||||
res.append(type_dict)
|
||||
@@ -127,7 +132,9 @@ class CITypeManager(object):
|
||||
def add(cls, **kwargs):
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
app_cli = CMDBApp()
|
||||
validate_permission(app_cli.op.Model_Configuration, app_cli.resource_type_name,
|
||||
app_cli.op.create_CIType, app_cli.app_name)
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
@@ -192,7 +199,7 @@ class CITypeManager(object):
|
||||
CITypeAttributeManager.update(type_id, [attr])
|
||||
|
||||
ci_type2 = ci_type.to_dict()
|
||||
new = ci_type.update(**kwargs)
|
||||
new = ci_type.update(**kwargs, filter_none=False)
|
||||
|
||||
CITypeCache.clean(type_id)
|
||||
|
||||
@@ -250,6 +257,16 @@ class CITypeManager(object):
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
try:
|
||||
from api.models.cmdb import CITypeReconciliation
|
||||
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
@@ -411,9 +428,6 @@ class CITypeGroupManager(object):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -680,6 +694,9 @@ class CITypeAttributeManager(object):
|
||||
|
||||
CITypeAttributeCache.clean(type_id, attr_id)
|
||||
|
||||
if ci_type.show_id == attr_id:
|
||||
ci_type.update(show_id=None, filter_none=False)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
|
||||
change=attr and attr.to_dict())
|
||||
|
||||
@@ -812,6 +829,70 @@ class CITypeRelationManager(object):
|
||||
|
||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||
|
||||
@staticmethod
|
||||
def get_relations_by_type_id(type_id):
|
||||
nodes, edges = [], []
|
||||
node_ids, edge_tuples = set(), set()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return nodes, edges
|
||||
|
||||
nodes.append(ci_type.to_dict())
|
||||
node_ids.add(ci_type.id)
|
||||
|
||||
def _find(_id, lv):
|
||||
lv += 1
|
||||
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
|
||||
if i.child_id not in node_ids:
|
||||
node_ids.add(i.child_id)
|
||||
node = i.child.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_find(i.child_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_find(i.child_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
def _reverse_find(_id, lv):
|
||||
lv -= 1
|
||||
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
|
||||
if i.parent_id not in node_ids:
|
||||
node_ids.add(i.parent_id)
|
||||
node = i.parent.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_reverse_find(i.parent_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_reverse_find(i.parent_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
level = 0
|
||||
_reverse_find(ci_type.id, level)
|
||||
level = 0
|
||||
_find(ci_type.id, level)
|
||||
|
||||
return nodes, edges
|
||||
|
||||
@staticmethod
|
||||
def _get(parent_id, child_id):
|
||||
return CITypeRelation.get_by(parent_id=parent_id,
|
||||
@@ -1227,7 +1308,10 @@ class CITypeTemplateManager(object):
|
||||
def _import_ci_types(self, ci_types, attr_id_map):
|
||||
for i in ci_types:
|
||||
i.pop("unique_key", None)
|
||||
i.pop("show_name", None)
|
||||
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
|
||||
if i.get('show_id'):
|
||||
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
|
||||
i['uid'] = current_user.uid
|
||||
|
||||
return self.__import(CIType, ci_types)
|
||||
|
@@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 删除关系
|
||||
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||
DELETE_RECONCILIATION = "16" # 删除关系
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -70,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
@@ -98,6 +102,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
class ExecuteStatusEnum(BaseEnum):
|
||||
COMPLETED = '0'
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
@@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
ci_id=None, attr_id=None, ci_ids=None, more=False):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
@@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if ci_ids and isinstance(ci_ids, list):
|
||||
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
@@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
if more:
|
||||
attr_hist['is_list'] = attr_hist['attr'].is_list
|
||||
attr_hist['is_computed'] = attr_hist['attr'].is_computed
|
||||
attr_hist['is_password'] = attr_hist['attr'].is_password
|
||||
attr_hist['default'] = attr_hist['attr'].default
|
||||
attr_hist['value_type'] = attr_hist['attr'].value_type
|
||||
attr_hist.pop("attr")
|
||||
|
||||
if record_id not in res:
|
||||
@@ -161,6 +170,7 @@ class AttributeHistoryManger(object):
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
value_type=attr.value_type,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
@@ -271,7 +281,7 @@ class CITypeHistoryManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
@@ -284,6 +294,7 @@ class CITypeHistoryManager(object):
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
rc_id=rc_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
|
@@ -25,6 +25,8 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
@@ -43,22 +45,46 @@ class PreferenceManager(object):
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
type2group = {}
|
||||
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
|
||||
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
|
||||
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
|
||||
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
group_types = []
|
||||
other_types = []
|
||||
group2idx = {}
|
||||
type_ids = set()
|
||||
for ci_type in types:
|
||||
type_id = ci_type.type_id
|
||||
type_ids.add(type_id)
|
||||
type_dict = CITypeCache.get(type_id).to_dict()
|
||||
if type_id not in type2group:
|
||||
other_types.append(type_dict)
|
||||
else:
|
||||
group = type2group[type_id]
|
||||
if group['id'] not in group2idx:
|
||||
group_types.append(type2group[type_id])
|
||||
group2idx[group['id']] = len(group_types) - 1
|
||||
group_types[group2idx[group['id']]].setdefault('ci_types', []).append(type_dict)
|
||||
if other_types:
|
||||
group_types.append(dict(ci_types=other_types))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
tree_types = [CITypeCache.get(_type.type_id).to_dict() for _type in tree_types]
|
||||
for _type in tree_types:
|
||||
type_ids.add(_type['id'])
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
|
||||
|
||||
@staticmethod
|
||||
def get_types2(instance=False, tree=False):
|
||||
@@ -297,6 +323,10 @@ class PreferenceManager(object):
|
||||
|
||||
for type_id in id2type:
|
||||
id2type[type_id] = CITypeCache.get(type_id).to_dict()
|
||||
id2type[type_id]['unique_name'] = AttributeCache.get(id2type[type_id]['unique_id']).name
|
||||
if id2type[type_id]['show_id']:
|
||||
show_attr = AttributeCache.get(id2type[type_id]['show_id'])
|
||||
id2type[type_id]['show_name'] = show_attr and show_attr.name
|
||||
|
||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||
|
||||
|
@@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
|
||||
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
|
||||
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
@@ -138,3 +140,12 @@ class ErrFormat(CommonErrFormat):
|
||||
|
||||
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
||||
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
||||
|
||||
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
||||
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
||||
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
|
||||
|
||||
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
|
||||
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
|
||||
# 因为该分组下定义了拓扑视图,不能删除
|
||||
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||
|
@@ -17,10 +17,12 @@ def search(query=None,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None,
|
||||
use_id_filter=False):
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
|
||||
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
|
||||
|
||||
return s
|
||||
|
@@ -47,7 +47,8 @@ class Search(object):
|
||||
excludes=None,
|
||||
parent_node_perm_passed=False,
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
use_ci_filter=True,
|
||||
only_ids=False):
|
||||
self.orig_query = query
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
@@ -64,10 +65,12 @@ class Search(object):
|
||||
self.parent_node_perm_passed = parent_node_perm_passed
|
||||
self.use_id_filter = use_id_filter
|
||||
self.use_ci_filter = use_ci_filter
|
||||
self.only_ids = only_ids
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
@@ -590,6 +593,8 @@ class Search(object):
|
||||
def search(self):
|
||||
numfound, ci_ids = self._query_build_raw()
|
||||
ci_ids = list(map(str, ci_ids))
|
||||
if self.only_ids:
|
||||
return ci_ids
|
||||
|
||||
_fl = self._fl_build()
|
||||
|
||||
|
@@ -8,6 +8,8 @@ from flask import current_app
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
@@ -18,6 +20,8 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
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.es.search import Search as SearchFromES
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import CI
|
||||
@@ -65,7 +69,7 @@ class Search(object):
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
self.type2filter_perms = None
|
||||
self.type2filter_perms = {}
|
||||
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
@@ -75,7 +79,7 @@ class Search(object):
|
||||
key = []
|
||||
_tmp = []
|
||||
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]):
|
||||
if len(self.descendant_ids or []) >= 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 = {}
|
||||
@@ -147,9 +151,9 @@ class Search(object):
|
||||
|
||||
return True
|
||||
|
||||
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()
|
||||
def search(self, only_ids=False):
|
||||
use_ci_filter = len(self.descendant_ids or []) == self.level[0] - 1
|
||||
parent_node_perm_passed = not self.is_app_admin and self._has_read_perm_from_parent_nodes()
|
||||
|
||||
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]
|
||||
@@ -193,7 +197,8 @@ class Search(object):
|
||||
sort=self.sort,
|
||||
ci_ids=merge_ids,
|
||||
parent_node_perm_passed=parent_node_perm_passed,
|
||||
use_ci_filter=use_ci_filter).search()
|
||||
use_ci_filter=use_ci_filter,
|
||||
only_ids=only_ids).search()
|
||||
|
||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||
ci_filters = ci_filters or []
|
||||
@@ -236,7 +241,7 @@ class Search(object):
|
||||
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
|
||||
_tmp = []
|
||||
_tmp, tmp_res = [], []
|
||||
level2ids = {}
|
||||
for lv in range(1, self.level + 1):
|
||||
level2ids[lv] = []
|
||||
@@ -303,25 +308,26 @@ class Search(object):
|
||||
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:
|
||||
__tmp = [[i for i in x if i[1] in type_ids and
|
||||
(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)]
|
||||
tmp_res = [[i for i in x if i[1] in type_ids and
|
||||
(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)]
|
||||
else:
|
||||
__tmp = [[i for i in x if (not id_filter_limit or (
|
||||
tmp_res = [[i 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)]
|
||||
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]
|
||||
tmp_res = [[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_res]
|
||||
else:
|
||||
__tmp = []
|
||||
tmp_res = []
|
||||
|
||||
if __tmp:
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
if tmp_res:
|
||||
_tmp[idx] = [j for i in tmp_res for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
level2ids[lv].append([])
|
||||
@@ -332,3 +338,84 @@ class Search(object):
|
||||
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
||||
|
||||
return result
|
||||
|
||||
def search_full(self, type_ids):
|
||||
def _get_id2name(_type_id):
|
||||
ci_type = CITypeCache.get(_type_id)
|
||||
|
||||
attr = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
unique_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
|
||||
attr = AttributeCache.get(ci_type.show_id)
|
||||
if attr:
|
||||
value_table = TableMap(attr=attr).table
|
||||
serializer = ValueTypeMap.serialize[attr.value_type]
|
||||
show_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
|
||||
else:
|
||||
show_value = unique_value
|
||||
|
||||
return show_value, unique_value
|
||||
|
||||
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
|
||||
|
||||
level_ids = [str(i) for i in ids]
|
||||
result = []
|
||||
id2children = {}
|
||||
id2name = _get_id2name(type_ids[0])
|
||||
for i in level_ids:
|
||||
item = dict(id=int(i),
|
||||
type_id=type_ids[0],
|
||||
isLeaf=False,
|
||||
title=id2name[0].get(int(i)),
|
||||
uniqueValue=id2name[1].get(int(i)),
|
||||
children=[])
|
||||
result.append(item)
|
||||
id2children[str(i)] = item['children']
|
||||
|
||||
for lv in range(1, self.level):
|
||||
|
||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
|
||||
if self.has_m2m and lv != 1:
|
||||
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
res = [[i 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)]
|
||||
_level_ids = []
|
||||
type_id = type_ids[lv]
|
||||
id2name = _get_id2name(type_id)
|
||||
for idx, node_path in enumerate(level_ids):
|
||||
for child_id, _ in (res[idx] or []):
|
||||
item = dict(id=int(child_id),
|
||||
type_id=type_id,
|
||||
isLeaf=True if lv == self.level - 1 else False,
|
||||
title=id2name[0].get(int(child_id)),
|
||||
uniqueValue=id2name[1].get(int(child_id)),
|
||||
children=[])
|
||||
id2children[node_path].append(item)
|
||||
|
||||
_node_path = "{},{}".format(node_path, child_id)
|
||||
_level_ids.append(_node_path)
|
||||
id2children[_node_path] = item['children']
|
||||
|
||||
level_ids = _level_ids
|
||||
|
||||
return result
|
||||
|
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import TopologyViewGroup
|
||||
|
||||
|
||||
class TopologyViewManager(object):
|
||||
group_cls = TopologyViewGroup
|
||||
cls = TopologyView
|
||||
|
||||
@classmethod
|
||||
def get_name_by_id(cls, _id):
|
||||
res = cls.cls.get_by_id(_id)
|
||||
return res and res.name
|
||||
|
||||
def get_view_by_id(self, _id):
|
||||
res = self.cls.get_by_id(_id)
|
||||
|
||||
return res and res.to_dict() or {}
|
||||
|
||||
@classmethod
|
||||
def add_group(cls, name, order):
|
||||
if order is None:
|
||||
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
|
||||
400, ErrFormat.topology_group_exists.format(name))
|
||||
|
||||
return cls.group_cls.create(name=name, order=order)
|
||||
|
||||
def update_group(self, group_id, name, view_ids):
|
||||
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
|
||||
if name is not None and name != existed.name:
|
||||
existed.update(name=name)
|
||||
|
||||
for idx, view_id in enumerate(view_ids):
|
||||
view = self.cls.get_by_id(view_id)
|
||||
if view is not None:
|
||||
view.update(group_id=group_id, order=idx)
|
||||
|
||||
return existed.to_dict()
|
||||
|
||||
@classmethod
|
||||
def delete_group(cls, _id):
|
||||
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
if cls.cls.get_by(group_id=_id, first=True):
|
||||
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@classmethod
|
||||
def group_order(cls, group_ids):
|
||||
for idx, group_id in enumerate(group_ids):
|
||||
group = cls.group_cls.get_by_id(group_id)
|
||||
group.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, group_id, option, order=None, **kwargs):
|
||||
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
|
||||
if order is None:
|
||||
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
|
||||
cls.cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL'):
|
||||
try:
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
ACLManager().grant_resource_to_role(name,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
existed_name = existed.name
|
||||
|
||||
inst = existed.update(filter_none=False, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
|
||||
try:
|
||||
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
existed.soft_delete()
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
@classmethod
|
||||
def group_inner_order(cls, _ids):
|
||||
for idx, _id in enumerate(_ids):
|
||||
topology = cls.cls.get_by_id(_id)
|
||||
topology.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)])
|
||||
|
||||
groups = cls.group_cls.get_by(to_dict=True)
|
||||
groups = sorted(groups, key=lambda x: x['order'])
|
||||
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
|
||||
|
||||
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
|
||||
other_group = dict(views=[])
|
||||
for view in topo_views:
|
||||
if resources is not None and view['name'] not in resources:
|
||||
continue
|
||||
|
||||
if view['group_id']:
|
||||
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
|
||||
else:
|
||||
other_group['views'].append(view)
|
||||
|
||||
if other_group['views']:
|
||||
groups.append(other_group)
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def relation_from_ci_type(type_id):
|
||||
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
|
||||
|
||||
return dict(nodes=nodes, edges=edges)
|
||||
|
||||
def topology_view(self, view_id=None, preview=None):
|
||||
if view_id is not None:
|
||||
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
central_node_type, central_node_instances, path = (view.central_node_type,
|
||||
view.central_node_instances, view.path)
|
||||
else:
|
||||
central_node_type = preview.get('central_node_type')
|
||||
central_node_instances = preview.get('central_node_instances')
|
||||
path = preview.get('path')
|
||||
|
||||
nodes, links = [], []
|
||||
_type = CITypeCache.get(central_node_type)
|
||||
if not _type:
|
||||
return dict(nodes=nodes, links=links)
|
||||
root_ids = []
|
||||
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
|
||||
|
||||
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
|
||||
central_node_instances)
|
||||
s = search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.info(e)
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
root_ids.append(i['_id'])
|
||||
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
|
||||
if not root_ids:
|
||||
return dict(nodes=nodes, links=links)
|
||||
|
||||
prefix = REDIS_PREFIX_CI_RELATION
|
||||
key = list(map(str, root_ids))
|
||||
id2node = {}
|
||||
type2meta = {}
|
||||
for level in sorted([i for i in path.keys() if int(i) > 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
new_key = []
|
||||
for idx, from_id in enumerate(key):
|
||||
for to_id, type_id in res[idx]:
|
||||
if type_id in type_ids:
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[to_id] = {'id': to_id, 'type_id': type_id}
|
||||
new_key.append(to_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
key = new_key
|
||||
|
||||
ci_ids = list(map(int, root_ids))
|
||||
for level in sorted([i for i in path.keys() if int(i) < 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
res = CIRelationManager.get_parent_ids(ci_ids)
|
||||
_ci_ids = []
|
||||
for to_id in res:
|
||||
for from_id, type_id in res[to_id]:
|
||||
if type_id in type_ids:
|
||||
from_id, to_id = str(from_id), str(to_id)
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
|
||||
_ci_ids.append(from_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
ci_ids = _ci_ids
|
||||
|
||||
fl = set()
|
||||
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
|
||||
type2show = {}
|
||||
for type_id in type_ids:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type:
|
||||
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||
if attr:
|
||||
fl.add(attr.name)
|
||||
type2show[type_id] = attr.name
|
||||
|
||||
if id2node:
|
||||
s = search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
|
||||
use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError:
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
|
||||
nodes.extend(id2node.values())
|
||||
|
||||
return dict(nodes=nodes, links=links, type2meta=type2meta)
|
@@ -274,26 +274,36 @@ class AttributeValueManager(object):
|
||||
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
|
||||
i.value or i.value == 0 else i.value) for i in existed_attrs]
|
||||
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
index = 0
|
||||
while index < min_len:
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
|
||||
# Delete first and then add to ensure id sorting
|
||||
for idx in range(index, len(existed_attrs)):
|
||||
existed_attr = existed_attrs[idx]
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
|
||||
for idx in range(index, len(value)):
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
|
||||
else:
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
|
||||
existed_value or existed_value == 0 else existed_value)
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
if existed_value != value:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
else:
|
||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
|
||||
def validate_app(app_id):
|
||||
app = AppCache.get(app_id)
|
||||
return app.id if app else None
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self, app_name='acl', uid=None):
|
||||
self.log = current_app.logger
|
||||
@@ -133,7 +138,8 @@ class ACLManager(object):
|
||||
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||
return res
|
||||
|
||||
def grant_resource(self, rid, resource_id, perms):
|
||||
@staticmethod
|
||||
def grant_resource(rid, resource_id, perms):
|
||||
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||
|
||||
@staticmethod
|
||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
||||
rt = AppCRUD.add(**payload)
|
||||
|
||||
return rt.to_dict()
|
||||
|
||||
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
|
||||
app_id = validate_app(self.app_name)
|
||||
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)
|
||||
|
37
cmdb-api/api/lib/common_setting/decorator.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
|
||||
|
||||
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
|
||||
def decorator_perms_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_required(*args, **kwargs):
|
||||
acl = ACLManager(app_name)
|
||||
has_perms = False
|
||||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
if not has_perms:
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_required
|
||||
|
||||
return decorator_perms_role_required
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
||||
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||
|
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,64 @@
|
||||
class OperationPermission(object):
|
||||
|
||||
def __init__(self, resource_perms):
|
||||
for _r in resource_perms:
|
||||
setattr(self, _r['page'], _r['page'])
|
||||
for _p in _r['perms']:
|
||||
setattr(self, _p, _p)
|
||||
|
||||
|
||||
class BaseApp(object):
|
||||
resource_type_name = 'OperationPermission'
|
||||
all_resource_perms = []
|
||||
|
||||
def __init__(self):
|
||||
self.admin_name = None
|
||||
self.roles = []
|
||||
self.app_name = 'acl'
|
||||
self.require_create_resource_type = self.resource_type_name
|
||||
self.extra_create_resource_type_list = []
|
||||
|
||||
self.op = None
|
||||
|
||||
@staticmethod
|
||||
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
|
||||
return dict(
|
||||
role_name=role_name,
|
||||
role_type=role_type,
|
||||
acl_rid=acl_rid,
|
||||
description=description,
|
||||
resource_perms=resource_perms,
|
||||
)
|
||||
|
||||
|
||||
class CMDBApp(BaseApp):
|
||||
all_resource_perms = [
|
||||
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
|
||||
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
|
||||
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
|
||||
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
|
||||
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
|
||||
{"page": "Model_Configuration", "page_cn": "模型配置",
|
||||
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
|
||||
"delete_CIType_group", "download_CIType"]},
|
||||
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
|
||||
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
|
||||
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
|
||||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read"]},
|
||||
{"page": "TopologyView", "page_cn": "拓扑视图",
|
||||
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||
"create_topology_view"],
|
||||
},
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.admin_name = 'cmdb_admin'
|
||||
self.app_name = 'cmdb'
|
||||
|
||||
self.op = OperationPermission(self.all_resource_perms)
|
@@ -1,5 +1,10 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
from sqlalchemy import inspect, text
|
||||
from sqlalchemy.dialects.mysql import ENUM
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
|
||||
def get_cur_time_str(split_flag='-'):
|
||||
@@ -23,3 +28,115 @@ class BaseEnum(object):
|
||||
if not attr.startswith("_") and not callable(getattr(cls, attr))
|
||||
}
|
||||
return cls._ALL_
|
||||
|
||||
|
||||
class CheckNewColumn(object):
|
||||
|
||||
def __init__(self):
|
||||
self.engine = db.get_engine()
|
||||
self.inspector = inspect(self.engine)
|
||||
self.table_names = self.inspector.get_table_names()
|
||||
|
||||
@staticmethod
|
||||
def get_model_by_table_name(_table_name):
|
||||
registry = getattr(db.Model, 'registry', None)
|
||||
class_registry = getattr(registry, '_class_registry', None)
|
||||
for _model in class_registry.values():
|
||||
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
|
||||
return _model
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
for table_name in self.table_names:
|
||||
self.check_by_table(table_name)
|
||||
|
||||
def check_by_table(self, table_name):
|
||||
existed_columns = self.inspector.get_columns(table_name)
|
||||
enum_columns = []
|
||||
existed_column_name_list = []
|
||||
for c in existed_columns:
|
||||
if isinstance(c['type'], ENUM):
|
||||
enum_columns.append(c['name'])
|
||||
existed_column_name_list.append(c['name'])
|
||||
|
||||
model = self.get_model_by_table_name(table_name)
|
||||
if model is None:
|
||||
return
|
||||
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
|
||||
for column in model_columns:
|
||||
if column.name not in existed_column_name_list:
|
||||
add_res = self.add_new_column(table_name, column)
|
||||
if not add_res:
|
||||
continue
|
||||
|
||||
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||
|
||||
if column.name in enum_columns:
|
||||
enum_columns.remove(column.name)
|
||||
|
||||
self.add_new_index(table_name, column)
|
||||
|
||||
if len(enum_columns) > 0:
|
||||
self.check_enum_column(enum_columns, existed_columns, model_columns, table_name)
|
||||
|
||||
def add_new_column(self, target_table_name, new_column):
|
||||
try:
|
||||
column_type = new_column.type.compile(self.engine.dialect)
|
||||
default_value = new_column.default.arg if new_column.default else None
|
||||
|
||||
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
|
||||
if new_column.comment:
|
||||
sql += f" comment '{new_column.comment}'"
|
||||
|
||||
if column_type == 'JSON':
|
||||
pass
|
||||
elif default_value:
|
||||
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||
if default_value is None or len(default_value) == 0:
|
||||
pass
|
||||
else:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
sql = text(sql)
|
||||
db.session.execute(sql)
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_column [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def add_new_index(target_table_name, new_column):
|
||||
try:
|
||||
if new_column.index:
|
||||
index_name = f"{target_table_name}_{new_column.name}"
|
||||
sql = "CREATE INDEX " + f"{index_name}" + " ON " + target_table_name + " (" + new_column.name + ")"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(f"add new index [{index_name}] in table [{target_table_name}] success.")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
err = f"add_new_index [{new_column.name}] to table [{target_table_name}] err: {e}"
|
||||
current_app.logger.error(err)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_enum_column(enum_columns, existed_columns, model_columns, table_name):
|
||||
for column_name in enum_columns:
|
||||
try:
|
||||
enum_column = list(filter(lambda x: x['name'] == column_name, existed_columns))[0]
|
||||
old_enum_value = enum_column.get('type', {}).enums
|
||||
target_column = list(filter(lambda x: x.name == column_name, model_columns))[0]
|
||||
new_enum_value = target_column.type.enums
|
||||
|
||||
if set(old_enum_value) == set(new_enum_value):
|
||||
continue
|
||||
|
||||
enum_values_str = ','.join(["'{}'".format(value) for value in new_enum_value])
|
||||
sql = f"ALTER TABLE {table_name} MODIFY COLUMN" + f"`{column_name}`" + f" enum({enum_values_str})"
|
||||
db.session.execute(sql)
|
||||
current_app.logger.info(
|
||||
f"modify column [{column_name}] ENUM: {new_enum_value} in table [{table_name}] success.")
|
||||
except Exception as e:
|
||||
current_app.logger.error(
|
||||
f"modify column ENUM [{column_name}] in table [{table_name}] err: {e}")
|
||||
|
@@ -153,11 +153,11 @@ class ACLManager(object):
|
||||
if resource:
|
||||
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, rid=None):
|
||||
if is_app_admin(self.app_id):
|
||||
return True
|
||||
|
||||
role = self._get_role(current_user.username)
|
||||
role = self._get_role(current_user.username) if rid is None else RoleCache.get(rid)
|
||||
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import msgpack
|
||||
import redis_lock
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.models.acl import App
|
||||
@@ -157,9 +158,10 @@ class RoleRelationCache(object):
|
||||
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, rid, app_id):
|
||||
def get_parent_ids(cls, rid, app_id, force=False):
|
||||
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
|
||||
if not parent_ids:
|
||||
if not parent_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
|
||||
@@ -167,9 +169,10 @@ class RoleRelationCache(object):
|
||||
return parent_ids
|
||||
|
||||
@classmethod
|
||||
def get_child_ids(cls, rid, app_id):
|
||||
def get_child_ids(cls, rid, app_id, force=False):
|
||||
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
|
||||
if not child_ids:
|
||||
if not child_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
|
||||
@@ -177,14 +180,16 @@ class RoleRelationCache(object):
|
||||
return child_ids
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls, rid, app_id):
|
||||
def get_resources(cls, rid, app_id, force=False):
|
||||
"""
|
||||
:param rid:
|
||||
:param app_id:
|
||||
:param force:
|
||||
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
|
||||
"""
|
||||
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
|
||||
if not resources:
|
||||
if not resources or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
resources = RoleCRUD.get_resources(rid, app_id)
|
||||
if resources['id2perms'] or resources['group2perms']:
|
||||
@@ -193,9 +198,10 @@ class RoleRelationCache(object):
|
||||
return resources or {}
|
||||
|
||||
@classmethod
|
||||
def get_resources2(cls, rid, app_id):
|
||||
def get_resources2(cls, rid, app_id, force=False):
|
||||
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
if not r_g:
|
||||
if not r_g or force:
|
||||
db.session.commit()
|
||||
res = cls.get_resources(rid, app_id)
|
||||
id2perms = res['id2perms']
|
||||
group2perms = res['group2perms']
|
||||
@@ -224,22 +230,28 @@ class RoleRelationCache(object):
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild(cls, rid, app_id):
|
||||
cls.clean(rid, app_id)
|
||||
|
||||
cls.get_parent_ids(rid, app_id)
|
||||
cls.get_child_ids(rid, app_id)
|
||||
resources = cls.get_resources(rid, app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, app_id)
|
||||
if app_id is None:
|
||||
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, app_id)
|
||||
cls.get_resources2(rid, app_id)
|
||||
app_ids = [app_id]
|
||||
|
||||
for _app_id in app_ids:
|
||||
cls.clean(rid, _app_id)
|
||||
|
||||
cls.get_parent_ids(rid, _app_id, force=True)
|
||||
cls.get_child_ids(rid, _app_id, force=True)
|
||||
resources = cls.get_resources(rid, _app_id, force=True)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, _app_id)
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, _app_id)
|
||||
cls.get_resources2(rid, _app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild2(cls, rid, app_id):
|
||||
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
cls.get_resources2(rid, app_id)
|
||||
cls.get_resources2(rid, app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, rid, app_id):
|
||||
|
@@ -274,12 +274,14 @@ class PermissionCRUD(object):
|
||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||
for _perm in perm2resource:
|
||||
perm = PermissionCache.get(_perm, resource_type_id)
|
||||
existeds = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in existeds:
|
||||
if perm is None:
|
||||
continue
|
||||
exists = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in exists:
|
||||
existed.deleted = True
|
||||
existed.deleted_at = datetime.datetime.now()
|
||||
db.session.add(existed)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
||||
existed_ids = [i.id for i in existed]
|
||||
current_ids = []
|
||||
|
||||
rebuild_rids = set()
|
||||
for i in existed:
|
||||
if i.name not in perms:
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
|
||||
rp.soft_delete(commit=False)
|
||||
rebuild_rids.add((rp.app_id, rp.rid))
|
||||
else:
|
||||
current_ids.append(i.id)
|
||||
db.session.commit()
|
||||
for _app_id, _rid in rebuild_rids:
|
||||
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
|
||||
|
||||
for i in perms:
|
||||
if i not in existed_names:
|
||||
@@ -309,9 +315,12 @@ class ResourceCRUD(object):
|
||||
return resource
|
||||
|
||||
@staticmethod
|
||||
def delete(_id, rebuild=True):
|
||||
def delete(_id, rebuild=True, app_id=None):
|
||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
if app_id is not None and resource.app_id != app_id:
|
||||
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
origin = resource.to_dict()
|
||||
resource.soft_delete()
|
||||
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
||||
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
parent = RoleCache.get(i.parent_id)
|
||||
if parent:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
db.session.commit()
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
|
||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||
AuditScope.role_relation, role.id, {}, {},
|
||||
@@ -372,16 +379,16 @@ class RoleCRUD(object):
|
||||
resource_type_id = resource_type and resource_type.id
|
||||
|
||||
result = dict(resources=dict(), groups=dict())
|
||||
s = time.time()
|
||||
# s = time.time()
|
||||
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
|
||||
current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
# current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
for parent_id in parent_ids:
|
||||
|
||||
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
|
||||
current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
_merge(result['resources'], _resources)
|
||||
current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
current_app.logger.info(len(_groups))
|
||||
# current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info(len(_groups))
|
||||
if not group_flat:
|
||||
_merge(result['groups'], _groups)
|
||||
else:
|
||||
@@ -392,7 +399,7 @@ class RoleCRUD(object):
|
||||
item.setdefault('permissions', [])
|
||||
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
|
||||
result['resources'][item['id']] = item
|
||||
current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
|
||||
result['resources'] = list(result['resources'].values())
|
||||
result['groups'] = list(result['groups'].values())
|
||||
|
@@ -1,19 +1,15 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||
from colorama import Back
|
||||
from colorama import Fore
|
||||
from colorama import Style
|
||||
from colorama import init as colorama_init
|
||||
from colorama import Back, Fore, Style, init as colorama_init
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.primitives import hashes, padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from flask import current_app
|
||||
@@ -27,11 +23,17 @@ backend_encrypt_key_name = "encrypt_key"
|
||||
backend_root_key_salt_name = "root_key_salt"
|
||||
backend_encrypt_key_salt_name = "encrypt_key_salt"
|
||||
backend_seal_key = "seal_status"
|
||||
|
||||
success = "success"
|
||||
seal_status = True
|
||||
|
||||
secrets_encrypt_key = ""
|
||||
secrets_root_key = ""
|
||||
|
||||
|
||||
def string_to_bytes(value):
|
||||
if not value:
|
||||
return ""
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
if sys.version_info.major == 2:
|
||||
@@ -44,6 +46,8 @@ def string_to_bytes(value):
|
||||
class Backend:
|
||||
def __init__(self, backend=None):
|
||||
self.backend = backend
|
||||
# cache is a redis object
|
||||
self.cache = backend.cache
|
||||
|
||||
def get(self, key):
|
||||
return self.backend.get(key)
|
||||
@@ -54,23 +58,33 @@ class Backend:
|
||||
def update(self, key, value):
|
||||
return self.backend.update(key, value)
|
||||
|
||||
def get_shares(self, key):
|
||||
return self.backend.get_shares(key)
|
||||
|
||||
def set_shares(self, key, value):
|
||||
return self.backend.set_shares(key, value)
|
||||
|
||||
|
||||
class KeyManage:
|
||||
|
||||
def __init__(self, trigger=None, backend=None):
|
||||
self.trigger = trigger
|
||||
self.backend = backend
|
||||
self.share_key = "cmdb::secret::secrets_share"
|
||||
if backend:
|
||||
self.backend = Backend(backend)
|
||||
|
||||
def init_app(self, app, backend=None):
|
||||
if (sys.argv[0].endswith("gunicorn") or
|
||||
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
|
||||
|
||||
self.backend = backend
|
||||
threading.Thread(target=self.watch_root_key, args=(app,), daemon=True).start()
|
||||
|
||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||
if not self.trigger:
|
||||
return
|
||||
|
||||
self.backend = backend
|
||||
resp = self.auto_unseal()
|
||||
self.print_response(resp)
|
||||
|
||||
@@ -124,6 +138,8 @@ class KeyManage:
|
||||
return new_shares
|
||||
|
||||
def is_valid_root_key(self, root_key):
|
||||
if not root_key:
|
||||
return False
|
||||
root_key_hash, ok = self.hash_root_key(root_key)
|
||||
if not ok:
|
||||
return root_key_hash, ok
|
||||
@@ -135,35 +151,42 @@ class KeyManage:
|
||||
else:
|
||||
return "", True
|
||||
|
||||
def auth_root_secret(self, root_key):
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
def auth_root_secret(self, root_key, app):
|
||||
with app.app_context():
|
||||
msg, ok = self.is_valid_root_key(root_key)
|
||||
if not ok:
|
||||
return {
|
||||
"message": msg,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
|
||||
if not encrypt_key_aes:
|
||||
return {
|
||||
"message": "encrypt key is empty",
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
secret_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
|
||||
if ok:
|
||||
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_shares"] = []
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secrets_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
msg, ok = self.backend.update(backend_seal_key, "open")
|
||||
if ok:
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = secret_encrypt_key
|
||||
secrets_root_key = root_key
|
||||
self.backend.cache.set(self.share_key, json.dumps([]))
|
||||
return {"message": success, "status": success}
|
||||
return {"message": msg, "status": "failed"}
|
||||
else:
|
||||
return {
|
||||
"message": secret_encrypt_key,
|
||||
"status": "failed"
|
||||
}
|
||||
|
||||
def parse_shares(self, shares, app):
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret), app)
|
||||
|
||||
def unseal(self, key):
|
||||
if not self.is_seal():
|
||||
@@ -175,14 +198,12 @@ class KeyManage:
|
||||
try:
|
||||
t = [i for i in b64decode(key)]
|
||||
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
|
||||
shares = current_app.config.get("secrets_shares", [])
|
||||
shares = self.backend.get_shares(self.share_key)
|
||||
if v not in shares:
|
||||
shares.append(v)
|
||||
current_app.config["secrets_shares"] = shares
|
||||
|
||||
self.set_shares(shares)
|
||||
if len(shares) >= global_key_threshold:
|
||||
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
|
||||
return self.auth_root_secret(b64encode(recovered_secret))
|
||||
return self.parse_shares(shares, current_app)
|
||||
else:
|
||||
return {
|
||||
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
|
||||
@@ -242,8 +263,11 @@ class KeyManage:
|
||||
msg, ok = self.backend.add(backend_seal_key, "open")
|
||||
if not ok:
|
||||
return {"message": msg, "status": "failed"}, False
|
||||
current_app.config["secrets_root_key"] = root_key
|
||||
current_app.config["secrets_encrypt_key"] = encrypt_key
|
||||
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = encrypt_key
|
||||
secrets_root_key = root_key
|
||||
|
||||
self.print_token(shares, root_token=root_key)
|
||||
|
||||
return {"message": "OK",
|
||||
@@ -266,7 +290,7 @@ class KeyManage:
|
||||
}
|
||||
# TODO
|
||||
elif len(self.trigger.strip()) == 24:
|
||||
res = self.auth_root_secret(self.trigger.encode())
|
||||
res = self.auth_root_secret(self.trigger.encode(), current_app)
|
||||
if res.get("status") == success:
|
||||
return {
|
||||
"message": success,
|
||||
@@ -298,22 +322,31 @@ class KeyManage:
|
||||
"message": msg,
|
||||
"status": "failed",
|
||||
}
|
||||
current_app.config["secrets_root_key"] = ''
|
||||
current_app.config["secrets_encrypt_key"] = ''
|
||||
self.clear()
|
||||
self.backend.cache.publish(self.share_key, "clear")
|
||||
|
||||
return {
|
||||
"message": success,
|
||||
"status": success
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
global secrets_encrypt_key, secrets_root_key
|
||||
secrets_encrypt_key = ''
|
||||
secrets_root_key = ''
|
||||
|
||||
def is_seal(self):
|
||||
"""
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state.
|
||||
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state..
|
||||
:return:
|
||||
"""
|
||||
secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
# secrets_root_key = current_app.config.get("secrets_root_key")
|
||||
if not secrets_root_key:
|
||||
return True
|
||||
msg, ok = self.is_valid_root_key(secrets_root_key)
|
||||
if not ok:
|
||||
return true
|
||||
return True
|
||||
status = self.backend.get(backend_seal_key)
|
||||
return status == "block"
|
||||
|
||||
@@ -349,22 +382,53 @@ class KeyManage:
|
||||
}
|
||||
print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL)
|
||||
|
||||
def set_shares(self, values):
|
||||
new_value = list()
|
||||
for v in values:
|
||||
new_value.append((v[0], b64encode(v[1]).decode("utf-8")))
|
||||
self.backend.cache.publish(self.share_key, json.dumps(new_value))
|
||||
self.backend.cache.set(self.share_key, json.dumps(new_value))
|
||||
|
||||
def watch_root_key(self, app):
|
||||
pubsub = self.backend.cache.pubsub()
|
||||
pubsub.subscribe(self.share_key)
|
||||
|
||||
new_value = set()
|
||||
for message in pubsub.listen():
|
||||
if message["type"] == "message":
|
||||
if message["data"] == b"clear":
|
||||
self.clear()
|
||||
continue
|
||||
try:
|
||||
value = json.loads(message["data"].decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.add((v[0], b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
if len(new_value) >= global_key_threshold:
|
||||
self.parse_shares(list(new_value), app)
|
||||
new_value = set()
|
||||
|
||||
|
||||
class InnerCrypt:
|
||||
def __init__(self):
|
||||
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "")
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8"))
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||
# self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
encrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_encrypt(self.encrypt_key, plaintext)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""
|
||||
decrypt method contain aes currently
|
||||
"""
|
||||
if not self.encrypt_key:
|
||||
return ValueError("secret is disabled, please seal firstly"), False
|
||||
return self.aes_decrypt(self.encrypt_key, ciphertext)
|
||||
|
||||
@classmethod
|
||||
@@ -381,6 +445,7 @@ class InnerCrypt:
|
||||
|
||||
return b64encode(iv + ciphertext).decode("utf-8"), True
|
||||
except Exception as e:
|
||||
|
||||
return str(e), False
|
||||
|
||||
@classmethod
|
||||
|
@@ -1,8 +1,13 @@
|
||||
import base64
|
||||
import json
|
||||
|
||||
from api.models.cmdb import InnerKV
|
||||
from api.extensions import rd
|
||||
|
||||
|
||||
class InnerKVManger(object):
|
||||
def __init__(self):
|
||||
self.cache = rd.r
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@@ -33,3 +38,26 @@ class InnerKVManger(object):
|
||||
return "success", True
|
||||
|
||||
return "update failed", True
|
||||
|
||||
@classmethod
|
||||
def get_shares(cls, key):
|
||||
new_value = list()
|
||||
v = rd.get_str(key)
|
||||
if not v:
|
||||
return new_value
|
||||
try:
|
||||
value = json.loads(v.decode("utf-8"))
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64decode(v[1])))
|
||||
except Exception as e:
|
||||
return []
|
||||
return new_value
|
||||
|
||||
@classmethod
|
||||
def set_shares(cls, key, value):
|
||||
new_value = list()
|
||||
for v in value:
|
||||
new_value.append((v[0], base64.b64encode(v[1]).decode("utf-8")))
|
||||
rd.set_str(key, json.dumps(new_value))
|
||||
|
||||
|
||||
|
@@ -117,6 +117,23 @@ class RedisHandler(object):
|
||||
except Exception as e:
|
||||
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
||||
|
||||
def set_str(self, key, value, expired=None):
|
||||
try:
|
||||
if expired:
|
||||
self.r.setex(key, expired, value)
|
||||
else:
|
||||
self.r.set(key, value)
|
||||
except Exception as e:
|
||||
current_app.logger.error("set redis error, {0}".format(str(e)))
|
||||
|
||||
def get_str(self, key):
|
||||
try:
|
||||
value = self.r.get(key)
|
||||
except Exception as e:
|
||||
current_app.logger.error("get redis error, {0}".format(str(e)))
|
||||
return
|
||||
return value
|
||||
|
||||
|
||||
class ESHandler(object):
|
||||
def __init__(self, flask_app=None):
|
||||
|
@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
|
||||
|
||||
params = webhook.get('parameters') or None
|
||||
if isinstance(params, dict):
|
||||
params = json.loads(Template(json.dumps(params)).render(payload))
|
||||
params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
|
||||
|
||||
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
||||
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
|
||||
auth = _wrap_auth(**webhook.get('authorization', {}))
|
||||
|
||||
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
||||
|
@@ -46,13 +46,17 @@ class CIType(Model):
|
||||
name = db.Column(db.String(32), nullable=False)
|
||||
alias = db.Column(db.String(32), nullable=False)
|
||||
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
show_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||
icon = db.Column(db.Text)
|
||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||
default_order_attr = db.Column(db.String(33))
|
||||
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id",
|
||||
primaryjoin="Attribute.id==CIType.unique_id", foreign_keys=[unique_id])
|
||||
show_key = db.relationship("Attribute", backref="c_ci_types.show_id",
|
||||
primaryjoin="Attribute.id==CIType.show_id", foreign_keys=[show_id])
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
@@ -205,6 +209,26 @@ class CITriggerHistory(Model):
|
||||
webhook = db.Column(db.Text)
|
||||
|
||||
|
||||
class TopologyViewGroup(Model):
|
||||
__tablename__ = 'c_topology_view_groups'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
|
||||
|
||||
class TopologyView(Model):
|
||||
__tablename__ = 'c_topology_views'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
|
||||
category = db.Column(db.String(32))
|
||||
central_node_type = db.Column(db.Integer)
|
||||
central_node_instances = db.Column(db.Text)
|
||||
path = db.Column(db.JSON)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CITypeUniqueConstraint(Model):
|
||||
__tablename__ = "c_c_t_u_c"
|
||||
|
||||
@@ -428,6 +452,7 @@ class CITypeHistory(Model):
|
||||
|
||||
attr_id = db.Column(db.Integer)
|
||||
trigger_id = db.Column(db.Integer)
|
||||
rc_id = db.Column(db.Integer)
|
||||
unique_constraint_id = db.Column(db.Integer)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
for rid in rids:
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
||||
|
@@ -113,18 +113,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
@@ -191,16 +190,15 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
|
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
|
||||
"POT-Creation-Date: 2024-05-28 18:05+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
@@ -284,166 +284,198 @@ msgstr "重复的触发器"
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
msgid "Duplicated reconciliation rule"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Reconciliation rule {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
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:103
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
#: api/lib/cmdb/resp_format.py:110
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
#: api/lib/cmdb/resp_format.py:126
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
#: api/lib/cmdb/resp_format.py:141
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:144
|
||||
msgid "Scheduling time format error"
|
||||
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:145
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:148
|
||||
msgid "Topology view {} already exists"
|
||||
msgstr "拓扑视图 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
msgid "Topology group {} already exists"
|
||||
msgstr "拓扑视图分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:151
|
||||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:8
|
||||
msgid "Company info already existed"
|
||||
msgstr "公司信息已存在,无法创建!"
|
||||
@@ -700,6 +732,10 @@ msgstr "LDAP测试用户名必填"
|
||||
msgid "Company wide"
|
||||
msgstr "全公司"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:84
|
||||
msgid "No permission to access resource {}, perm {} "
|
||||
msgstr "您没有资源: {} 的 {} 权限"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:9
|
||||
msgid "login successful"
|
||||
msgstr "登录成功"
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
@@ -254,3 +255,23 @@ class CIPasswordView(APIView):
|
||||
|
||||
def post(self, ci_id, attr_id):
|
||||
return self.get(ci_id, attr_id)
|
||||
|
||||
|
||||
class CIBaselineView(APIView):
|
||||
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
|
||||
|
||||
@args_required("before_date")
|
||||
def get(self):
|
||||
ci_ids = handle_arg_list(request.values.get('ci_ids'))
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
|
||||
|
||||
@args_required("before_date")
|
||||
def post(self, ci_id):
|
||||
if 'rollback' in request.url:
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(**CIManager().rollback(ci_id, before_date))
|
||||
|
||||
return self.get(ci_id)
|
||||
|
@@ -30,6 +30,7 @@ class CIRelationSearchView(APIView):
|
||||
level: default is 1
|
||||
facet: statistic
|
||||
"""
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
@@ -86,6 +87,26 @@ class CIRelationStatisticsView(APIView):
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class CIRelationSearchFullView(APIView):
|
||||
url_prefix = "/ci_relations/search/full"
|
||||
|
||||
def get(self):
|
||||
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
|
||||
level = request.values.get('level', 1)
|
||||
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
|
||||
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
|
||||
|
||||
start = time.time()
|
||||
s = Search(root_ids, level, has_m2m=has_m2m)
|
||||
try:
|
||||
result = s.search_full(type_ids)
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
current_app.logger.debug("search time is :{0}".format(time.time() - start))
|
||||
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class GetSecondCIsView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
||||
|
||||
|
@@ -7,7 +7,6 @@ from io import BytesIO
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
"/ci_types/groups/order",
|
||||
"/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.create_CIType_group, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def post(self):
|
||||
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
|
||||
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def put(self, gid=None):
|
||||
if "/order" in request.url:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||
type_ids = request.values.get('type_ids')
|
||||
|
||||
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.delete_CIType_group, app_cli.admin_name)
|
||||
def delete(self, gid):
|
||||
type_ids = request.values.get("type_ids")
|
||||
CITypeGroupManager.delete(gid, type_ids)
|
||||
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeGroupOrderView(APIView):
|
||||
url_prefix = "/ci_types/groups/order"
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.update_CIType_group, app_cli.admin_name)
|
||||
def put(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
tpt = request.values.get('ci_type_template') or {}
|
||||
|
||||
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
||||
class CITypeTemplateFileView(APIView):
|
||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self): # export
|
||||
tpt_json = CITypeTemplateManager.export_template()
|
||||
tpt_json = dict(ci_type_template=tpt_json)
|
||||
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
||||
mimetype='application/json',
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
f = request.files.get('file')
|
||||
|
||||
|
@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
@@ -41,7 +45,8 @@ class GetParentsView(APIView):
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
res, type2attributes = CITypeRelationManager.get()
|
||||
|
||||
@@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
|
||||
|
@@ -3,14 +3,16 @@
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(id2options=request.values.get('id2options'))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
CustomDashboardManager.delete(_id)
|
||||
|
||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
||||
class SystemConfigApiView(APIView):
|
||||
url_prefix = ("/system_config",)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name", value_required=True)
|
||||
def get(self):
|
||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(SystemConfigManager.cls)
|
||||
@args_required("name", value_required=True)
|
||||
@args_required("option", value_required=True)
|
||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
||||
def put(self, _id=None):
|
||||
return self.post()
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
CustomDashboardManager.delete(request.values['name'])
|
||||
|
@@ -5,28 +5,29 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
||||
|
||||
|
||||
class CITriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>",)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||
def get(self, ci_id=None):
|
||||
if ci_id is not None:
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
def get(self, ci_id):
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(result)
|
||||
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
class CIsTriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
trigger_id = request.values.get("trigger_id")
|
||||
operate_type = request.values.get("operate_type")
|
||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
||||
class CITypeHistoryView(APIView):
|
||||
url_prefix = "/history/ci_types"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
username = request.values.get("username")
|
||||
|
@@ -8,20 +8,22 @@ from flask import request
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_required("cr_ids")
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
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)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
|
@@ -4,14 +4,16 @@
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def post(self):
|
||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def put(self, rel_id):
|
||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
|
||||
|
178
cmdb-api/api/views/cmdb/topology.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.topology import TopologyViewManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class TopologyGroupView(APIView):
|
||||
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
|
||||
|
||||
@args_required('name')
|
||||
@args_validate(TopologyViewManager.group_cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.get('name')
|
||||
order = request.values.get('order')
|
||||
|
||||
group = TopologyViewManager.add_group(name, order)
|
||||
|
||||
return self.jsonify(group.to_dict())
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def put(self, group_id):
|
||||
name = request.values.get('name')
|
||||
view_ids = request.values.get('view_ids')
|
||||
group = TopologyViewManager().update_group(group_id, name, view_ids)
|
||||
|
||||
return self.jsonify(**group)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.delete_topology_group, app_cli.admin_name)
|
||||
def delete(self, group_id):
|
||||
TopologyViewManager.delete_group(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
class TopologyGroupOrderView(APIView):
|
||||
url_prefix = ('/topology_views/groups/order',)
|
||||
|
||||
@args_required('group_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
|
||||
TopologyViewManager.group_order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyView(APIView):
|
||||
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
|
||||
|
||||
def get(self, type_id=None, _id=None):
|
||||
if type_id is not None:
|
||||
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
|
||||
|
||||
if _id is not None:
|
||||
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
|
||||
|
||||
return self.jsonify(TopologyViewManager.get_all())
|
||||
|
||||
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.pop('name')
|
||||
group_id = request.values.pop('group_id', None)
|
||||
option = request.values.pop('option', None)
|
||||
order = request.values.pop('order', None)
|
||||
|
||||
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
|
||||
def put(self, _id):
|
||||
topo_view = TopologyViewManager.update(_id, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
|
||||
def delete(self, _id):
|
||||
TopologyViewManager.delete(_id)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyOrderView(APIView):
|
||||
url_prefix = ('/topology_views/order',)
|
||||
|
||||
@args_required('view_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
view_ids = request.values.get('view_ids')
|
||||
|
||||
TopologyViewManager.group_inner_order(view_ids)
|
||||
|
||||
return self.jsonify(view_ids=view_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyViewPreview(APIView):
|
||||
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
|
||||
|
||||
def get(self, _id=None):
|
||||
if _id is not None:
|
||||
acl = ACLManager('cmdb')
|
||||
resource_name = TopologyViewManager.get_name_by_id(_id)
|
||||
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
|
||||
not is_app_admin('cmdb')):
|
||||
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
|
||||
|
||||
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
|
||||
else:
|
||||
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
|
||||
|
||||
def post(self, _id=None):
|
||||
return self.get(_id)
|
||||
|
||||
|
||||
class TopologyViewGrantView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
|
||||
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyViewRevokeView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
|
||||
|
||||
@args_required('perms')
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
@@ -1,7 +1,7 @@
|
||||
-i https://mirrors.aliyun.com/pypi/simple
|
||||
alembic==1.7.7
|
||||
bs4==0.0.1
|
||||
celery>=5.3.1
|
||||
celery==5.3.1
|
||||
celery-once==3.0.1
|
||||
click==8.1.3
|
||||
elasticsearch==7.17.9
|
||||
@@ -53,4 +53,4 @@ shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
||||
python-magic==0.4.27
|
||||
|
@@ -20,10 +20,16 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
|
||||
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
|
||||
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
|
||||
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
|
||||
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
|
||||
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
|
||||
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
'user': SQLALCHEMY_DATABASE_URI
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
@@ -39,12 +39,14 @@
|
||||
"md5": "^2.2.1",
|
||||
"moment": "^2.24.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"relation-graph": "^1.1.0",
|
||||
"relation-graph": "^2.1.42",
|
||||
"snabbdom": "^3.5.1",
|
||||
"sortablejs": "1.9.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
"viser-vue": "^2.4.8",
|
||||
"vue": "2.6.11",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cropper": "^0.6.2",
|
||||
"vue-grid-layout": "2.3.12",
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1711963254221') format('woff2'),
|
||||
url('iconfont.woff?t=1711963254221') format('woff'),
|
||||
url('iconfont.ttf?t=1711963254221') format('truetype');
|
||||
src: url('iconfont.woff2?t=1716896994700') format('woff2'),
|
||||
url('iconfont.woff?t=1716896994700') format('woff'),
|
||||
url('iconfont.ttf?t=1716896994700') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,206 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ops-topology_view:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
|
||||
.monitor-host_analysis:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
|
||||
.a-Group427319324:before {
|
||||
content: "\e929";
|
||||
}
|
||||
|
||||
.monitor-native:before {
|
||||
content: "\e928";
|
||||
}
|
||||
|
||||
.veops-filter2:before {
|
||||
content: "\e927";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies:before {
|
||||
content: "\e926";
|
||||
}
|
||||
|
||||
.monitor-threshold_value:before {
|
||||
content: "\e921";
|
||||
}
|
||||
|
||||
.monitor-disposition:before {
|
||||
content: "\e922";
|
||||
}
|
||||
|
||||
.monitor-automatic_discovery:before {
|
||||
content: "\e923";
|
||||
}
|
||||
|
||||
.monitor-grouping_list:before {
|
||||
content: "\e924";
|
||||
}
|
||||
|
||||
.monitor-node_list:before {
|
||||
content: "\e925";
|
||||
}
|
||||
|
||||
.monitor-general_view:before {
|
||||
content: "\e920";
|
||||
}
|
||||
|
||||
.monitor-network_topology:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
|
||||
.monitor-node_management:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
||||
.monitor-alarm_policy:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
|
||||
.monitor-alarm:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
|
||||
.monitor-healing:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
.monitor-data_acquisition:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.monitor-analysis:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
|
||||
.monitor-index:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.monitor-user_defined:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.monitor-database:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.monitor-common:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.veops-edit:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.veops-empower:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.veops-share:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.veops-export:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.a-veops-import1:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.monitor-ip:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.monitor-director:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.monitor-host:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.a-cmdb-log1:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.monitor-add:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.monitor-down:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.monitor-up:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.itsm-unfold:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.itsm-stretch:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison2:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison1:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.a-monitor-online1:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-application-selected:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.ops-setting-application:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.ops-setting-basic:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-basic-selected:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.ops-setting-security:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.ops-setting-theme:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.veops-show:before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.itsm-duration:before {
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
.caise-VPC:before {
|
||||
content: "\e910";
|
||||
}
|
||||
@@ -257,10 +457,6 @@
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
@@ -489,10 +685,6 @@
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
@@ -657,38 +849,6 @@
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketsetting-selected:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.ops-itsm-reports-selected:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog-selected:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketmanage-selected:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.ops-itsm-knowledge-selected:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.ops-itsm-workstation-selected:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.ops-itsm-servicedesk-selected:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.ops-itsm-planticket-selected:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog:before {
|
||||
content: "\e868";
|
||||
}
|
||||
@@ -1061,26 +1221,10 @@
|
||||
content: "\e816";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch-selected:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc-selected:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource-selected:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference-selected:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference:before {
|
||||
content: "\e7fa";
|
||||
}
|
||||
@@ -1089,22 +1233,10 @@
|
||||
content: "\e7fb";
|
||||
}
|
||||
|
||||
.ops-cmdb-tree-selected:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation-selected:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc:before {
|
||||
content: "\e7fe";
|
||||
}
|
||||
|
||||
.ops-cmdb-search-selected:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation:before {
|
||||
content: "\e800";
|
||||
}
|
||||
@@ -1113,14 +1245,6 @@
|
||||
content: "\e801";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype-selected:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.ops-cmdb-dashboard-selected:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype:before {
|
||||
content: "\e805";
|
||||
}
|
||||
@@ -1129,10 +1253,6 @@
|
||||
content: "\e806";
|
||||
}
|
||||
|
||||
.ops-cmdb-screen-selected:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource:before {
|
||||
content: "\e808";
|
||||
}
|
||||
@@ -1481,14 +1601,6 @@
|
||||
content: "\e7a6";
|
||||
}
|
||||
|
||||
.ops-setting-role-selected:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-group-selected:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.ops-setting-role:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
@@ -1929,18 +2041,10 @@
|
||||
content: "\e738";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected-copy:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-notice:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.ops-setting-notice-selected:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected:before {
|
||||
content: "\e731";
|
||||
}
|
||||
@@ -1965,10 +2069,6 @@
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure-selected:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
@@ -1977,10 +2077,6 @@
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.ops-setting-companyInfo-selected:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.ops-email:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
@@ -3021,14 +3117,6 @@
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.ops-dag-dashboard-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-dag-applet-selected:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.ops-dag-applet:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -3037,62 +3125,26 @@
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.ops-dag-terminal-selected:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ops-dag-cron:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.ops-dag-cron-selected:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ops-dag-history:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.ops-dag-history-selected:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.ops-dag-dags-selected:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview-selected:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.ops-dag-panel:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.ops-dag-panel-selected:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.ops-dag-variables:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.ops-dag-variables-selected:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin-selected:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.ops-dag-dags:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
@@ -5,6 +5,356 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "40499246",
|
||||
"name": "ops-topology_view",
|
||||
"font_class": "ops-topology_view",
|
||||
"unicode": "e92b",
|
||||
"unicode_decimal": 59691
|
||||
},
|
||||
{
|
||||
"icon_id": "40411336",
|
||||
"name": "monitor-host_analysis",
|
||||
"font_class": "monitor-host_analysis",
|
||||
"unicode": "e92a",
|
||||
"unicode_decimal": 59690
|
||||
},
|
||||
{
|
||||
"icon_id": "40372105",
|
||||
"name": "monitor-add2",
|
||||
"font_class": "a-Group427319324",
|
||||
"unicode": "e929",
|
||||
"unicode_decimal": 59689
|
||||
},
|
||||
{
|
||||
"icon_id": "40368097",
|
||||
"name": "monitor-native",
|
||||
"font_class": "monitor-native",
|
||||
"unicode": "e928",
|
||||
"unicode_decimal": 59688
|
||||
},
|
||||
{
|
||||
"icon_id": "40357355",
|
||||
"name": "veops-filter2",
|
||||
"font_class": "veops-filter2",
|
||||
"unicode": "e927",
|
||||
"unicode_decimal": 59687
|
||||
},
|
||||
{
|
||||
"icon_id": "40356229",
|
||||
"name": "ops-cmdb-data_companies-selected",
|
||||
"font_class": "ops-cmdb-data_companies-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "40343814",
|
||||
"name": "ops-cmdb-data_companies",
|
||||
"font_class": "ops-cmdb-data_companies",
|
||||
"unicode": "e926",
|
||||
"unicode_decimal": 59686
|
||||
},
|
||||
{
|
||||
"icon_id": "40326916",
|
||||
"name": "monitor-threshold_value",
|
||||
"font_class": "monitor-threshold_value",
|
||||
"unicode": "e921",
|
||||
"unicode_decimal": 59681
|
||||
},
|
||||
{
|
||||
"icon_id": "40326913",
|
||||
"name": "monitor-disposition",
|
||||
"font_class": "monitor-disposition",
|
||||
"unicode": "e922",
|
||||
"unicode_decimal": 59682
|
||||
},
|
||||
{
|
||||
"icon_id": "40326911",
|
||||
"name": "monitor-automatic_discovery",
|
||||
"font_class": "monitor-automatic_discovery",
|
||||
"unicode": "e923",
|
||||
"unicode_decimal": 59683
|
||||
},
|
||||
{
|
||||
"icon_id": "40326907",
|
||||
"name": "monitor-grouping_list",
|
||||
"font_class": "monitor-grouping_list",
|
||||
"unicode": "e924",
|
||||
"unicode_decimal": 59684
|
||||
},
|
||||
{
|
||||
"icon_id": "40326906",
|
||||
"name": "monitor-node_list",
|
||||
"font_class": "monitor-node_list",
|
||||
"unicode": "e925",
|
||||
"unicode_decimal": 59685
|
||||
},
|
||||
{
|
||||
"icon_id": "40326667",
|
||||
"name": "monitor-general_view",
|
||||
"font_class": "monitor-general_view",
|
||||
"unicode": "e920",
|
||||
"unicode_decimal": 59680
|
||||
},
|
||||
{
|
||||
"icon_id": "40326683",
|
||||
"name": "monitor-network_topology",
|
||||
"font_class": "monitor-network_topology",
|
||||
"unicode": "e91b",
|
||||
"unicode_decimal": 59675
|
||||
},
|
||||
{
|
||||
"icon_id": "40326682",
|
||||
"name": "monitor-node_management",
|
||||
"font_class": "monitor-node_management",
|
||||
"unicode": "e91c",
|
||||
"unicode_decimal": 59676
|
||||
},
|
||||
{
|
||||
"icon_id": "40326681",
|
||||
"name": "monitor-alarm_policy",
|
||||
"font_class": "monitor-alarm_policy",
|
||||
"unicode": "e91d",
|
||||
"unicode_decimal": 59677
|
||||
},
|
||||
{
|
||||
"icon_id": "40326680",
|
||||
"name": "monitor-alarm",
|
||||
"font_class": "monitor-alarm",
|
||||
"unicode": "e91e",
|
||||
"unicode_decimal": 59678
|
||||
},
|
||||
{
|
||||
"icon_id": "40326679",
|
||||
"name": "monitor-healing",
|
||||
"font_class": "monitor-healing",
|
||||
"unicode": "e91f",
|
||||
"unicode_decimal": 59679
|
||||
},
|
||||
{
|
||||
"icon_id": "40326685",
|
||||
"name": "monitor-data_acquisition",
|
||||
"font_class": "monitor-data_acquisition",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "40326684",
|
||||
"name": "monitor-analysis",
|
||||
"font_class": "monitor-analysis",
|
||||
"unicode": "e91a",
|
||||
"unicode_decimal": 59674
|
||||
},
|
||||
{
|
||||
"icon_id": "40308359",
|
||||
"name": "monitor-index",
|
||||
"font_class": "monitor-index",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "40307829",
|
||||
"name": "monitor-user_defined",
|
||||
"font_class": "monitor-user_defined",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "40307835",
|
||||
"name": "monitor-database",
|
||||
"font_class": "monitor-database",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "40307833",
|
||||
"name": "monitor-common",
|
||||
"font_class": "monitor-common",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "40307035",
|
||||
"name": "veops-edit",
|
||||
"font_class": "veops-edit",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "40307027",
|
||||
"name": "veops-empower",
|
||||
"font_class": "veops-empower",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "40307026",
|
||||
"name": "veops-share",
|
||||
"font_class": "veops-share",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "40306997",
|
||||
"name": "veops-export",
|
||||
"font_class": "veops-export",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "40306881",
|
||||
"name": "veops-import",
|
||||
"font_class": "a-veops-import1",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "40262335",
|
||||
"name": "monitor-ip (1)",
|
||||
"font_class": "monitor-ip",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "40262337",
|
||||
"name": "monitor-director",
|
||||
"font_class": "monitor-director",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "40262336",
|
||||
"name": "monitor-host",
|
||||
"font_class": "monitor-host",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "40262216",
|
||||
"name": "cmdb-log",
|
||||
"font_class": "a-cmdb-log1",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "40261712",
|
||||
"name": "monitor-add",
|
||||
"font_class": "monitor-add",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "40248186",
|
||||
"name": "monitor-down",
|
||||
"font_class": "monitor-down",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "40248184",
|
||||
"name": "monitor-up",
|
||||
"font_class": "monitor-up",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "40235502",
|
||||
"name": "itsm-unfold",
|
||||
"font_class": "itsm-unfold",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "40235453",
|
||||
"name": "itsm-shrink",
|
||||
"font_class": "itsm-stretch",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "40217123",
|
||||
"name": "monitor-data_comaparison2",
|
||||
"font_class": "monitor-data_comaparison2",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "40217122",
|
||||
"name": "monitor-data_comaparison1",
|
||||
"font_class": "monitor-data_comaparison1",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "40176936",
|
||||
"name": "monitor-online",
|
||||
"font_class": "a-monitor-online1",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "40043662",
|
||||
"name": "ops-setting-application-selected",
|
||||
"font_class": "ops-setting-application-selected",
|
||||
"unicode": "e919",
|
||||
"unicode_decimal": 59673
|
||||
},
|
||||
{
|
||||
"icon_id": "40043685",
|
||||
"name": "ops-setting-application",
|
||||
"font_class": "ops-setting-application",
|
||||
"unicode": "e918",
|
||||
"unicode_decimal": 59672
|
||||
},
|
||||
{
|
||||
"icon_id": "40043049",
|
||||
"name": "ops-setting-basic",
|
||||
"font_class": "ops-setting-basic",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "40043047",
|
||||
"name": "ops-setting-basic-selected",
|
||||
"font_class": "ops-setting-basic-selected",
|
||||
"unicode": "e917",
|
||||
"unicode_decimal": 59671
|
||||
},
|
||||
{
|
||||
"icon_id": "40038753",
|
||||
"name": "ops-setting-security",
|
||||
"font_class": "ops-setting-security",
|
||||
"unicode": "e915",
|
||||
"unicode_decimal": 59669
|
||||
},
|
||||
{
|
||||
"icon_id": "40038752",
|
||||
"name": "ops-setting-theme",
|
||||
"font_class": "ops-setting-theme",
|
||||
"unicode": "e916",
|
||||
"unicode_decimal": 59670
|
||||
},
|
||||
{
|
||||
"icon_id": "39948814",
|
||||
"name": "veops-show",
|
||||
"font_class": "veops-show",
|
||||
"unicode": "e914",
|
||||
"unicode_decimal": 59668
|
||||
},
|
||||
{
|
||||
"icon_id": "39926816",
|
||||
"name": "itsm-duration",
|
||||
"font_class": "itsm-duration",
|
||||
"unicode": "e913",
|
||||
"unicode_decimal": 59667
|
||||
},
|
||||
{
|
||||
"icon_id": "39926833",
|
||||
"name": "itsm-workload (1)",
|
||||
"font_class": "itsm-workload",
|
||||
"unicode": "e912",
|
||||
"unicode_decimal": 59666
|
||||
},
|
||||
{
|
||||
"icon_id": "39782649",
|
||||
"name": "VPC",
|
||||
@@ -432,13 +782,6 @@
|
||||
"unicode": "e8d5",
|
||||
"unicode_decimal": 59605
|
||||
},
|
||||
{
|
||||
"icon_id": "38547389",
|
||||
"name": "setting-authentication-selected",
|
||||
"font_class": "ops-setting-auth-selected",
|
||||
"unicode": "e8d4",
|
||||
"unicode_decimal": 59604
|
||||
},
|
||||
{
|
||||
"icon_id": "38533133",
|
||||
"name": "itsm-knowledge (2)",
|
||||
@@ -838,13 +1181,6 @@
|
||||
"unicode": "e89c",
|
||||
"unicode_decimal": 59548
|
||||
},
|
||||
{
|
||||
"icon_id": "37940033",
|
||||
"name": "ops-setting-duty-selected",
|
||||
"font_class": "ops-setting-duty-selected",
|
||||
"unicode": "e89b",
|
||||
"unicode_decimal": 59547
|
||||
},
|
||||
{
|
||||
"icon_id": "37841524",
|
||||
"name": "datainsight-sequential",
|
||||
@@ -1132,62 +1468,6 @@
|
||||
"unicode": "e870",
|
||||
"unicode_decimal": 59504
|
||||
},
|
||||
{
|
||||
"icon_id": "35984161",
|
||||
"name": "ops-itsm-ticketsetting-selected",
|
||||
"font_class": "ops-itsm-ticketsetting-selected",
|
||||
"unicode": "e860",
|
||||
"unicode_decimal": 59488
|
||||
},
|
||||
{
|
||||
"icon_id": "35984162",
|
||||
"name": "ops-itsm-reports-selected",
|
||||
"font_class": "ops-itsm-reports-selected",
|
||||
"unicode": "e861",
|
||||
"unicode_decimal": 59489
|
||||
},
|
||||
{
|
||||
"icon_id": "35984163",
|
||||
"name": "ops-itsm-servicecatalog-selected",
|
||||
"font_class": "ops-itsm-servicecatalog-selected",
|
||||
"unicode": "e862",
|
||||
"unicode_decimal": 59490
|
||||
},
|
||||
{
|
||||
"icon_id": "35984164",
|
||||
"name": "ops-itsm-ticketmanage-selected",
|
||||
"font_class": "ops-itsm-ticketmanage-selected",
|
||||
"unicode": "e863",
|
||||
"unicode_decimal": 59491
|
||||
},
|
||||
{
|
||||
"icon_id": "35984165",
|
||||
"name": "ops-itsm-knowledge-selected",
|
||||
"font_class": "ops-itsm-knowledge-selected",
|
||||
"unicode": "e864",
|
||||
"unicode_decimal": 59492
|
||||
},
|
||||
{
|
||||
"icon_id": "35984166",
|
||||
"name": "ops-itsm-workstation-selected",
|
||||
"font_class": "ops-itsm-workstation-selected",
|
||||
"unicode": "e865",
|
||||
"unicode_decimal": 59493
|
||||
},
|
||||
{
|
||||
"icon_id": "35984167",
|
||||
"name": "ops-itsm-servicedesk-selected",
|
||||
"font_class": "ops-itsm-servicedesk-selected",
|
||||
"unicode": "e866",
|
||||
"unicode_decimal": 59494
|
||||
},
|
||||
{
|
||||
"icon_id": "35984168",
|
||||
"name": "ops-itsm-planticket-selected",
|
||||
"font_class": "ops-itsm-planticket-selected",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "35984169",
|
||||
"name": "ops-itsm-servicecatalog",
|
||||
@@ -1839,13 +2119,6 @@
|
||||
"unicode": "e816",
|
||||
"unicode_decimal": 59414
|
||||
},
|
||||
{
|
||||
"icon_id": "35400645",
|
||||
"name": "ops-cmdb-batch-selected",
|
||||
"font_class": "ops-cmdb-batch-selected",
|
||||
"unicode": "e803",
|
||||
"unicode_decimal": 59395
|
||||
},
|
||||
{
|
||||
"icon_id": "35400646",
|
||||
"name": "ops-cmdb-batch",
|
||||
@@ -1853,27 +2126,6 @@
|
||||
"unicode": "e80a",
|
||||
"unicode_decimal": 59402
|
||||
},
|
||||
{
|
||||
"icon_id": "35395300",
|
||||
"name": "ops-cmdb-adc-selected",
|
||||
"font_class": "ops-cmdb-adc-selected",
|
||||
"unicode": "e7f7",
|
||||
"unicode_decimal": 59383
|
||||
},
|
||||
{
|
||||
"icon_id": "35395301",
|
||||
"name": "ops-cmdb-resource-selected",
|
||||
"font_class": "ops-cmdb-resource-selected",
|
||||
"unicode": "e7f8",
|
||||
"unicode_decimal": 59384
|
||||
},
|
||||
{
|
||||
"icon_id": "35395302",
|
||||
"name": "ops-cmdb-preference-selected",
|
||||
"font_class": "ops-cmdb-preference-selected",
|
||||
"unicode": "e7f9",
|
||||
"unicode_decimal": 59385
|
||||
},
|
||||
{
|
||||
"icon_id": "35395303",
|
||||
"name": "ops-cmdb-preference",
|
||||
@@ -1888,20 +2140,6 @@
|
||||
"unicode": "e7fb",
|
||||
"unicode_decimal": 59387
|
||||
},
|
||||
{
|
||||
"icon_id": "35395305",
|
||||
"name": "ops-cmdb-tree-selected",
|
||||
"font_class": "ops-cmdb-tree-selected",
|
||||
"unicode": "e7fc",
|
||||
"unicode_decimal": 59388
|
||||
},
|
||||
{
|
||||
"icon_id": "35395306",
|
||||
"name": "ops-cmdb-relation-selected",
|
||||
"font_class": "ops-cmdb-relation-selected",
|
||||
"unicode": "e7fd",
|
||||
"unicode_decimal": 59389
|
||||
},
|
||||
{
|
||||
"icon_id": "35395307",
|
||||
"name": "ops-cmdb-adc",
|
||||
@@ -1909,13 +2147,6 @@
|
||||
"unicode": "e7fe",
|
||||
"unicode_decimal": 59390
|
||||
},
|
||||
{
|
||||
"icon_id": "35395308",
|
||||
"name": "ops-cmdb-search-selected",
|
||||
"font_class": "ops-cmdb-search-selected",
|
||||
"unicode": "e7ff",
|
||||
"unicode_decimal": 59391
|
||||
},
|
||||
{
|
||||
"icon_id": "35395309",
|
||||
"name": "ops-cmdb-relation",
|
||||
@@ -1930,20 +2161,6 @@
|
||||
"unicode": "e801",
|
||||
"unicode_decimal": 59393
|
||||
},
|
||||
{
|
||||
"icon_id": "35395311",
|
||||
"name": "ops-cmdb-citype-selected",
|
||||
"font_class": "ops-cmdb-citype-selected",
|
||||
"unicode": "e802",
|
||||
"unicode_decimal": 59394
|
||||
},
|
||||
{
|
||||
"icon_id": "35395313",
|
||||
"name": "ops-cmdb-dashboard-selected",
|
||||
"font_class": "ops-cmdb-dashboard-selected",
|
||||
"unicode": "e804",
|
||||
"unicode_decimal": 59396
|
||||
},
|
||||
{
|
||||
"icon_id": "35395314",
|
||||
"name": "ops-cmdb-citype",
|
||||
@@ -1958,13 +2175,6 @@
|
||||
"unicode": "e806",
|
||||
"unicode_decimal": 59398
|
||||
},
|
||||
{
|
||||
"icon_id": "35395316",
|
||||
"name": "ops-cmdb-screen-selected",
|
||||
"font_class": "ops-cmdb-screen-selected",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "35395317",
|
||||
"name": "ops-cmdb-resource",
|
||||
@@ -2574,20 +2784,6 @@
|
||||
"unicode": "e7a6",
|
||||
"unicode_decimal": 59302
|
||||
},
|
||||
{
|
||||
"icon_id": "34792792",
|
||||
"name": "ops-setting-role-selected",
|
||||
"font_class": "ops-setting-role-selected",
|
||||
"unicode": "e7a0",
|
||||
"unicode_decimal": 59296
|
||||
},
|
||||
{
|
||||
"icon_id": "34792793",
|
||||
"name": "ops-setting-group-selected",
|
||||
"font_class": "ops-setting-group-selected",
|
||||
"unicode": "e7a1",
|
||||
"unicode_decimal": 59297
|
||||
},
|
||||
{
|
||||
"icon_id": "34792794",
|
||||
"name": "ops-setting-role",
|
||||
@@ -3358,13 +3554,6 @@
|
||||
"unicode": "e738",
|
||||
"unicode_decimal": 59192
|
||||
},
|
||||
{
|
||||
"icon_id": "37575490",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
"font_class": "ops-setting-notice-email-selected-copy",
|
||||
"unicode": "e889",
|
||||
"unicode_decimal": 59529
|
||||
},
|
||||
{
|
||||
"icon_id": "34108346",
|
||||
"name": "ops-setting-notice",
|
||||
@@ -3372,13 +3561,6 @@
|
||||
"unicode": "e72f",
|
||||
"unicode_decimal": 59183
|
||||
},
|
||||
{
|
||||
"icon_id": "34108348",
|
||||
"name": "ops-setting-notice-selected",
|
||||
"font_class": "ops-setting-notice-selected",
|
||||
"unicode": "e730",
|
||||
"unicode_decimal": 59184
|
||||
},
|
||||
{
|
||||
"icon_id": "34108504",
|
||||
"name": "ops-setting-notice-email-selected",
|
||||
@@ -3421,13 +3603,6 @@
|
||||
"unicode": "e736",
|
||||
"unicode_decimal": 59190
|
||||
},
|
||||
{
|
||||
"icon_id": "34108244",
|
||||
"name": "ops-setting-companyStructure-selected",
|
||||
"font_class": "ops-setting-companyStructure-selected",
|
||||
"unicode": "e72b",
|
||||
"unicode_decimal": 59179
|
||||
},
|
||||
{
|
||||
"icon_id": "34108296",
|
||||
"name": "ops-setting-companyStructure",
|
||||
@@ -3442,13 +3617,6 @@
|
||||
"unicode": "e72d",
|
||||
"unicode_decimal": 59181
|
||||
},
|
||||
{
|
||||
"icon_id": "34108330",
|
||||
"name": "ops-setting-companyInfo-selected",
|
||||
"font_class": "ops-setting-companyInfo-selected",
|
||||
"unicode": "e72e",
|
||||
"unicode_decimal": 59182
|
||||
},
|
||||
{
|
||||
"icon_id": "34099810",
|
||||
"name": "ops-email",
|
||||
@@ -5269,20 +5437,6 @@
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "33053294",
|
||||
"name": "ops-dag-dashboard-selected",
|
||||
"font_class": "ops-dag-dashboard-selected",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "33053330",
|
||||
"name": "ops-dag-applet-selected",
|
||||
"font_class": "ops-dag-applet-selected",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "33053531",
|
||||
"name": "ops-dag-applet",
|
||||
@@ -5297,13 +5451,6 @@
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "33053589",
|
||||
"name": "ops-dag-terminal-selected",
|
||||
"font_class": "ops-dag-terminal-selected",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "33053591",
|
||||
"name": "ops-dag-cron",
|
||||
@@ -5311,13 +5458,6 @@
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "33053609",
|
||||
"name": "ops-dag-cron-selected",
|
||||
"font_class": "ops-dag-cron-selected",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "33053615",
|
||||
"name": "ops-dag-history",
|
||||
@@ -5325,20 +5465,6 @@
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "33053617",
|
||||
"name": "ops-dag-history-selected",
|
||||
"font_class": "ops-dag-history-selected",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "33053681",
|
||||
"name": "ops-dag-dags-selected",
|
||||
"font_class": "ops-dag-dags-selected",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "33053682",
|
||||
"name": "ops-dag-dagreview",
|
||||
@@ -5346,13 +5472,6 @@
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "33053684",
|
||||
"name": "ops-dag-dagreview-selected",
|
||||
"font_class": "ops-dag-dagreview-selected",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "33053691",
|
||||
"name": "ops-dag-panel",
|
||||
@@ -5360,13 +5479,6 @@
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "33053692",
|
||||
"name": "ops-dag-panel-selected",
|
||||
"font_class": "ops-dag-panel-selected",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "33053707",
|
||||
"name": "ops-dag-variables",
|
||||
@@ -5374,27 +5486,6 @@
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "33053715",
|
||||
"name": "ops-dag-variables-selected",
|
||||
"font_class": "ops-dag-variables-selected",
|
||||
"unicode": "e618",
|
||||
"unicode_decimal": 58904
|
||||
},
|
||||
{
|
||||
"icon_id": "33053718",
|
||||
"name": "ops-dag-appletadmin",
|
||||
"font_class": "ops-dag-appletadmin",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "33053720",
|
||||
"name": "ops-dag-appletadmin-selected",
|
||||
"font_class": "ops-dag-appletadmin-selected",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "33055163",
|
||||
"name": "ops-dag-dags",
|
||||
|
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
|
||||
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F84FF"/>
|
||||
<stop offset="1" stop-color="#85CBFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 916 B |
@@ -1,14 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5187FF"/>
|
||||
<stop offset="1" stop-color="#84C9FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 840 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#497DFF"/>
|
||||
<stop offset="1" stop-color="#8CD5FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4D82FF"/>
|
||||
<stop offset="1" stop-color="#88CFFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1022 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 979 B |
@@ -1,9 +0,0 @@
|
||||
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5A85FF"/>
|
||||
<stop offset="1" stop-color="#8DD8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -9,7 +9,7 @@
|
||||
:bodyStyle="{ padding: '24px 12px' }"
|
||||
:placement="placement"
|
||||
>
|
||||
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
|
||||
<ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,14 @@ export default {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'resourceSearch'
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -69,12 +69,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
background: @primary-color;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
|
@@ -230,7 +230,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
width: 100%;
|
||||
.vue-treeselect__multi-value-item-container {
|
||||
@@ -263,7 +262,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -300,14 +298,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -182,8 +182,8 @@ export default {
|
||||
<tag {...{ props, attrs }}>
|
||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||
<span>
|
||||
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot &&
|
||||
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot && !menu.meta.disabled &&
|
||||
<a-popover
|
||||
overlayClassName="custom-menu-extra-submenu"
|
||||
placement="rightTop"
|
||||
|
@@ -146,7 +146,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.regex-select {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
@@ -162,7 +161,7 @@ export default {
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +179,7 @@ export default {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
@@ -126,7 +126,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.role-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -186,14 +185,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -60,7 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.sidebar-list-item {
|
||||
.ops_popover_item();
|
||||
margin: 2px 0;
|
||||
@@ -94,7 +93,7 @@ export default {
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
|
@@ -52,7 +52,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.two-column-layout {
|
||||
margin-bottom: -24px;
|
||||
|
@@ -4,34 +4,22 @@
|
||||
@click="jumpTo"
|
||||
v-if="showTitle && !collapsed"
|
||||
style="width: 100%; height: 100%; cursor: pointer"
|
||||
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')"
|
||||
:src="require('@/assets/logo_VECMDB.png')"
|
||||
/>
|
||||
<img
|
||||
@click="jumpTo"
|
||||
v-else
|
||||
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
|
||||
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')"
|
||||
:src="require('@/assets/logo.png')"
|
||||
/>
|
||||
<!-- <logo-svg/> -->
|
||||
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
|
||||
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import LogoSvg from '@/assets/logo.svg?inline'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'Logo',
|
||||
components: {
|
||||
// LogoSvg,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
file_name: (state) => state.logo.file_name,
|
||||
small_file_name: (state) => state.logo.small_file_name,
|
||||
}),
|
||||
},
|
||||
components: {},
|
||||
computed: {},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@@ -81,7 +81,7 @@ export default {
|
||||
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))) {
|
||||
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
|
||||
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||
} else {
|
||||
this.$router.push('/cmdb/dashboard')
|
||||
|
@@ -96,10 +96,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.color {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.custom-user {
|
||||
.custom-user-item {
|
||||
@@ -118,7 +117,7 @@ export default {
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -9,25 +9,11 @@
|
||||
import gridSvg from '@/assets/icons/grid.svg?inline'
|
||||
import top_agent from '@/assets/icons/top_agent.svg?inline'
|
||||
import top_acl from '@/assets/icons/top_acl.svg?inline'
|
||||
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
|
||||
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
|
||||
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
|
||||
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
|
||||
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
|
||||
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
|
||||
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
|
||||
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
|
||||
|
||||
export {
|
||||
gridSvg,
|
||||
top_agent,
|
||||
top_acl,
|
||||
ops_default_show,
|
||||
ops_is_choice,
|
||||
ops_is_index,
|
||||
ops_is_link,
|
||||
ops_is_password,
|
||||
ops_is_sortable,
|
||||
ops_is_unique,
|
||||
ops_move_icon
|
||||
}
|
||||
|
14
cmdb-ui/src/directive/highlight/highlight.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import './highlight.less'
|
||||
|
||||
const highlight = (el, binding) => {
|
||||
if (binding.value.value) {
|
||||
let testValue = `${binding.value.value}`
|
||||
if (['(', ')', '$'].includes(testValue)) {
|
||||
testValue = `\\${testValue}`
|
||||
}
|
||||
const regex = new RegExp(`(${testValue})`, 'gi')
|
||||
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
|
||||
}
|
||||
}
|
||||
|
||||
export default highlight
|
5
cmdb-ui/src/directive/highlight/highlight.less
Normal file
@@ -0,0 +1,5 @@
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.ops-text-highlight {
|
||||
background-color: @primary-color_3;
|
||||
}
|
12
cmdb-ui/src/directive/highlight/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import hightlight from './highlight'
|
||||
|
||||
const install = function (Vue) {
|
||||
Vue.directive('hightlight', hightlight)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.hightlight = hightlight
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
hightlight.install = install
|
||||
export default hightlight
|
13
cmdb-ui/src/directive/waves/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import waves from './waves'
|
||||
|
||||
const install = function (Vue) {
|
||||
Vue.directive('waves', waves)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.waves = waves
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
waves.install = install
|
||||
export default waves
|
26
cmdb-ui/src/directive/waves/waves.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.waves-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
72
cmdb-ui/src/directive/waves/waves.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import './waves.css'
|
||||
|
||||
const context = '@@wavesContext'
|
||||
|
||||
function handleClick(el, binding) {
|
||||
function handle(e) {
|
||||
const customOpts = Object.assign({}, binding.value)
|
||||
const opts = Object.assign({
|
||||
ele: el, // 波纹作用元素
|
||||
type: 'hit', // hit 点击位置扩散 center中心点扩展
|
||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
},
|
||||
customOpts
|
||||
)
|
||||
const target = opts.ele
|
||||
if (target) {
|
||||
target.style.position = 'relative'
|
||||
target.style.overflow = 'hidden'
|
||||
const rect = target.getBoundingClientRect()
|
||||
let ripple = target.querySelector('.waves-ripple')
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span')
|
||||
ripple.className = 'waves-ripple'
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
|
||||
target.appendChild(ripple)
|
||||
} else {
|
||||
ripple.className = 'waves-ripple'
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
|
||||
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
|
||||
break
|
||||
default:
|
||||
ripple.style.top =
|
||||
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
|
||||
document.body.scrollTop) + 'px'
|
||||
ripple.style.left =
|
||||
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
|
||||
document.body.scrollLeft) + 'px'
|
||||
}
|
||||
ripple.style.backgroundColor = opts.color
|
||||
ripple.className = 'waves-ripple z-active'
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!el[context]) {
|
||||
el[context] = {
|
||||
removeHandle: handle
|
||||
}
|
||||
} else {
|
||||
el[context].removeHandle = handle
|
||||
}
|
||||
|
||||
return handle
|
||||
}
|
||||
|
||||
export default {
|
||||
bind(el, binding) {
|
||||
el.addEventListener('click', handleClick(el, binding), false)
|
||||
},
|
||||
update(el, binding) {
|
||||
el.removeEventListener('click', el[context].removeHandle, false)
|
||||
el.addEventListener('click', handleClick(el, binding), false)
|
||||
},
|
||||
unbind(el) {
|
||||
el.removeEventListener('click', el[context].removeHandle, false)
|
||||
el[context] = null
|
||||
delete el[context]
|
||||
}
|
||||
}
|
@@ -31,7 +31,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
store.dispatch("loadAllUsers")
|
||||
store.dispatch("loadAllEmployees")
|
||||
store.dispatch("loadAllDepartments")
|
||||
store.dispatch("getCompanyInfo")
|
||||
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
const redirect = decodeURIComponent(from.query.redirect || to.path)
|
||||
|
@@ -233,7 +233,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-history {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -32,7 +32,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-operation-history {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -189,7 +189,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resource-types {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -352,7 +352,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-resources {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -285,7 +285,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-roles {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -88,7 +88,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-secret-key {
|
||||
background-color: #fff;
|
||||
|
@@ -320,7 +320,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-trigger {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -188,7 +188,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.acl-users {
|
||||
border-radius: @border-radius-box;
|
||||
|
@@ -73,3 +73,11 @@ export function deleteCIRelationView(firstCiId, secondCiId, data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function searchCIRelationFull(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/search/full`,
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function searchCI(params) {
|
||||
export function searchCI(params, isShowMessage = true) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/s`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
params: params,
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -54,3 +54,36 @@ export function getCiTriggersByCiId(ci_id, params) {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCiRelatedTickets(params) {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/get_tickets_by`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function judgeItsmInstalled() {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/itsm_existed`,
|
||||
method: 'GET',
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIsBaseline(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/baseline`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function CIBaselineRollback(ciId, params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/${ciId}/baseline/rollback`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
110
cmdb-ui/src/modules/cmdb/api/topology.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function getTopoGroups() {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function postTopoGroup(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupByGId(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupsOrder(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/order`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoGroup(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addTopoView(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTopoView(_id, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationsByTypeId(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/relations/ci_types/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function previewTopoView(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/preview`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export function showTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}/view`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function grantTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/grant`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function revokeTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/revoke`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
After Width: | Height: | Size: 22 KiB |
@@ -221,7 +221,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-transfer {
|
||||
.ant-transfer-list {
|
||||
@@ -231,7 +230,7 @@ export default {
|
||||
background-color: #f9fbff;
|
||||
border-bottom: none;
|
||||
.ant-transfer-list-header-title {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -259,7 +258,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
border-radius: 4px;
|
||||
width: 12px;
|
||||
}
|
||||
@@ -272,7 +271,7 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #cacdd9;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
@@ -292,8 +291,8 @@ export default {
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||
<div class="cmdb-grant" :style="{ }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
@@ -57,6 +57,20 @@
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('TopologyView')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
|
||||
<TopologyViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
:viewId="CITypeId"
|
||||
grantType="TopologyView"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grantTopologyView"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
@@ -72,11 +86,12 @@ import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import TopologyViewGrant from './topologyViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, TopologyViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -291,6 +306,20 @@ export default {
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'TopologyView') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.addedRids = rids
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
@@ -311,7 +340,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
@@ -324,7 +352,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
|
@@ -59,7 +59,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.read-checkbox {
|
||||
.read-checkbox-half-checked {
|
||||
width: 16px;
|
||||
@@ -76,10 +75,10 @@ export default {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
// background-color: @primary-color;
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
border-left-color: @primary-color;
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
|
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="topology-view-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:scroll-y="{enabled: true}"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTopologyView, revokeTopologyView } from '@/modules/cmdb/api/topology.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
export default {
|
||||
name: 'TopologyViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
viewId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['read', 'update', 'delete', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.topology-view-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
@@ -49,6 +49,8 @@
|
||||
import _ from 'lodash'
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||
|
||||
export default {
|
||||
name: 'NoticeContent',
|
||||
components: { Editor, Toolbar },
|
||||
@@ -76,6 +78,10 @@ export default {
|
||||
if (editor == null) return
|
||||
editor.destroy() // When the component is destroyed, destroy the editor in time
|
||||
},
|
||||
beforeCreate() {
|
||||
const locale = this.$i18n.locale === 'zh' ? 'zh-CN' : 'en'
|
||||
i18nChangeLanguage(locale)
|
||||
},
|
||||
methods: {
|
||||
onCreated(editor) {
|
||||
this.editor = Object.seal(editor) // Be sure to use Object.seal(), otherwise an error will be reported
|
||||
@@ -112,7 +118,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.notice-content {
|
||||
width: 100%;
|
||||
& &-main {
|
||||
@@ -176,8 +181,8 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,11 +191,10 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.notice-content {
|
||||
.w-e-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.w-e-text-placeholder {
|
||||
line-height: 1.5;
|
||||
|
@@ -95,12 +95,12 @@
|
||||
isFocusExpression = false
|
||||
}
|
||||
"
|
||||
class="ci-searchform-expression"
|
||||
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
|
||||
:style="{ width }"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="emitRefresh"
|
||||
>
|
||||
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
|
||||
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
|
||||
</a-input>
|
||||
<slot></slot>
|
||||
</a-space>
|
||||
@@ -198,6 +198,9 @@ export default {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
if (this.typeId) {
|
||||
this.currenCiType = [this.typeId]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleAdvanced() {
|
||||
@@ -264,7 +267,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
@import '../../views/index.less';
|
||||
.ci-searchform-expression {
|
||||
> input {
|
||||
@@ -285,6 +287,9 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ci-searchform-expression-has-value .ant-input-suffix {
|
||||
color: @func-color_3;
|
||||
}
|
||||
.cmdb-search-form {
|
||||
.ant-form-item-label {
|
||||
overflow: hidden;
|
||||
@@ -295,8 +300,6 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.search-form-bar {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
|
@@ -244,7 +244,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-subscribe-drawer {
|
||||
.cmdb-subscribe-drawer-container {
|
||||
@@ -254,7 +253,7 @@ export default {
|
||||
}
|
||||
.cmdb-subscribe-drawer-tree-header {
|
||||
border-radius: 4px;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
@@ -265,7 +264,7 @@ export default {
|
||||
> span {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding: 3px 12px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
@@ -273,7 +272,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,10 +314,9 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-subscribe-drawer {
|
||||
.ant-tabs-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|