Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5aff5d728d | ||
|
464b9d5394 | ||
|
9c4cc20e13 | ||
|
c3c8602207 | ||
|
c961e288af | ||
|
e22b0c5290 | ||
|
900cf1f617 | ||
|
f28ad4d041 | ||
|
d2698b05c0 | ||
|
6f1332148c | ||
|
5fa18eeb00 | ||
|
03fdf5c004 | ||
|
f277cf088e | ||
|
b1f8a0024b | ||
|
aae43a53b5 | ||
|
8c2cdb1ca4 | ||
|
57d4bf5548 | ||
|
5e7c6199bf | ||
|
28dca7f086 | ||
|
4a3c21eec4 | ||
|
5d28c28023 | ||
|
ba6edb3abe | ||
|
2f1d57cee1 | ||
|
4111c634d9 | ||
|
f1fababa3d | ||
|
fe6373422e | ||
|
b3ea776886 | ||
|
c4d2ce313d | ||
|
20103a0fe6 | ||
|
394e2aeac6 | ||
|
8f7d78c26c | ||
|
7eecf3cec3 | ||
|
f6e9c443f7 | ||
|
857cbd82fd | ||
|
9a14296e02 | ||
|
f638b52759 | ||
|
78da728105 | ||
|
eb69029a51 | ||
|
07a097eba2 | ||
|
e843e3eac9 | ||
|
7308cfa6c2 | ||
|
64ea4fb21f | ||
|
e15cefaa38 | ||
|
f32339b969 | ||
|
131d213a73 | ||
|
ff98777689 | ||
|
383d4c88ed | ||
|
bb7157e292 | ||
|
b1a82f1a67 | ||
|
de86ea3852 | ||
|
bf05ea240e | ||
|
8ec0d619d7 | ||
|
61f8c463bc | ||
|
9b4dc3e43b | ||
|
9e69be8256 |
1
.gitignore
vendored
@@ -78,3 +78,4 @@ cmdb-ui/npm-debug.log*
|
|||||||
cmdb-ui/yarn-debug.log*
|
cmdb-ui/yarn-debug.log*
|
||||||
cmdb-ui/yarn-error.log*
|
cmdb-ui/yarn-error.log*
|
||||||
cmdb-ui/package-lock.json
|
cmdb-ui/package-lock.json
|
||||||
|
start.sh
|
||||||
|
@@ -11,7 +11,7 @@ click = ">=5.0"
|
|||||||
# Api
|
# Api
|
||||||
Flask-RESTful = "==0.3.10"
|
Flask-RESTful = "==0.3.10"
|
||||||
# Database
|
# Database
|
||||||
Flask-SQLAlchemy = "==2.5.0"
|
Flask-SQLAlchemy = "==3.0.5"
|
||||||
SQLAlchemy = "==1.4.49"
|
SQLAlchemy = "==1.4.49"
|
||||||
PyMySQL = "==1.1.0"
|
PyMySQL = "==1.1.0"
|
||||||
redis = "==4.6.0"
|
redis = "==4.6.0"
|
||||||
@@ -68,6 +68,8 @@ pycryptodomex = ">=3.19.0"
|
|||||||
lz4 = ">=4.3.2"
|
lz4 = ">=4.3.2"
|
||||||
python-magic = "==0.4.27"
|
python-magic = "==0.4.27"
|
||||||
jsonpath = "==0.82.2"
|
jsonpath = "==0.82.2"
|
||||||
|
networkx = ">=3.1"
|
||||||
|
ipaddress = ">=1.0.23"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
# Testing
|
# Testing
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import click
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import requests
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import click
|
|
||||||
import requests
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
@@ -24,6 +23,7 @@ from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
|||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
|
from api.lib.cmdb.dcim.rack import RackManager
|
||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import UserCache
|
from api.lib.perm.acl.acl import UserCache
|
||||||
@@ -37,11 +37,14 @@ from api.lib.secrets.secrets import InnerKVManger
|
|||||||
from api.models.acl import App
|
from api.models.acl import App
|
||||||
from api.models.acl import ResourceType
|
from api.models.acl import ResourceType
|
||||||
from api.models.cmdb import Attribute
|
from api.models.cmdb import Attribute
|
||||||
|
from api.models.cmdb import AttributeHistory
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
from api.models.cmdb import CIRelation
|
||||||
from api.models.cmdb import CIType
|
from api.models.cmdb import CIType
|
||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
|
from api.models.cmdb import OperationRecord
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
|
from api.tasks.cmdb import batch_ci_cache
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@@ -193,7 +196,7 @@ def cmdb_counter():
|
|||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
db.session.remove()
|
db.session.commit()
|
||||||
|
|
||||||
CMDBCounterCache.reset()
|
CMDBCounterCache.reset()
|
||||||
|
|
||||||
@@ -207,6 +210,8 @@ def cmdb_counter():
|
|||||||
|
|
||||||
CMDBCounterCache.flush_sub_counter()
|
CMDBCounterCache.flush_sub_counter()
|
||||||
|
|
||||||
|
RackManager().check_u_slot()
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -557,5 +562,20 @@ def cmdb_patch(version):
|
|||||||
existed.update(option=option, commit=False)
|
existed.update(option=option, commit=False)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
if version >= "2.4.14": # update ci columns: updated_at and updated_by
|
||||||
|
ci_ids = []
|
||||||
|
for i in CI.get_by(only_query=True).filter(CI.updated_at.is_(None)):
|
||||||
|
hist = AttributeHistory.get_by(ci_id=i.id, only_query=True).order_by(AttributeHistory.id.desc()).first()
|
||||||
|
if hist is not None:
|
||||||
|
record = OperationRecord.get_by_id(hist.record_id)
|
||||||
|
if record is not None:
|
||||||
|
u = UserCache.get(record.uid)
|
||||||
|
i.update(updated_at=record.created_at, updated_by=u and u.nickname, flush=True)
|
||||||
|
ci_ids.append(i.id)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
batch_ci_cache.apply_async(args=(ci_ids,))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("cmdb patch failed: {}".format(e))
|
print("cmdb patch failed: {}".format(e))
|
||||||
|
@@ -512,7 +512,7 @@ class CMDBCounterCache(object):
|
|||||||
result[i.type_id]['rule_count'] = len(adts) + AutoDiscoveryCITypeRelation.get_by(
|
result[i.type_id]['rule_count'] = len(adts) + AutoDiscoveryCITypeRelation.get_by(
|
||||||
ad_type_id=i.type_id, only_query=True).count()
|
ad_type_id=i.type_id, only_query=True).count()
|
||||||
result[i.type_id]['exec_target_count'] = len(
|
result[i.type_id]['exec_target_count'] = len(
|
||||||
set([i.oneagent_id for adt in adts for i in db.session.query(
|
set([j.oneagent_id for adt in adts for j in db.session.query(
|
||||||
AutoDiscoveryRuleSyncHistory.oneagent_id).filter(
|
AutoDiscoveryRuleSyncHistory.oneagent_id).filter(
|
||||||
AutoDiscoveryRuleSyncHistory.adt_id == adt.id)]))
|
AutoDiscoveryRuleSyncHistory.adt_id == adt.id)]))
|
||||||
|
|
||||||
|
@@ -45,6 +45,7 @@ from api.lib.notify import notify_send
|
|||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.lib.perm.acl.acl import validate_permission
|
from api.lib.perm.acl.acl import validate_permission
|
||||||
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.secrets.inner import InnerCrypt
|
from api.lib.secrets.inner import InnerCrypt
|
||||||
from api.lib.secrets.vault import VaultClient
|
from api.lib.secrets.vault import VaultClient
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
@@ -113,7 +114,8 @@ class CIManager(object):
|
|||||||
ci_type = CITypeCache.get(ci.type_id)
|
ci_type = CITypeCache.get(ci.type_id)
|
||||||
res["ci_type"] = ci_type.name
|
res["ci_type"] = ci_type.name
|
||||||
|
|
||||||
res.update(cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key))
|
ci_list = cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key)
|
||||||
|
ci_list and res.update(ci_list[0])
|
||||||
|
|
||||||
res['_type'] = ci_type.id
|
res['_type'] = ci_type.id
|
||||||
res['_id'] = ci_id
|
res['_id'] = ci_id
|
||||||
@@ -206,6 +208,8 @@ class CIManager(object):
|
|||||||
res['_type'] = ci_type.id
|
res['_type'] = ci_type.id
|
||||||
res['ci_type_alias'] = ci_type.alias
|
res['ci_type_alias'] = ci_type.alias
|
||||||
res['_id'] = ci_id
|
res['_id'] = ci_id
|
||||||
|
res['_updated_at'] = str(ci.updated_at or '')
|
||||||
|
res['_updated_by'] = ci.updated_by
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -353,6 +357,7 @@ class CIManager(object):
|
|||||||
is_auto_discovery=False,
|
is_auto_discovery=False,
|
||||||
_is_admin=False,
|
_is_admin=False,
|
||||||
ticket_id=None,
|
ticket_id=None,
|
||||||
|
_sync=False,
|
||||||
**ci_dict):
|
**ci_dict):
|
||||||
"""
|
"""
|
||||||
add ci
|
add ci
|
||||||
@@ -362,6 +367,7 @@ class CIManager(object):
|
|||||||
:param is_auto_discovery: default is False
|
:param is_auto_discovery: default is False
|
||||||
:param _is_admin: default is False
|
:param _is_admin: default is False
|
||||||
:param ticket_id:
|
:param ticket_id:
|
||||||
|
:param _sync:
|
||||||
:param ci_dict:
|
:param ci_dict:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -492,10 +498,16 @@ class CIManager(object):
|
|||||||
record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id)
|
record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id)
|
||||||
|
|
||||||
if record_id or has_dynamic: # has changed
|
if record_id or has_dynamic: # has changed
|
||||||
|
if not _sync:
|
||||||
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
|
||||||
|
else:
|
||||||
|
ci_cache(ci.id, operate_type, record_id)
|
||||||
|
|
||||||
if ref_ci_dict: # add relations
|
if ref_ci_dict: # add relations
|
||||||
|
if not _sync:
|
||||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
||||||
|
else:
|
||||||
|
ci_relation_add(ref_ci_dict, ci.id, current_user.uid)
|
||||||
|
|
||||||
return ci.id
|
return ci.id
|
||||||
|
|
||||||
@@ -568,6 +580,9 @@ class CIManager(object):
|
|||||||
for attr_id in password_dict:
|
for attr_id in password_dict:
|
||||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||||
|
|
||||||
|
u = UserCache.get(current_user.uid)
|
||||||
|
ci.update(updated_at=now, updated_by=u and u.nickname)
|
||||||
|
|
||||||
if record_id or has_dynamic: # has changed
|
if record_id or has_dynamic: # has changed
|
||||||
if not _sync:
|
if not _sync:
|
||||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||||
@@ -581,6 +596,9 @@ class CIManager(object):
|
|||||||
else:
|
else:
|
||||||
ci_relation_add(ref_ci_dict, ci.id)
|
ci_relation_add(ref_ci_dict, ci.id)
|
||||||
|
|
||||||
|
u = UserCache.get(current_user.uid)
|
||||||
|
ci.update(updated_at=now, updated_by=u and u.nickname)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_unique_value(ci_id, unique_name, unique_value):
|
def update_unique_value(ci_id, unique_name, unique_value):
|
||||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||||
@@ -709,13 +727,18 @@ class CIManager(object):
|
|||||||
elif fields:
|
elif fields:
|
||||||
_res = []
|
_res = []
|
||||||
for d in res:
|
for d in res:
|
||||||
|
if isinstance(fields, dict) and d.get("_type") not in fields:
|
||||||
|
_res.append(d)
|
||||||
|
continue
|
||||||
|
|
||||||
_d = dict()
|
_d = dict()
|
||||||
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
||||||
_d["ci_type"] = d.get("ci_type")
|
_d["ci_type"] = d.get("ci_type")
|
||||||
if unique_required:
|
if unique_required:
|
||||||
_d[d.get('unique')] = d.get(d.get('unique'))
|
_d[d.get('unique')] = d.get(d.get('unique'))
|
||||||
|
|
||||||
for field in fields + ['ci_type_alias', 'unique', 'unique_alias']:
|
_fields = list(fields.get(_d['_type']) or [] if isinstance(fields, dict) else fields)
|
||||||
|
for field in _fields + ['ci_type_alias', 'unique', 'unique_alias']:
|
||||||
_d[field] = d.get(field)
|
_d[field] = d.get(field)
|
||||||
_res.append(_d)
|
_res.append(_d)
|
||||||
return _res
|
return _res
|
||||||
@@ -732,9 +755,8 @@ class CIManager(object):
|
|||||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_IDS
|
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_IDS
|
||||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||||
|
|
||||||
if not fields:
|
|
||||||
filter_fields_sql = ""
|
filter_fields_sql = ""
|
||||||
else:
|
if fields and isinstance(fields, list):
|
||||||
_fields = list()
|
_fields = list()
|
||||||
for field in fields:
|
for field in fields:
|
||||||
attr = AttributeCache.get(field)
|
attr = AttributeCache.get(field)
|
||||||
@@ -776,6 +798,10 @@ class CIManager(object):
|
|||||||
ci_set.add(ci_id)
|
ci_set.add(ci_id)
|
||||||
res[ci2pos[ci_id]] = ci_dict
|
res[ci2pos[ci_id]] = ci_dict
|
||||||
|
|
||||||
|
if isinstance(fields, dict) and fields.get(type_id):
|
||||||
|
if attr_name not in fields[type_id]:
|
||||||
|
continue
|
||||||
|
|
||||||
if ret_key == RetKey.NAME:
|
if ret_key == RetKey.NAME:
|
||||||
attr_key = attr_name
|
attr_key = attr_name
|
||||||
elif ret_key == RetKey.ALIAS:
|
elif ret_key == RetKey.ALIAS:
|
||||||
@@ -813,7 +839,7 @@ class CIManager(object):
|
|||||||
if not ci_ids:
|
if not ci_ids:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
fields = [] if fields is None or not isinstance(fields, list) else fields
|
fields = [] if not fields else fields
|
||||||
|
|
||||||
ci_id_tuple = tuple(map(int, ci_ids))
|
ci_id_tuple = tuple(map(int, ci_ids))
|
||||||
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields, unique_required, excludes=excludes)
|
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields, unique_required, excludes=excludes)
|
||||||
@@ -1263,10 +1289,10 @@ class CIRelationManager(object):
|
|||||||
return existed.id
|
return existed.id
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(cr_id, apply_async=True):
|
def delete(cr_id, apply_async=True, valid=True):
|
||||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(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') and current_user.username != 'worker':
|
if current_app.config.get('USE_ACL') and current_user.username != 'worker' and valid:
|
||||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||||
if not ACLManager().has_permission(
|
if not ACLManager().has_permission(
|
||||||
resource_name,
|
resource_name,
|
||||||
@@ -1305,7 +1331,7 @@ class CIRelationManager(object):
|
|||||||
return cr
|
return cr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
|
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True, valid=True):
|
||||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||||
second_ci_id=second_ci_id,
|
second_ci_id=second_ci_id,
|
||||||
to_dict=False,
|
to_dict=False,
|
||||||
@@ -1315,7 +1341,7 @@ class CIRelationManager(object):
|
|||||||
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
# 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)
|
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
cls.delete(cr.id, apply_async=apply_async)
|
cls.delete(cr.id, apply_async=apply_async, valid=valid)
|
||||||
|
|
||||||
return cr
|
return cr
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import networkx as nx
|
||||||
import toposort
|
import toposort
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -16,12 +17,14 @@ from api.lib.cmdb.cache import AttributeCache
|
|||||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
from api.lib.cmdb.const import CITypeOperateType
|
from api.lib.cmdb.const import CITypeOperateType
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.cmdb.const import CMDB_QUEUE
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
|
from api.lib.cmdb.const import SysComputedAttributes
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
from api.lib.cmdb.history import CITypeHistoryManager
|
from api.lib.cmdb.history import CITypeHistoryManager
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
@@ -63,6 +66,7 @@ class CITypeManager(object):
|
|||||||
"""
|
"""
|
||||||
manage CIType
|
manage CIType
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cls = CIType
|
cls = CIType
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -185,6 +189,9 @@ class CITypeManager(object):
|
|||||||
|
|
||||||
ci_type = cls.check_is_existed(type_id)
|
ci_type = cls.check_is_existed(type_id)
|
||||||
|
|
||||||
|
if ci_type.name in BuiltinModelEnum.all() and kwargs.get('name', ci_type.name) != ci_type.name:
|
||||||
|
return abort(400, ErrFormat.builtin_type_cannot_update_name)
|
||||||
|
|
||||||
cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
|
cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
|
||||||
# cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
|
# cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
|
||||||
|
|
||||||
@@ -845,10 +852,35 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_path(source_type_id, target_type_ids):
|
||||||
|
source_type_id = int(source_type_id)
|
||||||
|
target_type_ids = map(int, target_type_ids)
|
||||||
|
|
||||||
|
graph = nx.DiGraph()
|
||||||
|
|
||||||
|
def get_children(_id):
|
||||||
|
children = CITypeRelation.get_by(parent_id=_id, to_dict=False)
|
||||||
|
|
||||||
|
for i in children:
|
||||||
|
if i.child_id != _id:
|
||||||
|
graph.add_edge(i.parent_id, i.child_id)
|
||||||
|
get_children(i.child_id)
|
||||||
|
|
||||||
|
get_children(source_type_id)
|
||||||
|
|
||||||
|
paths = list(nx.all_simple_paths(graph, source_type_id, target_type_ids))
|
||||||
|
|
||||||
|
del graph
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _wrap_relation_type_dict(type_id, relation_inst):
|
def _wrap_relation_type_dict(type_id, relation_inst):
|
||||||
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
||||||
ci_type_dict["ctr_id"] = relation_inst.id
|
ci_type_dict["ctr_id"] = relation_inst.id
|
||||||
|
show_key = AttributeCache.get(ci_type_dict.get('show_id') or ci_type_dict['unique_id'])
|
||||||
|
ci_type_dict["show_key"] = show_key and show_key.name
|
||||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||||
if attr_filter:
|
if attr_filter:
|
||||||
@@ -1071,6 +1103,7 @@ class CITypeAttributeGroupManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_by_type_id(type_id, need_other=False):
|
def get_by_type_id(type_id, need_other=False):
|
||||||
|
_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found)
|
||||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||||
|
|
||||||
groups = []
|
groups = []
|
||||||
@@ -1120,6 +1153,12 @@ class CITypeAttributeGroupManager(object):
|
|||||||
if i.attr_id in attr2pos:
|
if i.attr_id in attr2pos:
|
||||||
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
|
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
|
||||||
|
|
||||||
|
if (_type.name in SysComputedAttributes.type2attr and
|
||||||
|
attr['name'] in SysComputedAttributes.type2attr[_type.name]):
|
||||||
|
attr['sys_computed'] = True
|
||||||
|
else:
|
||||||
|
attr['sys_computed'] = False
|
||||||
|
|
||||||
attr2pos[i.attr_id] = [group_pos, attr]
|
attr2pos[i.attr_id] = [group_pos, attr]
|
||||||
|
|
||||||
group.pop('inherited_from', None)
|
group.pop('inherited_from', None)
|
||||||
@@ -1514,7 +1553,10 @@ class CITypeTemplateManager(object):
|
|||||||
if existed is None:
|
if existed is None:
|
||||||
_group['type_id'] = type_id_map.get(_group['type_id'], _group['type_id'])
|
_group['type_id'] = type_id_map.get(_group['type_id'], _group['type_id'])
|
||||||
|
|
||||||
|
try:
|
||||||
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
for order, attr in enumerate(group['attributes'] or []):
|
for order, attr in enumerate(group['attributes'] or []):
|
||||||
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
|
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask_babel import lazy_gettext as _l
|
||||||
|
|
||||||
from api.lib.utils import BaseEnum
|
from api.lib.utils import BaseEnum
|
||||||
|
|
||||||
|
|
||||||
@@ -110,17 +112,47 @@ class ExecuteStatusEnum(BaseEnum):
|
|||||||
FAILED = '1'
|
FAILED = '1'
|
||||||
RUNNING = '2'
|
RUNNING = '2'
|
||||||
|
|
||||||
|
|
||||||
class RelationSourceEnum(BaseEnum):
|
class RelationSourceEnum(BaseEnum):
|
||||||
ATTRIBUTE_VALUES = "0"
|
ATTRIBUTE_VALUES = "0"
|
||||||
AUTO_DISCOVERY = "1"
|
AUTO_DISCOVERY = "1"
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinModelEnum(BaseEnum):
|
||||||
|
IPAM_SUBNET = "ipam_subnet"
|
||||||
|
IPAM_ADDRESS = "ipam_address"
|
||||||
|
IPAM_SCOPE = "ipam_scope"
|
||||||
|
|
||||||
|
DCIM_REGION = "dcim_region"
|
||||||
|
DCIM_IDC = "dcim_idc"
|
||||||
|
DCIM_SERVER_ROOM = "dcim_server_room"
|
||||||
|
DCIM_RACK = "dcim_rack"
|
||||||
|
|
||||||
|
|
||||||
|
BUILTIN_ATTRIBUTES = {
|
||||||
|
"_updated_at": _l("Update Time"),
|
||||||
|
"_updated_by": _l("Updated By"),
|
||||||
|
}
|
||||||
|
|
||||||
CMDB_QUEUE = "one_cmdb_async"
|
CMDB_QUEUE = "one_cmdb_async"
|
||||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||||
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||||
|
|
||||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id'}
|
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id', *BUILTIN_ATTRIBUTES.keys()}
|
||||||
|
|
||||||
|
|
||||||
|
class SysComputedAttributes(object):
|
||||||
|
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||||
|
type2attr = {
|
||||||
|
BuiltinModelEnum.IPAM_SUBNET: {
|
||||||
|
SubnetBuiltinAttributes.HOSTS_COUNT,
|
||||||
|
SubnetBuiltinAttributes.ASSIGN_COUNT,
|
||||||
|
SubnetBuiltinAttributes.USED_COUNT,
|
||||||
|
SubnetBuiltinAttributes.FREE_COUNT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
L_TYPE = None
|
L_TYPE = None
|
||||||
L_CI = None
|
L_CI = None
|
||||||
|
1
cmdb-api/api/lib/cmdb/dcim/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
33
cmdb-api/api/lib/cmdb/dcim/base.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
|
|
||||||
|
|
||||||
|
class DCIMBase(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.type_id = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_relation(parent_id, child_id):
|
||||||
|
if not parent_id or not child_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False)
|
||||||
|
|
||||||
|
def add(self, parent_id, **kwargs):
|
||||||
|
ci_id = CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)
|
||||||
|
|
||||||
|
if parent_id:
|
||||||
|
self.add_relation(parent_id, ci_id)
|
||||||
|
|
||||||
|
return ci_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, _id, **kwargs):
|
||||||
|
CIManager().update(_id, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, _id):
|
||||||
|
CIManager().delete(_id)
|
17
cmdb-api/api/lib/cmdb/dcim/const.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from api.lib.utils import BaseEnum
|
||||||
|
|
||||||
|
|
||||||
|
class RackBuiltinAttributes(BaseEnum):
|
||||||
|
U_COUNT = 'u_count'
|
||||||
|
U_START = 'u_start'
|
||||||
|
FREE_U_COUNT = 'free_u_count'
|
||||||
|
U_SLOT_ABNORMAL = 'u_slot_abnormal'
|
||||||
|
|
||||||
|
|
||||||
|
class OperateTypeEnum(BaseEnum):
|
||||||
|
ADD_DEVICE = "0"
|
||||||
|
REMOVE_DEVICE = "1"
|
||||||
|
MOVE_DEVICE = "2"
|
40
cmdb-api/api/lib/cmdb/dcim/history.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.mixin import DBMixin
|
||||||
|
from api.models.cmdb import DCIMOperationHistory
|
||||||
|
|
||||||
|
|
||||||
|
class OperateHistoryManager(DBMixin):
|
||||||
|
cls = DCIMOperationHistory
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False,
|
||||||
|
last_size=None, **kwargs):
|
||||||
|
numfound, result = super(OperateHistoryManager, cls).search(page, page_size, fl, only_query, reverse,
|
||||||
|
count_query, last_size, **kwargs)
|
||||||
|
|
||||||
|
ci_ids = [i['ci_id'] for i in result]
|
||||||
|
id2ci = {i['_id']: i for i in (CIManager.get_cis_by_ids(ci_ids) or []) if i}
|
||||||
|
type2show_key = dict()
|
||||||
|
for i in id2ci.values():
|
||||||
|
if i.get('_type') not in type2show_key:
|
||||||
|
ci_type = CITypeCache.get(i.get('_type'))
|
||||||
|
if ci_type:
|
||||||
|
show_key = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||||
|
type2show_key[i['_type']] = show_key and show_key.name
|
||||||
|
|
||||||
|
return numfound, result, id2ci, type2show_key
|
||||||
|
|
||||||
|
def _can_add(self, **kwargs):
|
||||||
|
kwargs['uid'] = current_user.uid
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def _can_update(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _can_delete(self, **kwargs):
|
||||||
|
pass
|
19
cmdb-api/api/lib/cmdb/dcim/idc.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.dcim.base import DCIMBase
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
|
||||||
|
|
||||||
|
class IDCManager(DCIMBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(IDCManager, self).__init__()
|
||||||
|
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_IDC) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_IDC))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
183
cmdb-api/api/lib/cmdb/dcim/rack.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import redis_lock
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.extensions import rd
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.dcim.base import DCIMBase
|
||||||
|
from api.lib.cmdb.dcim.const import OperateTypeEnum
|
||||||
|
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
|
||||||
|
from api.lib.cmdb.dcim.history import OperateHistoryManager
|
||||||
|
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_relation.search import Search as RelationSearch
|
||||||
|
|
||||||
|
|
||||||
|
class RackManager(DCIMBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(RackManager, self).__init__()
|
||||||
|
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, _id, **kwargs):
|
||||||
|
if RackBuiltinAttributes.U_COUNT in kwargs:
|
||||||
|
devices, _, _, _, _, _ = RelationSearch(
|
||||||
|
[_id],
|
||||||
|
level=[1],
|
||||||
|
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||||
|
count=1000000).search()
|
||||||
|
for device in devices:
|
||||||
|
u_start = device.get(RackBuiltinAttributes.U_START)
|
||||||
|
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||||
|
if u_start and u_start + u_count - 1 > kwargs[RackBuiltinAttributes.U_COUNT]:
|
||||||
|
return abort(400, ErrFormat.dcim_rack_u_count_invalid)
|
||||||
|
|
||||||
|
CIManager().update(_id, _sync=True, **kwargs)
|
||||||
|
|
||||||
|
if RackBuiltinAttributes.U_COUNT in kwargs:
|
||||||
|
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls._calc_u_free_count(_id)}
|
||||||
|
|
||||||
|
CIManager().update(_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
def delete(self, _id):
|
||||||
|
super(RackManager, self).delete(_id)
|
||||||
|
|
||||||
|
payload = {RackBuiltinAttributes.U_START: None}
|
||||||
|
_, _, second_cis = CIRelationManager.get_second_cis(_id, per_page='all')
|
||||||
|
for ci in second_cis:
|
||||||
|
CIManager().update(ci['_id'], **payload)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
|
||||||
|
rack = CIManager.get_ci_by_id(rack_id, need_children=False)
|
||||||
|
if not rack.get(RackBuiltinAttributes.U_COUNT):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if device_id is not None and u_count is None:
|
||||||
|
ci = CIManager().get_ci_by_id(device_id, need_children=False)
|
||||||
|
u_count = ci.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||||
|
|
||||||
|
if u_start and u_start + u_count - 1 > rack.get(RackBuiltinAttributes.U_COUNT):
|
||||||
|
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)
|
||||||
|
|
||||||
|
devices, _, _, _, _, _ = RelationSearch(
|
||||||
|
[rack_id],
|
||||||
|
level=[1],
|
||||||
|
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||||
|
count=1000000).search()
|
||||||
|
|
||||||
|
u_count_sum = 0
|
||||||
|
for device in devices:
|
||||||
|
u_count_sum += (device.get(RackBuiltinAttributes.U_COUNT) or 2)
|
||||||
|
if device_id is not None:
|
||||||
|
_u_start = device.get(RackBuiltinAttributes.U_START)
|
||||||
|
_u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||||
|
if not _u_start:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if device.get('_id') != device_id and set(range(u_start, u_start + u_count)) & set(
|
||||||
|
range(_u_start, _u_start + _u_count)):
|
||||||
|
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)
|
||||||
|
|
||||||
|
return rack[RackBuiltinAttributes.U_COUNT] - u_count_sum
|
||||||
|
|
||||||
|
def check_u_slot(self):
|
||||||
|
racks, _, _, _, _, _ = SearchFromDB(
|
||||||
|
"_type:{}".format(self.type_id),
|
||||||
|
count=10000000,
|
||||||
|
fl=[RackBuiltinAttributes.U_START, RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_SLOT_ABNORMAL],
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
|
||||||
|
for rack in racks:
|
||||||
|
devices, _, _, _, _, _ = RelationSearch(
|
||||||
|
[rack['_id']],
|
||||||
|
level=[1],
|
||||||
|
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||||
|
count=1000000).search()
|
||||||
|
|
||||||
|
u_slot_sets = []
|
||||||
|
for device in devices:
|
||||||
|
u_start = device.get(RackBuiltinAttributes.U_START)
|
||||||
|
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||||
|
if u_start is not None and str(u_start).isdigit():
|
||||||
|
u_slot_sets.append(set(range(u_start, u_start + u_count)))
|
||||||
|
|
||||||
|
if len(u_slot_sets) > 1:
|
||||||
|
u_slot_abnormal = False
|
||||||
|
for a, b in itertools.combinations(u_slot_sets, 2):
|
||||||
|
if a.intersection(b):
|
||||||
|
u_slot_abnormal = True
|
||||||
|
break
|
||||||
|
if u_slot_abnormal != rack.get(RackBuiltinAttributes.U_SLOT_ABNORMAL):
|
||||||
|
payload = {RackBuiltinAttributes.U_SLOT_ABNORMAL: u_slot_abnormal}
|
||||||
|
CIManager().update(rack['_id'], **payload)
|
||||||
|
|
||||||
|
def add_device(self, rack_id, device_id, u_start, u_count=None):
|
||||||
|
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||||
|
self._calc_u_free_count(rack_id, device_id, u_start, u_count)
|
||||||
|
|
||||||
|
self.add_relation(rack_id, device_id)
|
||||||
|
|
||||||
|
payload = {RackBuiltinAttributes.U_START: u_start}
|
||||||
|
if u_count:
|
||||||
|
payload[RackBuiltinAttributes.U_COUNT] = u_count
|
||||||
|
CIManager().update(device_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, u_start, u_count)}
|
||||||
|
CIManager().update(rack_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||||
|
|
||||||
|
def remove_device(self, rack_id, device_id):
|
||||||
|
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||||
|
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
|
||||||
|
|
||||||
|
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
|
||||||
|
CIManager().update(rack_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
payload = {RackBuiltinAttributes.U_START: None}
|
||||||
|
CIManager().update(device_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||||
|
|
||||||
|
def move_device(self, rack_id, device_id, to_u_start):
|
||||||
|
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||||
|
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, to_u_start)}
|
||||||
|
CIManager().update(rack_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.MOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||||
|
|
||||||
|
def migrate_device(self, rack_id, device_id, to_rack_id, to_u_start):
|
||||||
|
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||||
|
self._calc_u_free_count(to_rack_id, device_id, to_u_start)
|
||||||
|
|
||||||
|
if rack_id != to_rack_id:
|
||||||
|
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
|
||||||
|
|
||||||
|
self.add_relation(to_rack_id, device_id)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(to_rack_id, device_id, to_u_start)}
|
||||||
|
CIManager().update(to_rack_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
|
||||||
|
|
||||||
|
if rack_id != to_rack_id:
|
||||||
|
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
|
||||||
|
CIManager().update(rack_id, _sync=True, **payload)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=to_rack_id, ci_id=device_id)
|
||||||
|
|
29
cmdb-api/api/lib/cmdb/dcim/region.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
|
||||||
|
|
||||||
|
class RegionManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_REGION) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_REGION))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
def add(self, **kwargs):
|
||||||
|
return CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, _id, **kwargs):
|
||||||
|
CIManager().update(_id, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, _id):
|
||||||
|
CIManager().delete(_id)
|
56
cmdb-api/api/lib/cmdb/dcim/server_room.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.dcim.base import DCIMBase
|
||||||
|
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CIRelation
|
||||||
|
|
||||||
|
|
||||||
|
class ServerRoomManager(DCIMBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(ServerRoomManager, self).__init__()
|
||||||
|
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_SERVER_ROOM) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_SERVER_ROOM))
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_racks(_id, q=None):
|
||||||
|
rack_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||||
|
|
||||||
|
relations = CIRelation.get_by(first_ci_id=_id, only_query=True).join(
|
||||||
|
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id == rack_type.id)
|
||||||
|
rack_ids = [i.second_ci_id for i in relations]
|
||||||
|
|
||||||
|
q = "_type:{}".format(rack_type.id) if not q else "_type:{},{}".format(rack_type.id, q)
|
||||||
|
if rack_ids:
|
||||||
|
response, _, _, _, numfound, _ = SearchFromDB(
|
||||||
|
q,
|
||||||
|
ci_ids=list(rack_ids),
|
||||||
|
count=1000000,
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
else:
|
||||||
|
response, numfound = [], 0
|
||||||
|
|
||||||
|
counter = dict(rack_count=numfound)
|
||||||
|
u_count = 0
|
||||||
|
free_u_count = 0
|
||||||
|
for i in response:
|
||||||
|
_u_count = i.get(RackBuiltinAttributes.U_COUNT) or 0
|
||||||
|
u_count += _u_count
|
||||||
|
free_u_count += (_u_count if i.get(RackBuiltinAttributes.FREE_U_COUNT) is None else
|
||||||
|
i.get(RackBuiltinAttributes.FREE_U_COUNT))
|
||||||
|
counter["u_count"] = u_count
|
||||||
|
counter["u_used_count"] = u_count - free_u_count
|
||||||
|
counter["device_count"] = CIRelation.get_by(only_query=True).filter(
|
||||||
|
CIRelation.first_ci_id.in_(rack_ids)).count()
|
||||||
|
|
||||||
|
return counter, response
|
85
cmdb-api/api/lib/cmdb/dcim/tree_view.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CIRelation
|
||||||
|
|
||||||
|
|
||||||
|
class TreeViewManager(object):
|
||||||
|
@classmethod
|
||||||
|
def get(cls):
|
||||||
|
region_type = CITypeCache.get(BuiltinModelEnum.DCIM_REGION) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_REGION))
|
||||||
|
|
||||||
|
idc_type = CITypeCache.get(BuiltinModelEnum.DCIM_IDC) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_IDC))
|
||||||
|
|
||||||
|
server_room_type = CITypeCache.get(BuiltinModelEnum.DCIM_SERVER_ROOM) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_SERVER_ROOM))
|
||||||
|
|
||||||
|
rack_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||||
|
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||||
|
|
||||||
|
relations = defaultdict(set)
|
||||||
|
ids = set()
|
||||||
|
has_parent_ids = set()
|
||||||
|
|
||||||
|
for i in CIRelation.get_by(only_query=True).join(CI, CI.id == CIRelation.first_ci_id).filter(
|
||||||
|
CI.type_id.in_([region_type.id, idc_type.id])):
|
||||||
|
relations[i.first_ci_id].add(i.second_ci_id)
|
||||||
|
ids.add(i.first_ci_id)
|
||||||
|
ids.add(i.second_ci_id)
|
||||||
|
has_parent_ids.add(i.second_ci_id)
|
||||||
|
|
||||||
|
for i in CIRelation.get_by(only_query=True).join(
|
||||||
|
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id.in_([idc_type.id, server_room_type.id])):
|
||||||
|
relations[i.first_ci_id].add(i.second_ci_id)
|
||||||
|
ids.add(i.first_ci_id)
|
||||||
|
ids.add(i.second_ci_id)
|
||||||
|
has_parent_ids.add(i.second_ci_id)
|
||||||
|
|
||||||
|
for i in CI.get_by(only_query=True).filter(CI.type_id.in_([region_type.id, idc_type.id])):
|
||||||
|
ids.add(i.id)
|
||||||
|
|
||||||
|
for _id in ids:
|
||||||
|
if _id not in has_parent_ids:
|
||||||
|
relations[None].add(_id)
|
||||||
|
|
||||||
|
type2name = dict()
|
||||||
|
type2name[region_type.id] = AttributeCache.get(region_type.show_id or region_type.unique_id).name
|
||||||
|
type2name[idc_type.id] = AttributeCache.get(idc_type.show_id or idc_type.unique_id).name
|
||||||
|
type2name[server_room_type.id] = AttributeCache.get(server_room_type.show_id or server_room_type.unique_id).name
|
||||||
|
|
||||||
|
response, _, _, _, _, _ = SearchFromDB(
|
||||||
|
"_type:({})".format(";".join(map(str, [region_type.id, idc_type.id, server_room_type.id]))),
|
||||||
|
ci_ids=list(ids),
|
||||||
|
count=1000000,
|
||||||
|
fl=list(type2name.values()),
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
id2ci = {i['_id']: i for i in response}
|
||||||
|
|
||||||
|
def _build_tree(_tree, parent_id=None):
|
||||||
|
tree = []
|
||||||
|
for child_id in _tree.get(parent_id, []):
|
||||||
|
children = sorted(_build_tree(_tree, child_id), key=lambda x: x['_id'])
|
||||||
|
if not id2ci.get(child_id):
|
||||||
|
continue
|
||||||
|
ci = id2ci[child_id]
|
||||||
|
if ci['ci_type'] == BuiltinModelEnum.DCIM_SERVER_ROOM:
|
||||||
|
ci['rack_count'] = CIRelation.get_by(first_ci_id=child_id, only_query=True).join(
|
||||||
|
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id == rack_type.id).count()
|
||||||
|
|
||||||
|
tree.append({'children': children, **ci})
|
||||||
|
return tree
|
||||||
|
|
||||||
|
result = sorted(_build_tree(relations), key=lambda x: x['_id'])
|
||||||
|
|
||||||
|
return result, type2name
|
@@ -10,6 +10,7 @@ from api.extensions import db
|
|||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import RelationTypeCache
|
from api.lib.cmdb.cache import RelationTypeCache
|
||||||
from api.lib.cmdb.const import OperateType
|
from api.lib.cmdb.const import OperateType
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
@@ -22,6 +23,7 @@ from api.models.cmdb import CITypeHistory
|
|||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
from api.models.cmdb import OperationRecord
|
from api.models.cmdb import OperationRecord
|
||||||
|
from api.lib.cmdb.utils import TableMap
|
||||||
|
|
||||||
|
|
||||||
class AttributeHistoryManger(object):
|
class AttributeHistoryManger(object):
|
||||||
@@ -59,8 +61,23 @@ class AttributeHistoryManger(object):
|
|||||||
total = len(records)
|
total = len(records)
|
||||||
|
|
||||||
res = {}
|
res = {}
|
||||||
|
show_attr_set = {}
|
||||||
|
show_attr_cache = {}
|
||||||
for record in records:
|
for record in records:
|
||||||
record_id = record.OperationRecord.id
|
record_id = record.OperationRecord.id
|
||||||
|
type_id = record.OperationRecord.type_id
|
||||||
|
ci_id = record.AttributeHistory.ci_id
|
||||||
|
show_attr_set[ci_id] = None
|
||||||
|
show_attr = show_attr_cache.setdefault(
|
||||||
|
type_id,
|
||||||
|
AttributeCache.get(
|
||||||
|
CITypeCache.get(type_id).show_id or CITypeCache.get(type_id).unique_id) if CITypeCache.get(type_id) else None
|
||||||
|
)
|
||||||
|
if show_attr:
|
||||||
|
attr_table = TableMap(attr=show_attr).table
|
||||||
|
attr_record = attr_table.get_by(attr_id=show_attr.id, ci_id=ci_id, first=True, to_dict=False)
|
||||||
|
show_attr_set[ci_id] = attr_record.value if attr_record else None
|
||||||
|
|
||||||
attr_hist = record.AttributeHistory.to_dict()
|
attr_hist = record.AttributeHistory.to_dict()
|
||||||
attr_hist['attr'] = AttributeCache.get(attr_hist['attr_id'])
|
attr_hist['attr'] = AttributeCache.get(attr_hist['attr_id'])
|
||||||
if attr_hist['attr']:
|
if attr_hist['attr']:
|
||||||
@@ -76,6 +93,7 @@ class AttributeHistoryManger(object):
|
|||||||
|
|
||||||
if record_id not in res:
|
if record_id not in res:
|
||||||
record_dict = record.OperationRecord.to_dict()
|
record_dict = record.OperationRecord.to_dict()
|
||||||
|
record_dict['show_attr_value'] = show_attr_set.get(ci_id)
|
||||||
record_dict["user"] = UserCache.get(record_dict.get("uid"))
|
record_dict["user"] = UserCache.get(record_dict.get("uid"))
|
||||||
if record_dict["user"]:
|
if record_dict["user"]:
|
||||||
record_dict['user'] = record_dict['user'].nickname
|
record_dict['user'] = record_dict['user'].nickname
|
||||||
|
1
cmdb-api/api/lib/cmdb/ipam/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
131
cmdb-api/api/lib/cmdb/ipam/address.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
import redis_lock
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.extensions import rd
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.ipam.const import IPAddressAssignStatus
|
||||||
|
from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes
|
||||||
|
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||||
|
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||||
|
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||||
|
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_relation.search import Search as RelationSearch
|
||||||
|
|
||||||
|
|
||||||
|
class IpAddressManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) or abort(
|
||||||
|
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def list_ip_address(parent_id):
|
||||||
|
numfound, _, result = CIRelationManager.get_second_cis(parent_id, per_page="all")
|
||||||
|
|
||||||
|
return numfound, result
|
||||||
|
|
||||||
|
def _get_cis(self, ips):
|
||||||
|
response, _, _, _, _, _ = SearchFromDB(
|
||||||
|
"_type:{},{}:({})".format(self.type_id, IPAddressBuiltinAttributes.IP, ";".join(ips or [])),
|
||||||
|
count=10000000, parent_node_perm_passed=True).search()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_relation(parent_id, child_id):
|
||||||
|
if not parent_id or not child_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calc_used_count(subnet_id):
|
||||||
|
q = "{}:(0;2),-{}:true".format(IPAddressBuiltinAttributes.ASSIGN_STATUS, IPAddressBuiltinAttributes.IS_USED)
|
||||||
|
|
||||||
|
return len(set(RelationSearch([subnet_id], level=[1], query=q, count=1000000).search(only_ids=True) or []))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calc_assign_count(subnet_id):
|
||||||
|
q = "{}:(0;2)".format(IPAddressBuiltinAttributes.ASSIGN_STATUS)
|
||||||
|
|
||||||
|
return len(set(RelationSearch([subnet_id], level=[1], query=q, count=1000000).search(only_ids=True) or []))
|
||||||
|
|
||||||
|
def _update_subnet_count(self, subnet_id, assign_count_computed, used_count=None):
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
cur = CIManager.get_ci_by_id(subnet_id, need_children=False)
|
||||||
|
if assign_count_computed:
|
||||||
|
payload[SubnetBuiltinAttributes.ASSIGN_COUNT] = self._calc_assign_count(subnet_id)
|
||||||
|
if used_count is not None:
|
||||||
|
payload[SubnetBuiltinAttributes.USED_COUNT] = used_count
|
||||||
|
|
||||||
|
payload[SubnetBuiltinAttributes.FREE_COUNT] = (cur[SubnetBuiltinAttributes.HOSTS_COUNT] -
|
||||||
|
self.calc_used_count(subnet_id))
|
||||||
|
CIManager().update(subnet_id, **payload)
|
||||||
|
|
||||||
|
def assign_ips(self, ips, subnet_id, cidr, **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param ips: ip list
|
||||||
|
:param subnet_id: subnet id
|
||||||
|
:param cidr: subnet cidr
|
||||||
|
:param kwargs: other attributes for ip address
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if subnet_id is not None:
|
||||||
|
subnet = CIManager.get_ci_by_id(subnet_id)
|
||||||
|
else:
|
||||||
|
cis, _, _, _, _, _ = SearchFromDB("_type:{},{}:{}".format(
|
||||||
|
BuiltinModelEnum.IPAM_SUBNET, SubnetBuiltinAttributes.CIDR, cidr),
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
if cis:
|
||||||
|
subnet = cis[0]
|
||||||
|
subnet_id = subnet['_id']
|
||||||
|
else:
|
||||||
|
return abort(400, ErrFormat.ipam_address_model_not_found)
|
||||||
|
|
||||||
|
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id))):
|
||||||
|
cis = self._get_cis(ips)
|
||||||
|
ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis}
|
||||||
|
|
||||||
|
ci_ids = []
|
||||||
|
for ip in ips:
|
||||||
|
kwargs['name'] = ip
|
||||||
|
kwargs[IPAddressBuiltinAttributes.IP] = ip
|
||||||
|
if ip not in ip2ci:
|
||||||
|
ci_id = CIManager.add(self.type_id, _sync=True, **kwargs)
|
||||||
|
else:
|
||||||
|
ci_id = ip2ci[ip]['_id']
|
||||||
|
CIManager().update(ci_id, _sync=True, **kwargs)
|
||||||
|
ci_ids.append(ci_id)
|
||||||
|
|
||||||
|
self._add_relation(subnet_id, ci_id)
|
||||||
|
|
||||||
|
if ips and IPAddressBuiltinAttributes.ASSIGN_STATUS in kwargs:
|
||||||
|
self._update_subnet_count(subnet_id, True)
|
||||||
|
|
||||||
|
if ips and IPAddressBuiltinAttributes.IS_USED in kwargs:
|
||||||
|
q = "{}:true".format(IPAddressBuiltinAttributes.IS_USED)
|
||||||
|
cur_used_ids = RelationSearch([subnet_id], level=[1], query=q).search(only_ids=True)
|
||||||
|
for _id in set(cur_used_ids) - set(ci_ids):
|
||||||
|
CIManager().update(_id, **{IPAddressBuiltinAttributes.IS_USED: False})
|
||||||
|
|
||||||
|
self._update_subnet_count(subnet_id, False, used_count=len(ips))
|
||||||
|
|
||||||
|
if kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) in (
|
||||||
|
IPAddressAssignStatus.ASSIGNED, IPAddressAssignStatus.RESERVED):
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.ASSIGN_ADDRESS,
|
||||||
|
cidr=subnet.get(SubnetBuiltinAttributes.CIDR),
|
||||||
|
description=" | ".join(ips))
|
||||||
|
|
||||||
|
elif kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) == IPAddressAssignStatus.UNASSIGNED:
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.REVOKE_ADDRESS,
|
||||||
|
cidr=subnet.get(SubnetBuiltinAttributes.CIDR),
|
||||||
|
description=" | ".join(ips))
|
35
cmdb-api/api/lib/cmdb/ipam/const.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from api.lib.utils import BaseEnum
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressAssignStatus(BaseEnum):
|
||||||
|
ASSIGNED = 0
|
||||||
|
UNASSIGNED = 1
|
||||||
|
RESERVED = 2
|
||||||
|
|
||||||
|
|
||||||
|
class OperateTypeEnum(BaseEnum):
|
||||||
|
ADD_SCOPE = "0"
|
||||||
|
UPDATE_SCOPE = "1"
|
||||||
|
DELETE_SCOPE = "2"
|
||||||
|
ADD_SUBNET = "3"
|
||||||
|
UPDATE_SUBNET = "4"
|
||||||
|
DELETE_SUBNET = "5"
|
||||||
|
ASSIGN_ADDRESS = "6"
|
||||||
|
REVOKE_ADDRESS = "7"
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetBuiltinAttributes(BaseEnum):
|
||||||
|
NAME = 'name'
|
||||||
|
CIDR = 'cidr'
|
||||||
|
HOSTS_COUNT = 'hosts_count'
|
||||||
|
ASSIGN_COUNT = 'assign_count'
|
||||||
|
USED_COUNT = 'used_count'
|
||||||
|
FREE_COUNT = 'free_count'
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressBuiltinAttributes(BaseEnum):
|
||||||
|
IP = 'ip'
|
||||||
|
ASSIGN_STATUS = 'assign_status' # enum: 0 - assigned 1 - unassigned 2 - reserved
|
||||||
|
IS_USED = 'is_used' # bool
|
61
cmdb-api/api/lib/cmdb/ipam/history.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes
|
||||||
|
from api.lib.mixin import DBMixin
|
||||||
|
from api.models.cmdb import IPAMOperationHistory
|
||||||
|
from api.models.cmdb import IPAMSubnetScan
|
||||||
|
from api.models.cmdb import IPAMSubnetScanHistory
|
||||||
|
|
||||||
|
|
||||||
|
class OperateHistoryManager(DBMixin):
|
||||||
|
cls = IPAMOperationHistory
|
||||||
|
|
||||||
|
def _can_add(self, **kwargs):
|
||||||
|
kwargs['uid'] = current_user.uid
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def _can_update(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _can_delete(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScanHistoryManager(DBMixin):
|
||||||
|
cls = IPAMSubnetScanHistory
|
||||||
|
|
||||||
|
def _can_add(self, **kwargs):
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def add(self, **kwargs):
|
||||||
|
kwargs.pop('_key', None)
|
||||||
|
kwargs.pop('_secret', None)
|
||||||
|
ci_id = kwargs.pop('ci_id', None)
|
||||||
|
|
||||||
|
existed = self.cls.get_by(exec_id=kwargs['exec_id'], first=True, to_dict=False)
|
||||||
|
if existed is None:
|
||||||
|
self.cls.create(**kwargs)
|
||||||
|
else:
|
||||||
|
existed.update(**kwargs)
|
||||||
|
|
||||||
|
if kwargs.get('ips'):
|
||||||
|
from api.lib.cmdb.ipam.address import IpAddressManager
|
||||||
|
IpAddressManager().assign_ips(kwargs['ips'], None, kwargs.get('cidr'),
|
||||||
|
**{IPAddressBuiltinAttributes.IS_USED: 1})
|
||||||
|
|
||||||
|
scan_rule = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False)
|
||||||
|
if scan_rule is not None:
|
||||||
|
scan_rule.update(last_scan_time=kwargs.get('start_at'))
|
||||||
|
|
||||||
|
for i in self.cls.get_by(subnet_scan_id=kwargs.get('subnet_scan_id'), only_query=True).order_by(
|
||||||
|
self.cls.id.desc()).offset(100):
|
||||||
|
i.delete()
|
||||||
|
|
||||||
|
def _can_update(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _can_delete(self, **kwargs):
|
||||||
|
pass
|
104
cmdb-api/api/lib/cmdb/ipam/stats.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.extensions import rd
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CIRelation
|
||||||
|
from api.models.cmdb import IPAMSubnetScan
|
||||||
|
|
||||||
|
|
||||||
|
class Stats(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.address_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) or abort(
|
||||||
|
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||||
|
|
||||||
|
self.address_type_id = self.address_type.id
|
||||||
|
|
||||||
|
self.subnet_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) or abort(
|
||||||
|
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||||
|
|
||||||
|
self.subnet_type_id = self.subnet_type.id
|
||||||
|
|
||||||
|
def leaf_nodes(self, parent_id):
|
||||||
|
if str(parent_id) == '0': # all
|
||||||
|
ci_ids = [i.id for i in CI.get_by(type_id=self.subnet_type_id, to_dict=False)]
|
||||||
|
has_children_ci_ids = [i.first_ci_id for i in CIRelation.get_by(
|
||||||
|
only_query=True).join(CI, CIRelation.second_ci_id == CI.id).filter(
|
||||||
|
CIRelation.first_ci_id.in_(ci_ids)).filter(CI.type_id == self.subnet_type_id)]
|
||||||
|
|
||||||
|
return list(set(ci_ids) - set(has_children_ci_ids))
|
||||||
|
|
||||||
|
else:
|
||||||
|
_type = CIManager().get_by_id(parent_id)
|
||||||
|
if not _type:
|
||||||
|
return abort(404, ErrFormat.ipam_subnet_not_found)
|
||||||
|
key = [(str(parent_id), _type.type_id)]
|
||||||
|
result = []
|
||||||
|
while True:
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(
|
||||||
|
[i[0] for i in key], REDIS_PREFIX_CI_RELATION) or []]]
|
||||||
|
|
||||||
|
for idx, i in enumerate(res):
|
||||||
|
if (not i or list(i)[0][1] == self.address_type_id) and key[idx][1] == self.subnet_type_id:
|
||||||
|
result.append(int(key[idx][0]))
|
||||||
|
|
||||||
|
res = [j for i in res for j in i] # [(id, type_id)]
|
||||||
|
|
||||||
|
if not res:
|
||||||
|
return result
|
||||||
|
|
||||||
|
key = res
|
||||||
|
|
||||||
|
def statistic_subnets(self, subnet_ids):
|
||||||
|
if subnet_ids:
|
||||||
|
response, _, _, _, _, _ = SearchFromDB(
|
||||||
|
"_type:{}".format(self.subnet_type_id),
|
||||||
|
ci_ids=subnet_ids,
|
||||||
|
count=1000000,
|
||||||
|
parent_node_perm_passed=True,
|
||||||
|
).search()
|
||||||
|
else:
|
||||||
|
response = []
|
||||||
|
|
||||||
|
scans = IPAMSubnetScan.get_by(only_query=True).filter(IPAMSubnetScan.ci_id.in_(list(map(int, subnet_ids))))
|
||||||
|
id2scan = {i.ci_id: i for i in scans}
|
||||||
|
|
||||||
|
address_num, address_free_num, address_assign_num, address_used_num = 0, 0, 0, 0
|
||||||
|
for subnet in response:
|
||||||
|
address_num += (subnet.get('hosts_count') or 0)
|
||||||
|
address_free_num += (subnet.get('free_count') or 0)
|
||||||
|
address_assign_num += (subnet.get('assign_count') or 0)
|
||||||
|
address_used_num += (subnet.get('used_count') or 0)
|
||||||
|
|
||||||
|
if id2scan.get(subnet['_id']):
|
||||||
|
subnet['scan_enabled'] = id2scan[subnet['_id']].scan_enabled
|
||||||
|
subnet['last_scan_time'] = id2scan[subnet['_id']].last_scan_time
|
||||||
|
else:
|
||||||
|
subnet['scan_enabled'] = False
|
||||||
|
subnet['last_scan_time'] = None
|
||||||
|
|
||||||
|
return response, address_num, address_free_num, address_assign_num, address_used_num
|
||||||
|
|
||||||
|
def summary(self, parent_id):
|
||||||
|
subnet_ids = self.leaf_nodes(parent_id)
|
||||||
|
|
||||||
|
subnets, address_num, address_free_num, address_assign_num, address_used_num = (
|
||||||
|
self.statistic_subnets(subnet_ids))
|
||||||
|
|
||||||
|
return dict(subnet_num=len(subnets),
|
||||||
|
address_num=address_num,
|
||||||
|
address_free_num=address_free_num,
|
||||||
|
address_assign_num=address_assign_num,
|
||||||
|
address_unassign_num=address_num - address_assign_num,
|
||||||
|
address_used_num=address_used_num,
|
||||||
|
address_used_free_num=address_num - address_used_num,
|
||||||
|
subnets=subnets)
|
355
cmdb-api/api/lib/cmdb/ipam/subnet.py
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import ipaddress
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
|
from api.lib.cmdb.const import BuiltinModelEnum
|
||||||
|
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||||
|
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||||
|
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||||
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||||
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CIRelation
|
||||||
|
from api.models.cmdb import IPAMSubnetScan
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) or abort(
|
||||||
|
404, ErrFormat.ipam_subnet_model_not_found.format(BuiltinModelEnum.IPAM_SUBNET))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
def scan_rules(self, oneagent_id, last_update_at=None):
|
||||||
|
result = []
|
||||||
|
rules = IPAMSubnetScan.get_by(agent_id=oneagent_id, to_dict=True)
|
||||||
|
ci_ids = [i['ci_id'] for i in rules]
|
||||||
|
if ci_ids:
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||||
|
ci_ids=list(ci_ids),
|
||||||
|
count=1000000,
|
||||||
|
fl=[SubnetBuiltinAttributes.CIDR],
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
id2ci = {i['_id']: i for i in response}
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
if rule['ci_id'] in id2ci:
|
||||||
|
rule[SubnetBuiltinAttributes.CIDR] = id2ci[rule['ci_id']][SubnetBuiltinAttributes.CIDR]
|
||||||
|
result.append(rule)
|
||||||
|
|
||||||
|
new_last_update_at = ""
|
||||||
|
for i in result:
|
||||||
|
__last_update_at = max([i['rule_updated_at'] or "", i['created_at'] or ""])
|
||||||
|
if new_last_update_at < __last_update_at:
|
||||||
|
new_last_update_at = __last_update_at
|
||||||
|
|
||||||
|
if not last_update_at or new_last_update_at > last_update_at:
|
||||||
|
return result, new_last_update_at
|
||||||
|
else:
|
||||||
|
return [], new_last_update_at
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_hosts(cidr):
|
||||||
|
try:
|
||||||
|
return list(map(str, ipaddress.ip_network(cidr).hosts()))
|
||||||
|
except ValueError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_by_id(self, subnet_id):
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||||
|
ci_ids=[subnet_id],
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
scan_rule = IPAMSubnetScan.get_by(ci_id=subnet_id, first=True, to_dict=True)
|
||||||
|
if scan_rule and response:
|
||||||
|
scan_rule.update(response[0])
|
||||||
|
|
||||||
|
return scan_rule
|
||||||
|
|
||||||
|
def tree_view(self):
|
||||||
|
scope = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE)
|
||||||
|
ci_types = scope and [scope.id, self.type_id] or [self.type_id]
|
||||||
|
|
||||||
|
relations = defaultdict(set)
|
||||||
|
ids = set()
|
||||||
|
has_parent_ids = set()
|
||||||
|
for i in CIRelation.get_by(only_query=True).join(
|
||||||
|
CI, CI.id == CIRelation.first_ci_id).filter(CI.type_id.in_(ci_types)):
|
||||||
|
relations[i.first_ci_id].add(i.second_ci_id)
|
||||||
|
ids.add(i.first_ci_id)
|
||||||
|
ids.add(i.second_ci_id)
|
||||||
|
has_parent_ids.add(i.second_ci_id)
|
||||||
|
for i in CIRelation.get_by(only_query=True).join(
|
||||||
|
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id.in_(ci_types)):
|
||||||
|
relations[i.first_ci_id].add(i.second_ci_id)
|
||||||
|
ids.add(i.first_ci_id)
|
||||||
|
ids.add(i.second_ci_id)
|
||||||
|
has_parent_ids.add(i.second_ci_id)
|
||||||
|
|
||||||
|
for i in CI.get_by(only_query=True).filter(CI.type_id.in_(ci_types)):
|
||||||
|
ids.add(i.id)
|
||||||
|
|
||||||
|
for _id in ids:
|
||||||
|
if _id not in has_parent_ids:
|
||||||
|
relations[None].add(_id)
|
||||||
|
|
||||||
|
type2name = dict()
|
||||||
|
type2name[self.type_id] = AttributeCache.get(self.ci_type.show_id or self.ci_type.unique_id).name
|
||||||
|
|
||||||
|
fl = [type2name[self.type_id]]
|
||||||
|
if scope:
|
||||||
|
type2name[scope.id] = AttributeCache.get(scope.show_id or scope.unique_id).name
|
||||||
|
fl.append(type2name[scope.id])
|
||||||
|
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:({})".format(";".join(map(str, ci_types))),
|
||||||
|
ci_ids=list(ids),
|
||||||
|
count=1000000,
|
||||||
|
fl=list(set(fl + [SubnetBuiltinAttributes.CIDR])),
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
id2ci = {i['_id']: i for i in response}
|
||||||
|
|
||||||
|
def _build_tree(_tree, parent_id=None):
|
||||||
|
tree = []
|
||||||
|
for child_id in _tree.get(parent_id, []):
|
||||||
|
children = sorted(_build_tree(_tree, child_id), key=lambda x: x['_id'])
|
||||||
|
if not id2ci.get(child_id):
|
||||||
|
continue
|
||||||
|
tree.append({'children': children, **id2ci[child_id]})
|
||||||
|
return tree
|
||||||
|
|
||||||
|
result = sorted(_build_tree(relations), key=lambda x: x['_id'])
|
||||||
|
|
||||||
|
return result, type2name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_valid_cidr(cidr):
|
||||||
|
try:
|
||||||
|
cidr = ipaddress.ip_network(cidr)
|
||||||
|
if not (8 <= cidr.prefixlen <= 31):
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
return str(cidr)
|
||||||
|
except ValueError:
|
||||||
|
return abort(400, ErrFormat.ipam_cidr_invalid_notation.format(cidr))
|
||||||
|
|
||||||
|
def _check_root_node_is_overlapping(self, cidr, _id=None):
|
||||||
|
none_root_nodes = [i.id for i in CI.get_by(only_query=True).join(
|
||||||
|
CIRelation, CIRelation.second_ci_id == CI.id).filter(CI.type_id == self.type_id)]
|
||||||
|
all_nodes = [i.id for i in CI.get_by(type_id=self.type_id, to_dict=False, fl=['id'])]
|
||||||
|
|
||||||
|
root_nodes = set(all_nodes) - set(none_root_nodes) - set(_id and [_id] or [])
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||||
|
ci_ids=list(root_nodes),
|
||||||
|
count=1000000,
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
|
||||||
|
cur_subnet = ipaddress.ip_network(cidr)
|
||||||
|
for item in response:
|
||||||
|
if item['_id'] == _id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))):
|
||||||
|
return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR)))
|
||||||
|
|
||||||
|
return cidr
|
||||||
|
|
||||||
|
def _check_child_node_is_overlapping(self, parent_id, cidr, _id=None):
|
||||||
|
child_nodes = [i.second_ci_id for i in CIRelation.get_by(
|
||||||
|
first_ci_id=parent_id, to_dict=False, fl=['second_ci_id']) if i.second_ci_id != _id]
|
||||||
|
if not child_nodes:
|
||||||
|
return
|
||||||
|
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||||
|
ci_ids=list(child_nodes),
|
||||||
|
count=1000000,
|
||||||
|
parent_node_perm_passed=True).search()
|
||||||
|
|
||||||
|
cur_subnet = ipaddress.ip_network(cidr)
|
||||||
|
for item in response:
|
||||||
|
if item['_id'] == _id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))):
|
||||||
|
return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR)))
|
||||||
|
|
||||||
|
def validate_cidr(self, parent_id, cidr, _id=None):
|
||||||
|
cidr = self._is_valid_cidr(cidr)
|
||||||
|
|
||||||
|
if not parent_id:
|
||||||
|
return self._check_root_node_is_overlapping(cidr, _id)
|
||||||
|
|
||||||
|
parent_subnet = CIManager().get_ci_by_id(parent_id, need_children=False)
|
||||||
|
if parent_subnet['ci_type'] == BuiltinModelEnum.IPAM_SUBNET:
|
||||||
|
if parent_subnet.get(SubnetBuiltinAttributes.CIDR):
|
||||||
|
prefix = int(cidr.split('/')[1])
|
||||||
|
if int(parent_subnet[SubnetBuiltinAttributes.CIDR].split('/')[1]) >= prefix:
|
||||||
|
return abort(400, ErrFormat.ipam_subnet_prefix_length_invalid.format(prefix))
|
||||||
|
|
||||||
|
valid_subnets = [str(i) for i in
|
||||||
|
ipaddress.ip_network(parent_subnet[SubnetBuiltinAttributes.CIDR]).subnets(
|
||||||
|
new_prefix=prefix)]
|
||||||
|
if cidr not in valid_subnets:
|
||||||
|
return abort(400, ErrFormat.ipam_cidr_invalid_subnet.format(cidr, valid_subnets))
|
||||||
|
else:
|
||||||
|
return abort(400, ErrFormat.ipam_parent_subnet_node_cidr_cannot_empty)
|
||||||
|
|
||||||
|
self._check_child_node_is_overlapping(parent_id, cidr, _id)
|
||||||
|
|
||||||
|
return cidr
|
||||||
|
|
||||||
|
def _add_subnet(self, cidr, **kwargs):
|
||||||
|
kwargs[SubnetBuiltinAttributes.HOSTS_COUNT] = len(list(ipaddress.ip_network(cidr).hosts()))
|
||||||
|
kwargs[SubnetBuiltinAttributes.USED_COUNT] = 0
|
||||||
|
kwargs[SubnetBuiltinAttributes.ASSIGN_COUNT] = 0
|
||||||
|
kwargs[SubnetBuiltinAttributes.FREE_COUNT] = kwargs[SubnetBuiltinAttributes.HOSTS_COUNT]
|
||||||
|
|
||||||
|
return CIManager().add(self.type_id, cidr=cidr, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_scan_rule(ci_id, agent_id, cron, scan_enabled=True):
|
||||||
|
IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_relation(parent_id, child_id):
|
||||||
|
if not parent_id or not child_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
CIRelationManager().add(parent_id, child_id, valid=False)
|
||||||
|
|
||||||
|
def add(self, cidr, parent_id, agent_id, cron, scan_enabled=True, **kwargs):
|
||||||
|
cidr = self.validate_cidr(parent_id, cidr)
|
||||||
|
|
||||||
|
ci_id = self._add_subnet(cidr, **kwargs)
|
||||||
|
|
||||||
|
self._add_scan_rule(ci_id, agent_id, cron, scan_enabled)
|
||||||
|
|
||||||
|
self._add_relation(parent_id, ci_id)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SUBNET,
|
||||||
|
cidr=cidr,
|
||||||
|
description=cidr)
|
||||||
|
|
||||||
|
return ci_id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_subnet(_id, **kwargs):
|
||||||
|
return CIManager().update(_id, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_scan_rule(ci_id, agent_id, cron, scan_enabled=True):
|
||||||
|
existed = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False)
|
||||||
|
if existed is not None:
|
||||||
|
existed.update(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled,
|
||||||
|
rule_updated_at=datetime.datetime.now())
|
||||||
|
else:
|
||||||
|
IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled)
|
||||||
|
|
||||||
|
def update(self, _id, **kwargs):
|
||||||
|
kwargs[SubnetBuiltinAttributes.CIDR] = self.validate_cidr(kwargs.pop('parent_id', None),
|
||||||
|
kwargs.get(SubnetBuiltinAttributes.CIDR), _id)
|
||||||
|
|
||||||
|
agent_id = kwargs.pop('agent_id', None)
|
||||||
|
cron = kwargs.pop('cron', None)
|
||||||
|
scan_enabled = kwargs.pop('scan_enabled', True)
|
||||||
|
|
||||||
|
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||||
|
|
||||||
|
self._update_subnet(_id, **kwargs)
|
||||||
|
|
||||||
|
self._update_scan_rule(_id, agent_id, cron, scan_enabled)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SUBNET,
|
||||||
|
cidr=cur.get(SubnetBuiltinAttributes.CIDR),
|
||||||
|
description="{} -> {}".format(cur.get(SubnetBuiltinAttributes.CIDR),
|
||||||
|
kwargs.get(SubnetBuiltinAttributes.CIDR)))
|
||||||
|
|
||||||
|
return _id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, _id):
|
||||||
|
if CIRelation.get_by(only_query=True).filter(CIRelation.first_ci_id == _id).first():
|
||||||
|
return abort(400, ErrFormat.ipam_subnet_cannot_delete)
|
||||||
|
|
||||||
|
existed = IPAMSubnetScan.get_by(ci_id=_id, first=True, to_dict=False)
|
||||||
|
existed and existed.delete()
|
||||||
|
|
||||||
|
delete_ci_ids = []
|
||||||
|
for i in CIRelation.get_by(first_ci_id=_id, to_dict=False):
|
||||||
|
delete_ci_ids.append(i.second_ci_id)
|
||||||
|
i.delete()
|
||||||
|
|
||||||
|
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||||
|
|
||||||
|
CIManager().delete(_id)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SUBNET,
|
||||||
|
cidr=cur.get(SubnetBuiltinAttributes.CIDR),
|
||||||
|
description=cur.get(SubnetBuiltinAttributes.CIDR))
|
||||||
|
|
||||||
|
# batch_delete_ci.apply_async(args=(delete_ci_ids,))
|
||||||
|
|
||||||
|
return _id
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetScopeManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE)
|
||||||
|
not self.ci_type and abort(400, ErrFormat.ipam_subnet_model_not_found.format(
|
||||||
|
BuiltinModelEnum.IPAM_SCOPE))
|
||||||
|
|
||||||
|
self.type_id = self.ci_type.id
|
||||||
|
|
||||||
|
def _add_scope(self, name):
|
||||||
|
return CIManager().add(self.type_id, name=name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_relation(parent_id, child_id):
|
||||||
|
if not parent_id or not child_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
CIRelationManager().add(parent_id, child_id, valid=False)
|
||||||
|
|
||||||
|
def add(self, parent_id, name):
|
||||||
|
ci_id = self._add_scope(name)
|
||||||
|
|
||||||
|
self._add_relation(parent_id, ci_id)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SCOPE,
|
||||||
|
description=name)
|
||||||
|
|
||||||
|
return ci_id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_scope(_id, name):
|
||||||
|
return CIManager().update(_id, name=name)
|
||||||
|
|
||||||
|
def update(self, _id, name):
|
||||||
|
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||||
|
|
||||||
|
res = self._update_scope(_id, name)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SCOPE,
|
||||||
|
description="{} -> {}".format(cur.get('name'), name))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete(_id):
|
||||||
|
if CIRelation.get_by(first_ci_id=_id, first=True, to_dict=False):
|
||||||
|
return abort(400, ErrFormat.ipam_scope_cannot_delete)
|
||||||
|
|
||||||
|
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||||
|
|
||||||
|
CIManager().delete(_id)
|
||||||
|
|
||||||
|
OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SCOPE,
|
||||||
|
description=cur.get('name'))
|
||||||
|
|
||||||
|
return _id
|
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
import redis_lock
|
import redis_lock
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -10,6 +9,7 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
|
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.mixin import DBMixin
|
from api.lib.mixin import DBMixin
|
||||||
@@ -27,7 +27,7 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result = {}
|
result = {}
|
||||||
for i in res:
|
for i in res:
|
||||||
if i['attr_filter']:
|
if i['attr_filter']:
|
||||||
i['attr_filter'] = i['attr_filter'].split(',')
|
i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys())
|
||||||
|
|
||||||
if i['rid'] not in result:
|
if i['rid'] not in result:
|
||||||
result[i['rid']] = i
|
result[i['rid']] = i
|
||||||
@@ -62,7 +62,7 @@ class CIFilterPermsCRUD(DBMixin):
|
|||||||
result = {}
|
result = {}
|
||||||
for i in res:
|
for i in res:
|
||||||
if i['attr_filter']:
|
if i['attr_filter']:
|
||||||
i['attr_filter'] = i['attr_filter'].split(',')
|
i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys())
|
||||||
|
|
||||||
if i['type_id'] not in result:
|
if i['type_id'] not in result:
|
||||||
result[i['type_id']] = i
|
result[i['type_id']] = i
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
@@ -17,15 +16,16 @@ from api.lib.cmdb.cache import CITypeAttributesCache
|
|||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.cache import CMDBCounterCache
|
from api.lib.cmdb.cache import CMDBCounterCache
|
||||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||||
|
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
from api.lib.cmdb.const import RoleEnum
|
||||||
|
from api.lib.cmdb.const import SysComputedAttributes
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.models.cmdb import CITypeAttribute
|
|
||||||
from api.models.cmdb import CITypeGroup
|
from api.models.cmdb import CITypeGroup
|
||||||
from api.models.cmdb import CITypeGroupItem
|
from api.models.cmdb import CITypeGroupItem
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
@@ -133,21 +133,24 @@ class PreferenceManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_show_attributes(type_id):
|
def get_show_attributes(type_id):
|
||||||
|
_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found)
|
||||||
|
type_id = _type and _type.id
|
||||||
|
|
||||||
if not isinstance(type_id, six.integer_types):
|
if not isinstance(type_id, six.integer_types):
|
||||||
_type = CITypeCache.get(type_id)
|
_type = CITypeCache.get(type_id)
|
||||||
type_id = _type and _type.id
|
type_id = _type and _type.id
|
||||||
|
|
||||||
attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join(
|
attrs = PreferenceShowAttributes.get_by(uid=current_user.uid, type_id=type_id, to_dict=False)
|
||||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
|
||||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
|
||||||
PreferenceShowAttributes.type_id == type_id).filter(
|
|
||||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
|
||||||
CITypeAttribute.attr_id).all()
|
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
for i in sorted(attrs, key=lambda x: x.order):
|
||||||
item = i.PreferenceShowAttributes.attr.to_dict()
|
if i.attr_id:
|
||||||
item.update(dict(is_fixed=i.PreferenceShowAttributes.is_fixed))
|
item = i.attr.to_dict()
|
||||||
|
elif i.builtin_attr:
|
||||||
|
item = dict(name=i.builtin_attr, alias=BUILTIN_ATTRIBUTES[i.builtin_attr])
|
||||||
|
else:
|
||||||
|
item = dict(name="", alias="")
|
||||||
|
item.update(dict(is_fixed=i.is_fixed))
|
||||||
result.append(item)
|
result.append(item)
|
||||||
|
|
||||||
is_subscribed = True
|
is_subscribed = True
|
||||||
@@ -156,13 +159,23 @@ class PreferenceManager(object):
|
|||||||
choice_web_hook_parse=False,
|
choice_web_hook_parse=False,
|
||||||
choice_other_parse=False)
|
choice_other_parse=False)
|
||||||
result = [i for i in result if i['default_show']]
|
result = [i for i in result if i['default_show']]
|
||||||
|
|
||||||
|
for i in BUILTIN_ATTRIBUTES:
|
||||||
|
result.append(dict(name=i, alias=BUILTIN_ATTRIBUTES[i]))
|
||||||
|
|
||||||
is_subscribed = False
|
is_subscribed = False
|
||||||
|
|
||||||
for i in result:
|
for i in result:
|
||||||
if i["is_choice"]:
|
if i.get("is_choice"):
|
||||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||||
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
|
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
|
||||||
|
|
||||||
|
if (_type.name in SysComputedAttributes.type2attr and
|
||||||
|
i['name'] in SysComputedAttributes.type2attr[_type.name]):
|
||||||
|
i['sys_computed'] = True
|
||||||
|
else:
|
||||||
|
i['sys_computed'] = False
|
||||||
|
|
||||||
return is_subscribed, result
|
return is_subscribed, result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -173,24 +186,34 @@ class PreferenceManager(object):
|
|||||||
_attr, is_fixed = x
|
_attr, is_fixed = x
|
||||||
else:
|
else:
|
||||||
_attr, is_fixed = x, False
|
_attr, is_fixed = x, False
|
||||||
attr = AttributeCache.get(_attr) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
|
||||||
|
if _attr in BUILTIN_ATTRIBUTES:
|
||||||
|
attr = None
|
||||||
|
builtin_attr = _attr
|
||||||
|
else:
|
||||||
|
attr = AttributeCache.get(_attr) or abort(
|
||||||
|
404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
||||||
|
builtin_attr = None
|
||||||
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
||||||
uid=current_user.uid,
|
uid=current_user.uid,
|
||||||
attr_id=attr.id,
|
attr_id=attr and attr.id,
|
||||||
|
builtin_attr=builtin_attr,
|
||||||
first=True,
|
first=True,
|
||||||
to_dict=False)
|
to_dict=False)
|
||||||
if existed is None:
|
if existed is None:
|
||||||
PreferenceShowAttributes.create(type_id=type_id,
|
PreferenceShowAttributes.create(type_id=type_id,
|
||||||
uid=current_user.uid,
|
uid=current_user.uid,
|
||||||
attr_id=attr.id,
|
attr_id=attr and attr.id,
|
||||||
|
builtin_attr=builtin_attr,
|
||||||
order=order,
|
order=order,
|
||||||
is_fixed=is_fixed)
|
is_fixed=is_fixed)
|
||||||
else:
|
else:
|
||||||
existed.update(order=order, is_fixed=is_fixed)
|
existed.update(order=order, is_fixed=is_fixed)
|
||||||
|
|
||||||
attr_dict = {int(i[0]) if isinstance(i, list) else int(i): j for i, j in attr_order}
|
attr_dict = {(int(i[0]) if i[0].isdigit() else i[0]) if isinstance(i, list) else
|
||||||
|
(int(i) if i.isdigit() else i): j for i, j in attr_order}
|
||||||
for i in existed_all:
|
for i in existed_all:
|
||||||
if i.attr_id not in attr_dict:
|
if (i.attr_id and i.attr_id not in attr_dict) or (i.builtin_attr and i.builtin_attr not in attr_dict):
|
||||||
i.soft_delete()
|
i.soft_delete()
|
||||||
|
|
||||||
if not existed_all and attr_order:
|
if not existed_all and attr_order:
|
||||||
@@ -384,7 +407,7 @@ class PreferenceManager(object):
|
|||||||
def add_search_option(**kwargs):
|
def add_search_option(**kwargs):
|
||||||
kwargs['uid'] = current_user.uid
|
kwargs['uid'] = current_user.uid
|
||||||
|
|
||||||
if kwargs['name'] in ('__recent__', '__favor__'):
|
if kwargs['name'] in ('__recent__', '__favor__', '__relation_favor__'):
|
||||||
if kwargs['name'] == '__recent__':
|
if kwargs['name'] == '__recent__':
|
||||||
for i in PreferenceSearchOption.get_by(
|
for i in PreferenceSearchOption.get_by(
|
||||||
only_query=True, name=kwargs['name'], uid=current_user.uid).order_by(
|
only_query=True, name=kwargs['name'], uid=current_user.uid).order_by(
|
||||||
|
@@ -154,3 +154,23 @@ class ErrFormat(CommonErrFormat):
|
|||||||
topology_group_exists = _l("Topology group {} 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")
|
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||||
|
|
||||||
|
relation_path_search_src_target_required = _l("Both the source model and the target model must be selected")
|
||||||
|
|
||||||
|
builtin_type_cannot_update_name = _l("The names of built-in models cannot be changed")
|
||||||
|
# # IPAM
|
||||||
|
ipam_subnet_model_not_found = _l("The subnet model {} does not exist")
|
||||||
|
ipam_address_model_not_found = _l("The IP Address model {} does not exist")
|
||||||
|
ipam_cidr_invalid_notation = _l("CIDR {} is an invalid notation")
|
||||||
|
ipam_cidr_invalid_subnet = _l("Invalid CIDR: {}, available subnets: {}")
|
||||||
|
ipam_subnet_prefix_length_invalid = _l("Invalid subnet prefix length: {}")
|
||||||
|
ipam_parent_subnet_node_cidr_cannot_empty = _l("parent node cidr must be required")
|
||||||
|
ipam_subnet_overlapped = _l("{} and {} overlap")
|
||||||
|
ipam_subnet_cannot_delete = _l("Cannot delete because child nodes exist")
|
||||||
|
ipam_subnet_not_found = _l("Subnet is not found")
|
||||||
|
ipam_scope_cannot_delete = _l("Cannot delete because child nodes exist")
|
||||||
|
|
||||||
|
# # DCIM
|
||||||
|
dcim_builtin_model_not_found = _l("The dcim model {} does not exist")
|
||||||
|
dcim_rack_u_slot_invalid = _l("Irregularities in Rack Units")
|
||||||
|
dcim_rack_u_count_invalid = _l("The device's position is greater than the rack unit height")
|
||||||
|
@@ -4,8 +4,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import six
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
@@ -15,6 +15,7 @@ from api.extensions import db
|
|||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RetKey
|
from api.lib.cmdb.const import RetKey
|
||||||
@@ -66,6 +67,7 @@ class Search(object):
|
|||||||
self.use_id_filter = use_id_filter
|
self.use_id_filter = use_id_filter
|
||||||
self.use_ci_filter = use_ci_filter
|
self.use_ci_filter = use_ci_filter
|
||||||
self.only_ids = only_ids
|
self.only_ids = only_ids
|
||||||
|
self.multi_type_has_ci_filter = False
|
||||||
|
|
||||||
self.valid_type_names = []
|
self.valid_type_names = []
|
||||||
self.type2filter_perms = dict()
|
self.type2filter_perms = dict()
|
||||||
@@ -104,35 +106,56 @@ class Search(object):
|
|||||||
else:
|
else:
|
||||||
raise SearchError(ErrFormat.attribute_not_found.format(key))
|
raise SearchError(ErrFormat.attribute_not_found.format(key))
|
||||||
|
|
||||||
def _type_query_handler(self, v, queries):
|
def _type_query_handler(self, v, queries, is_sub=False):
|
||||||
new_v = v[1:-1].split(";") if v.startswith("(") and v.endswith(")") else [v]
|
new_v = v[1:-1].split(";") if v.startswith("(") and v.endswith(")") else [v]
|
||||||
|
type_num = len(new_v)
|
||||||
|
type_id_list = []
|
||||||
for _v in new_v:
|
for _v in new_v:
|
||||||
ci_type = CITypeCache.get(_v)
|
ci_type = CITypeCache.get(_v)
|
||||||
|
|
||||||
if len(new_v) == 1 and not self.sort and ci_type and ci_type.default_order_attr:
|
if type_num == 1 and not self.sort and ci_type and ci_type.default_order_attr:
|
||||||
self.sort = ci_type.default_order_attr
|
self.sort = ci_type.default_order_attr
|
||||||
|
|
||||||
if ci_type is not None:
|
if ci_type is not None:
|
||||||
if self.valid_type_names == "ALL" or ci_type.name in self.valid_type_names:
|
if self.valid_type_names == "ALL" or ci_type.name in self.valid_type_names:
|
||||||
|
if not is_sub:
|
||||||
self.type_id_list.append(str(ci_type.id))
|
self.type_id_list.append(str(ci_type.id))
|
||||||
if ci_type.id in self.type2filter_perms:
|
type_id_list.append(str(ci_type.id))
|
||||||
|
if ci_type.id in self.type2filter_perms and not is_sub:
|
||||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||||
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||||
sub = []
|
sub = []
|
||||||
ci_filter = Template(ci_filter).render(user=current_user)
|
ci_filter = Template(ci_filter).render(user=current_user)
|
||||||
for i in ci_filter.split(','):
|
for i in ci_filter.split(','):
|
||||||
|
if type_num == 1:
|
||||||
if i.startswith("~") and not sub:
|
if i.startswith("~") and not sub:
|
||||||
queries.append(i)
|
queries.append(i)
|
||||||
else:
|
else:
|
||||||
sub.append(i)
|
sub.append(i)
|
||||||
|
else:
|
||||||
|
sub.append(i)
|
||||||
if sub:
|
if sub:
|
||||||
|
if type_num == 1:
|
||||||
queries.append(dict(operator="&", queries=sub))
|
queries.append(dict(operator="&", queries=sub))
|
||||||
|
else:
|
||||||
|
if str(ci_type.id) in self.type_id_list:
|
||||||
|
self.type_id_list.remove(str(ci_type.id))
|
||||||
|
type_id_list.remove(str(ci_type.id))
|
||||||
|
sub.extend([i for i in queries[1:] if isinstance(i, six.string_types)])
|
||||||
|
|
||||||
|
sub.insert(0, "_type:{}".format(ci_type.id))
|
||||||
|
queries.append(dict(operator="|", queries=sub))
|
||||||
|
self.multi_type_has_ci_filter = True
|
||||||
if self.type2filter_perms[ci_type.id].get('attr_filter'):
|
if self.type2filter_perms[ci_type.id].get('attr_filter'):
|
||||||
|
if type_num == 1:
|
||||||
if not self.fl:
|
if not self.fl:
|
||||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
else:
|
else:
|
||||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
|
else:
|
||||||
|
self.fl = self.fl or {}
|
||||||
|
if not self.fl or isinstance(self.fl, dict):
|
||||||
|
self.fl[ci_type.id] = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||||
|
|
||||||
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
||||||
|
|
||||||
@@ -146,13 +169,17 @@ class Search(object):
|
|||||||
else:
|
else:
|
||||||
raise SearchError(ErrFormat.ci_type_not_found2.format(_v))
|
raise SearchError(ErrFormat.ci_type_not_found2.format(_v))
|
||||||
|
|
||||||
if self.type_id_list:
|
if type_num != len(self.type_id_list) and queries and queries[0].startswith('_type') and not is_sub:
|
||||||
type_ids = ",".join(self.type_id_list)
|
queries[0] = "_type:({})".format(";".join(self.type_id_list))
|
||||||
|
|
||||||
|
if type_id_list:
|
||||||
|
type_ids = ",".join(type_id_list)
|
||||||
_query_sql = QUERY_CI_BY_TYPE.format(type_ids)
|
_query_sql = QUERY_CI_BY_TYPE.format(type_ids)
|
||||||
if self.only_type_query:
|
if self.only_type_query or self.multi_type_has_ci_filter:
|
||||||
return _query_sql
|
return _query_sql
|
||||||
else:
|
elif type_num > 1: # there must be instance-level access control
|
||||||
return ""
|
return "select c_cis.id as ci_id from c_cis where c_cis.id=0"
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -229,7 +256,7 @@ class Search(object):
|
|||||||
return ret_sql.format(query_sql, "ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
return ret_sql.format(query_sql, "ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||||
(self.page - 1) * self.count, sort_type, self.count))
|
(self.page - 1) * self.count, sort_type, self.count))
|
||||||
|
|
||||||
elif self.type_id_list:
|
elif self.type_id_list and not self.multi_type_has_ci_filter:
|
||||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||||
query_sql,
|
query_sql,
|
||||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
||||||
@@ -254,7 +281,7 @@ class Search(object):
|
|||||||
def __sort_by_type(self, sort_type, query_sql):
|
def __sort_by_type(self, sort_type, query_sql):
|
||||||
ret_sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id FROM ({0}) AS B {1}"
|
ret_sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id FROM ({0}) AS B {1}"
|
||||||
|
|
||||||
if self.type_id_list:
|
if self.type_id_list and not self.multi_type_has_ci_filter:
|
||||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||||
query_sql,
|
query_sql,
|
||||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
||||||
@@ -278,16 +305,23 @@ class Search(object):
|
|||||||
(self.page - 1) * self.count, sort_type, self.count))
|
(self.page - 1) * self.count, sort_type, self.count))
|
||||||
|
|
||||||
def __sort_by_field(self, field, sort_type, query_sql):
|
def __sort_by_field(self, field, sort_type, query_sql):
|
||||||
|
if field not in BUILTIN_ATTRIBUTES:
|
||||||
|
|
||||||
attr = AttributeCache.get(field)
|
attr = AttributeCache.get(field)
|
||||||
attr_id = attr.id
|
attr_id = attr.id
|
||||||
|
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
_v_query_sql = """SELECT ALIAS.ci_id, {0}.value
|
||||||
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
FROM ({1}) AS ALIAS INNER JOIN {0} ON {0}.ci_id = ALIAS.ci_id
|
||||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
WHERE {0}.attr_id = {2}""".format(table_name, query_sql, attr_id)
|
||||||
|
new_table = _v_query_sql
|
||||||
|
else:
|
||||||
|
_v_query_sql = """SELECT c_cis.id AS ci_id, c_cis.{0} AS value
|
||||||
|
FROM c_cis INNER JOIN ({1}) AS ALIAS ON ALIAS.ci_id = c_cis.id""".format(
|
||||||
|
field[1:], query_sql)
|
||||||
new_table = _v_query_sql
|
new_table = _v_query_sql
|
||||||
|
|
||||||
if self.only_type_query or not self.type_id_list:
|
if self.only_type_query or not self.type_id_list or self.multi_type_has_ci_filter:
|
||||||
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
||||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
||||||
|
|
||||||
@@ -325,7 +359,9 @@ class Search(object):
|
|||||||
INNER JOIN ({2}) as {3} USING(ci_id)""".format(query_sql, alias, _query_sql, alias + "A")
|
INNER JOIN ({2}) as {3} USING(ci_id)""".format(query_sql, alias, _query_sql, alias + "A")
|
||||||
|
|
||||||
elif operator == "|" or operator == "|~":
|
elif operator == "|" or operator == "|~":
|
||||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL SELECT * FROM ({2}) as {3}".format(query_sql, alias,
|
||||||
|
_query_sql,
|
||||||
|
alias + "A")
|
||||||
|
|
||||||
elif operator == "~":
|
elif operator == "~":
|
||||||
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
||||||
@@ -430,14 +466,14 @@ class Search(object):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __query_by_attr(self, q, queries, alias):
|
def __query_by_attr(self, q, queries, alias, is_sub=False):
|
||||||
k = q.split(":")[0].strip()
|
k = q.split(":")[0].strip()
|
||||||
v = "\:".join(q.split(":")[1:]).strip()
|
v = "\:".join(q.split(":")[1:]).strip()
|
||||||
v = v.replace("'", "\\'")
|
v = v.replace("'", "\\'")
|
||||||
v = v.replace('"', '\\"')
|
v = v.replace('"', '\\"')
|
||||||
field, field_type, operator, attr = self._attr_name_proc(k)
|
field, field_type, operator, attr = self._attr_name_proc(k)
|
||||||
if field == "_type":
|
if field == "_type":
|
||||||
_query_sql = self._type_query_handler(v, queries)
|
_query_sql = self._type_query_handler(v, queries, is_sub)
|
||||||
|
|
||||||
elif field == "_id":
|
elif field == "_id":
|
||||||
_query_sql = self._id_query_handler(v)
|
_query_sql = self._id_query_handler(v)
|
||||||
@@ -484,19 +520,20 @@ class Search(object):
|
|||||||
|
|
||||||
return alias, _query_sql, operator
|
return alias, _query_sql, operator
|
||||||
|
|
||||||
def __query_build_by_field(self, queries, is_first=True, only_type_query_special=True, alias='A', operator='&'):
|
def __query_build_by_field(self, queries, is_first=True, only_type_query_special=True, alias='A', operator='&',
|
||||||
|
is_sub=False):
|
||||||
query_sql = ""
|
query_sql = ""
|
||||||
|
|
||||||
for q in queries:
|
for q in queries:
|
||||||
_query_sql = ""
|
_query_sql = ""
|
||||||
if isinstance(q, dict):
|
if isinstance(q, dict):
|
||||||
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias)
|
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias, is_sub=True)
|
||||||
current_app.logger.info(_query_sql)
|
# current_app.logger.info(_query_sql)
|
||||||
current_app.logger.info((operator, is_first, alias))
|
# current_app.logger.info((operator, is_first, alias))
|
||||||
operator = q['operator']
|
operator = q['operator']
|
||||||
|
|
||||||
elif ":" in q and not q.startswith("*"):
|
elif ":" in q and not q.startswith("*"):
|
||||||
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias)
|
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias, is_sub)
|
||||||
elif q == "*":
|
elif q == "*":
|
||||||
continue
|
continue
|
||||||
elif q:
|
elif q:
|
||||||
@@ -547,7 +584,6 @@ class Search(object):
|
|||||||
queries = handle_arg_list(self.orig_query)
|
queries = handle_arg_list(self.orig_query)
|
||||||
queries = self._extra_handle_query_expr(queries)
|
queries = self._extra_handle_query_expr(queries)
|
||||||
queries = self.__confirm_type_first(queries)
|
queries = self.__confirm_type_first(queries)
|
||||||
current_app.logger.debug(queries)
|
|
||||||
|
|
||||||
_, query_sql, _ = self.__query_build_by_field(queries)
|
_, query_sql, _ = self.__query_build_by_field(queries)
|
||||||
|
|
||||||
@@ -585,6 +621,7 @@ class Search(object):
|
|||||||
return facet_result
|
return facet_result
|
||||||
|
|
||||||
def _fl_build(self):
|
def _fl_build(self):
|
||||||
|
if isinstance(self.fl, list):
|
||||||
_fl = list()
|
_fl = list()
|
||||||
for f in self.fl:
|
for f in self.fl:
|
||||||
k, _, _, _ = self._attr_name_proc(f)
|
k, _, _, _ = self._attr_name_proc(f)
|
||||||
@@ -592,6 +629,8 @@ class Search(object):
|
|||||||
_fl.append(k)
|
_fl.append(k)
|
||||||
|
|
||||||
return _fl
|
return _fl
|
||||||
|
else:
|
||||||
|
return self.fl
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
numfound, ci_ids = self._query_build_raw()
|
numfound, ci_ids = self._query_build_raw()
|
||||||
@@ -610,6 +649,8 @@ class Search(object):
|
|||||||
if ci_ids:
|
if ci_ids:
|
||||||
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl, excludes=self.excludes)
|
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl, excludes=self.excludes)
|
||||||
for res in response:
|
for res in response:
|
||||||
|
if not res:
|
||||||
|
continue
|
||||||
ci_type = res.get("ci_type")
|
ci_type = res.get("ci_type")
|
||||||
if ci_type not in counter.keys():
|
if ci_type not in counter.keys():
|
||||||
counter[ci_type] = 0
|
counter[ci_type] = 0
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import networkx as nx
|
||||||
|
import sys
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -13,6 +16,7 @@ from api.lib.cmdb.cache import CITypeCache
|
|||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
@@ -25,10 +29,12 @@ from api.lib.cmdb.utils import ValueTypeMap
|
|||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
|
from api.models.cmdb import CITypeRelation
|
||||||
|
from api.models.cmdb import RelationType
|
||||||
|
|
||||||
|
|
||||||
class Search(object):
|
class Search(object):
|
||||||
def __init__(self, root_id,
|
def __init__(self, root_id=None,
|
||||||
level=None,
|
level=None,
|
||||||
query=None,
|
query=None,
|
||||||
fl=None,
|
fl=None,
|
||||||
@@ -385,9 +391,10 @@ class Search(object):
|
|||||||
id2children[str(i)] = item['children']
|
id2children[str(i)] = item['children']
|
||||||
|
|
||||||
for lv in range(1, self.level):
|
for lv in range(1, self.level):
|
||||||
|
type_id = type_ids[lv]
|
||||||
|
|
||||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
|
if len(type_ids or []) >= lv and type2filter_perms.get(type_id):
|
||||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
|
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_id])
|
||||||
else:
|
else:
|
||||||
id_filter_limit = {}
|
id_filter_limit = {}
|
||||||
|
|
||||||
@@ -395,12 +402,12 @@ class Search(object):
|
|||||||
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
||||||
else:
|
else:
|
||||||
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
|
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 = [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
|
res = [[i for i in x if i[1] == type_id 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[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)]
|
||||||
_level_ids = []
|
_level_ids = []
|
||||||
type_id = type_ids[lv]
|
|
||||||
id2name = _get_id2name(type_id)
|
id2name = _get_id2name(type_id)
|
||||||
for idx, node_path in enumerate(level_ids):
|
for idx, node_path in enumerate(level_ids):
|
||||||
for child_id, _ in (res[idx] or []):
|
for child_id, _ in (res[idx] or []):
|
||||||
@@ -419,3 +426,169 @@ class Search(object):
|
|||||||
level_ids = _level_ids
|
level_ids = _level_ids
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_src_ids(src):
|
||||||
|
q = src.get('q') or ''
|
||||||
|
if not q.startswith('_type:'):
|
||||||
|
q = "_type:{},{}".format(src['type_id'], q)
|
||||||
|
|
||||||
|
return SearchFromDB(q, use_ci_filter=True, only_ids=True, count=100000).search()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _filter_target_ids(target_ids, type_ids, q):
|
||||||
|
if not q.startswith('_type:'):
|
||||||
|
q = "_type:({}),{}".format(";".join(map(str, type_ids)), q)
|
||||||
|
|
||||||
|
ci_ids = SearchFromDB(q, ci_ids=target_ids, use_ci_filter=True, only_ids=True, count=100000).search()
|
||||||
|
cis = CI.get_by(fl=['id', 'type_id'], only_query=True).filter(CI.id.in_(ci_ids))
|
||||||
|
|
||||||
|
return [(str(i.id), i.type_id) for i in cis]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _path2level(src_type_id, target_type_ids, path):
|
||||||
|
if not src_type_id or not target_type_ids:
|
||||||
|
return abort(400, ErrFormat.relation_path_search_src_target_required)
|
||||||
|
|
||||||
|
graph = nx.DiGraph()
|
||||||
|
graph.add_edges_from([(n, _path[idx + 1]) for _path in path for idx, n in enumerate(_path[:-1])])
|
||||||
|
relation_types = defaultdict(dict)
|
||||||
|
level2type = defaultdict(set)
|
||||||
|
type2show_key = dict()
|
||||||
|
for _path in path:
|
||||||
|
for idx, node in enumerate(_path[1:]):
|
||||||
|
level2type[idx + 1].add(node)
|
||||||
|
|
||||||
|
src = CITypeCache.get(_path[idx])
|
||||||
|
target = CITypeCache.get(node)
|
||||||
|
relation_type = RelationType.get_by(only_query=True).join(
|
||||||
|
CITypeRelation, CITypeRelation.relation_type_id == RelationType.id).filter(
|
||||||
|
CITypeRelation.parent_id == src.id).filter(CITypeRelation.child_id == target.id).first()
|
||||||
|
relation_types[src.alias].update({target.alias: relation_type.name})
|
||||||
|
|
||||||
|
if src.id not in type2show_key:
|
||||||
|
type2show_key[src.id] = AttributeCache.get(src.show_id or src.unique_id).name
|
||||||
|
if target.id not in type2show_key:
|
||||||
|
type2show_key[target.id] = AttributeCache.get(target.show_id or target.unique_id).name
|
||||||
|
|
||||||
|
nodes = graph.nodes()
|
||||||
|
|
||||||
|
return level2type, list(nodes), relation_types, type2show_key
|
||||||
|
|
||||||
|
def _build_graph(self, source_ids, source_type_id, level2type, target_type_ids, acl):
|
||||||
|
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])))
|
||||||
|
|
||||||
|
target_type_ids = set(target_type_ids)
|
||||||
|
graph = nx.DiGraph()
|
||||||
|
target_ids = []
|
||||||
|
key = [(str(i), source_type_id) for i in source_ids]
|
||||||
|
graph.add_nodes_from(key)
|
||||||
|
for level in level2type:
|
||||||
|
filter_type_ids = level2type[level]
|
||||||
|
id_filter_limit = dict()
|
||||||
|
for _type_id in filter_type_ids:
|
||||||
|
if type2filter_perms.get(_type_id):
|
||||||
|
_id_filter_limit, _ = self._get_ci_filter(type2filter_perms[_type_id])
|
||||||
|
id_filter_limit.update(_id_filter_limit)
|
||||||
|
|
||||||
|
has_target = filter_type_ids & target_type_ids
|
||||||
|
|
||||||
|
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get([i[0] for i in key],
|
||||||
|
REDIS_PREFIX_CI_RELATION) or []]]
|
||||||
|
_key = []
|
||||||
|
for idx, _id in enumerate(key):
|
||||||
|
valid_targets = [i for i in res[idx] if i[1] in filter_type_ids and
|
||||||
|
(not id_filter_limit or int(i[0]) in id_filter_limit)]
|
||||||
|
_key.extend(valid_targets)
|
||||||
|
graph.add_edges_from(zip([_id] * len(valid_targets), valid_targets))
|
||||||
|
|
||||||
|
if has_target:
|
||||||
|
target_ids.extend([j[0] for i in res for j in i if j[1] in target_type_ids])
|
||||||
|
|
||||||
|
key = copy.deepcopy(_key)
|
||||||
|
|
||||||
|
return graph, target_ids
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_paths(graph, source_ids, source_type_id, target_ids, valid_path, max_depth=6):
|
||||||
|
paths = []
|
||||||
|
for source_id in source_ids:
|
||||||
|
_paths = nx.all_simple_paths(graph,
|
||||||
|
source=(source_id, source_type_id),
|
||||||
|
target=target_ids,
|
||||||
|
cutoff=max_depth)
|
||||||
|
for __path in _paths:
|
||||||
|
if tuple([i[1] for i in __path]) in valid_path:
|
||||||
|
paths.append([i[0] for i in __path])
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _wrap_path_result(paths, types, valid_path, target_types, type2show_key):
|
||||||
|
ci_ids = [j for i in paths for j in i]
|
||||||
|
|
||||||
|
response, _, _, _, _, _ = SearchFromDB("_type:({})".format(";".join(map(str, types))),
|
||||||
|
use_ci_filter=False,
|
||||||
|
ci_ids=list(map(int, ci_ids)),
|
||||||
|
count=1000000).search()
|
||||||
|
id2ci = {str(i.get('_id')): i if i['_type'] in target_types else {
|
||||||
|
type2show_key[i['_type']]: i[type2show_key[i['_type']]],
|
||||||
|
"ci_type_alias": i["ci_type_alias"],
|
||||||
|
"_type": i["_type"],
|
||||||
|
} for i in response}
|
||||||
|
|
||||||
|
result = defaultdict(list)
|
||||||
|
counter = defaultdict(int)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
key = "-".join([id2ci.get(i, {}).get('ci_type_alias') or '' for i in path])
|
||||||
|
if tuple([id2ci.get(i, {}).get('_type') for i in path]) in valid_path:
|
||||||
|
counter[key] += 1
|
||||||
|
result[key].append(path)
|
||||||
|
|
||||||
|
return result, counter, id2ci
|
||||||
|
|
||||||
|
def search_by_path(self, source, target, path):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param source: {type_id: id, q: expr}
|
||||||
|
:param target: {type_ids: [id], q: expr}
|
||||||
|
:param path: [source_type_id, ..., target_type_id], use type id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
acl = ACLManager('cmdb')
|
||||||
|
if not self.is_app_admin:
|
||||||
|
res = {i['name'] for i in acl.get_resources(ResourceTypeEnum.CI_TYPE)}
|
||||||
|
for type_id in (source.get('type_id') and [source['type_id']] or []) + (target.get('type_ids') or []):
|
||||||
|
_type = CITypeCache.get(type_id)
|
||||||
|
if _type and _type.name not in res:
|
||||||
|
return abort(403, ErrFormat.no_permission.format(_type.alias, PermEnum.READ))
|
||||||
|
|
||||||
|
target['type_ids'] = [i[-1] for i in path]
|
||||||
|
level2type, types, relation_types, type2show_key = self._path2level(
|
||||||
|
source.get('type_id'), target.get('type_ids'), path)
|
||||||
|
if not level2type:
|
||||||
|
return [], {}, 0, self.page, 0, {}, {}
|
||||||
|
|
||||||
|
source_ids = self._get_src_ids(source)
|
||||||
|
|
||||||
|
graph, target_ids = self._build_graph(source_ids, source['type_id'], level2type, target['type_ids'], acl)
|
||||||
|
target_ids = self._filter_target_ids(target_ids, target['type_ids'], target.get('q') or '')
|
||||||
|
paths = self._find_paths(graph,
|
||||||
|
source_ids,
|
||||||
|
source['type_id'],
|
||||||
|
set(target_ids),
|
||||||
|
{tuple(i): 1 for i in path})
|
||||||
|
|
||||||
|
numfound = len(paths)
|
||||||
|
paths = paths[(self.page - 1) * self.count:self.page * self.count]
|
||||||
|
response, counter, id2ci = self._wrap_path_result(paths,
|
||||||
|
types,
|
||||||
|
{tuple(i): 1 for i in path},
|
||||||
|
set(target.get('type_ids') or []),
|
||||||
|
type2show_key)
|
||||||
|
return response, counter, len(paths), self.page, numfound, id2ci, relation_types, type2show_key
|
||||||
|
@@ -97,6 +97,8 @@ class AttributeValueManager(object):
|
|||||||
deserialize = ValueTypeMap.deserialize[value_type]
|
deserialize = ValueTypeMap.deserialize[value_type]
|
||||||
try:
|
try:
|
||||||
v = deserialize(value)
|
v = deserialize(value)
|
||||||
|
if value_type in (ValueTypeEnum.DATE, ValueTypeEnum.DATETIME):
|
||||||
|
return str(v)
|
||||||
return v
|
return v
|
||||||
except ValueDeserializeError as e:
|
except ValueDeserializeError as e:
|
||||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||||
|
@@ -53,6 +53,8 @@ class CMDBApp(BaseApp):
|
|||||||
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||||
"create_topology_view"],
|
"create_topology_view"],
|
||||||
},
|
},
|
||||||
|
{"page": "IPAM", "page_cn": "IPAM", "perms": ["read"]},
|
||||||
|
{"page": "DCIM", "page_cn": "数据中心", "perms": ["read"]},
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
@@ -32,11 +33,21 @@ class DBMixin(object):
|
|||||||
|
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
if hasattr(cls.cls, k):
|
if hasattr(cls.cls, k):
|
||||||
|
if isinstance(kwargs[k], list):
|
||||||
|
query = query.filter(getattr(cls.cls, k).in_(kwargs[k]))
|
||||||
|
if count_query:
|
||||||
|
_query = _query.filter(getattr(cls.cls, k).in_(kwargs[k]))
|
||||||
|
else:
|
||||||
|
if "*" in str(kwargs[k]):
|
||||||
|
query = query.filter(getattr(cls.cls, k).ilike(kwargs[k].replace('*', '%')))
|
||||||
|
if count_query:
|
||||||
|
_query = _query.filter(getattr(cls.cls, k).ilike(kwargs[k].replace('*', '%')))
|
||||||
|
else:
|
||||||
query = query.filter(getattr(cls.cls, k) == kwargs[k])
|
query = query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||||
if count_query:
|
if count_query:
|
||||||
_query = _query.filter(getattr(cls.cls, k) == kwargs[k])
|
_query = _query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||||
|
|
||||||
if reverse:
|
if reverse in current_app.config.get('BOOL_TRUE'):
|
||||||
query = query.order_by(cls.cls.id.desc())
|
query = query.order_by(cls.cls.id.desc())
|
||||||
|
|
||||||
if only_query and not count_query:
|
if only_query and not count_query:
|
||||||
|
@@ -376,7 +376,7 @@ class AuditCRUD(object):
|
|||||||
origin=origin, current=current, extra=extra, source=source.value)
|
origin=origin, current=current, extra=extra, source=source.value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
|
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None, ip=None, browser=None):
|
||||||
if _id is not None:
|
if _id is not None:
|
||||||
existed = AuditLoginLog.get_by_id(_id)
|
existed = AuditLoginLog.get_by_id(_id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
@@ -387,8 +387,9 @@ class AuditCRUD(object):
|
|||||||
is_ok=is_ok,
|
is_ok=is_ok,
|
||||||
description=description,
|
description=description,
|
||||||
logout_at=logout_at,
|
logout_at=logout_at,
|
||||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
ip=(ip or request.headers.get('X-Forwarded-For') or
|
||||||
browser=request.headers.get('User-Agent'),
|
request.headers.get('X-Real-IP') or request.remote_addr or '').split(',')[0],
|
||||||
|
browser=browser or request.headers.get('User-Agent'),
|
||||||
channel=request.values.get('channel', 'web'),
|
channel=request.values.get('channel', 'web'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -71,7 +71,7 @@ class PermissionCRUD(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all2(cls, resource_name, resource_type_name, app_id):
|
def get_all2(cls, resource_name, resource_type_name, app_id):
|
||||||
rt = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
rt = ResourceType.get_by(name=resource_type_name, app_id=app_id, first=True, to_dict=False)
|
||||||
rt or abort(404, ErrFormat.resource_type_not_found.format(resource_type_name))
|
rt or abort(404, ErrFormat.resource_type_not_found.format(resource_type_name))
|
||||||
|
|
||||||
r = Resource.get_by(name=resource_name, resource_type_id=rt.id, app_id=app_id, first=True, to_dict=False)
|
r = Resource.get_by(name=resource_name, resource_type_id=rt.id, app_id=app_id, first=True, to_dict=False)
|
||||||
|
@@ -253,6 +253,7 @@ class CI(Model):
|
|||||||
status = db.Column(db.Enum(*CIStatusEnum.all(), name="status"))
|
status = db.Column(db.Enum(*CIStatusEnum.all(), name="status"))
|
||||||
heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
|
heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
|
||||||
is_auto_discovery = db.Column('a', db.Boolean, default=False)
|
is_auto_discovery = db.Column('a', db.Boolean, default=False)
|
||||||
|
updated_by = db.Column(db.String(64))
|
||||||
|
|
||||||
ci_type = db.relationship("CIType", backref="c_cis.type_id")
|
ci_type = db.relationship("CIType", backref="c_cis.type_id")
|
||||||
|
|
||||||
@@ -475,6 +476,7 @@ class PreferenceShowAttributes(Model):
|
|||||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||||
|
builtin_attr = db.Column(db.String(256), nullable=True)
|
||||||
order = db.Column(db.SmallInteger, default=0)
|
order = db.Column(db.SmallInteger, default=0)
|
||||||
is_fixed = db.Column(db.Boolean, default=False)
|
is_fixed = db.Column(db.Boolean, default=False)
|
||||||
|
|
||||||
@@ -534,6 +536,7 @@ class CustomDashboard(Model):
|
|||||||
|
|
||||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'))
|
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'))
|
||||||
|
builtin_attr = db.Column(db.String(256), nullable=True)
|
||||||
level = db.Column(db.Integer)
|
level = db.Column(db.Integer)
|
||||||
|
|
||||||
options = db.Column(db.JSON)
|
options = db.Column(db.JSON)
|
||||||
@@ -666,3 +669,52 @@ class InnerKV(Model):
|
|||||||
|
|
||||||
key = db.Column(db.String(128), index=True)
|
key = db.Column(db.String(128), index=True)
|
||||||
value = db.Column(db.Text)
|
value = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMSubnetScan(Model):
|
||||||
|
__tablename__ = "c_ipam_subnet_scans"
|
||||||
|
|
||||||
|
ci_id = db.Column(db.Integer, index=True, nullable=False)
|
||||||
|
scan_enabled = db.Column(db.Boolean, default=True)
|
||||||
|
rule_updated_at = db.Column(db.DateTime)
|
||||||
|
last_scan_time = db.Column(db.DateTime)
|
||||||
|
|
||||||
|
# scan rules
|
||||||
|
agent_id = db.Column(db.String(8), index=True)
|
||||||
|
cron = db.Column(db.String(128))
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMSubnetScanHistory(Model2):
|
||||||
|
__tablename__ = "c_ipam_subnet_scan_histories"
|
||||||
|
|
||||||
|
subnet_scan_id = db.Column(db.Integer, index=True)
|
||||||
|
exec_id = db.Column(db.String(64), index=True)
|
||||||
|
cidr = db.Column(db.String(18), index=True)
|
||||||
|
start_at = db.Column(db.DateTime)
|
||||||
|
end_at = db.Column(db.DateTime)
|
||||||
|
status = db.Column(db.Integer, default=0) # 0 is ok
|
||||||
|
stdout = db.Column(db.Text)
|
||||||
|
ip_num = db.Column(db.Integer)
|
||||||
|
ips = db.Column(db.JSON) # keep only the last 10 records
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMOperationHistory(Model2):
|
||||||
|
__tablename__ = "c_ipam_operation_histories"
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||||
|
|
||||||
|
uid = db.Column(db.Integer, index=True)
|
||||||
|
cidr = db.Column(db.String(18), index=True)
|
||||||
|
operate_type = db.Column(db.Enum(*OperateTypeEnum.all()))
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
class DCIMOperationHistory(Model2):
|
||||||
|
__tablename__ = "c_dcim_operation_histories"
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.const import OperateTypeEnum
|
||||||
|
|
||||||
|
uid = db.Column(db.Integer, index=True)
|
||||||
|
rack_id = db.Column(db.Integer, index=True)
|
||||||
|
ci_id = db.Column(db.Integer, index=True)
|
||||||
|
operate_type = db.Column(db.Enum(*OperateTypeEnum.all()))
|
||||||
|
@@ -5,6 +5,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import redis_lock
|
import redis_lock
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from flask import has_request_context
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
|
|
||||||
import api.lib.cmdb.ci
|
import api.lib.cmdb.ci
|
||||||
@@ -53,6 +54,7 @@ def ci_cache(ci_id, operate_type, record_id):
|
|||||||
current_app.logger.info("{0} flush..........".format(ci_id))
|
current_app.logger.info("{0} flush..........".format(ci_id))
|
||||||
|
|
||||||
if operate_type:
|
if operate_type:
|
||||||
|
if not has_request_context():
|
||||||
current_app.test_request_context().push()
|
current_app.test_request_context().push()
|
||||||
login_user(UserCache.get('worker'))
|
login_user(UserCache.get('worker'))
|
||||||
|
|
||||||
@@ -184,6 +186,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
from api.lib.cmdb.search.ci import search
|
from api.lib.cmdb.search.ci import search
|
||||||
|
|
||||||
|
if not has_request_context():
|
||||||
current_app.test_request_context().push()
|
current_app.test_request_context().push()
|
||||||
login_user(UserCache.get(uid))
|
login_user(UserCache.get(uid))
|
||||||
|
|
||||||
@@ -272,6 +275,7 @@ def ci_type_attribute_order_rebuild(type_id, uid):
|
|||||||
def calc_computed_attribute(attr_id, uid):
|
def calc_computed_attribute(attr_id, uid):
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
|
||||||
|
if not has_request_context():
|
||||||
current_app.test_request_context().push()
|
current_app.test_request_context().push()
|
||||||
login_user(UserCache.get(uid))
|
login_user(UserCache.get(uid))
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-08-20 13:47+0800\n"
|
"POT-Creation-Date: 2024-11-26 18:54+0800\n"
|
||||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: zh\n"
|
"Language: zh\n"
|
||||||
@@ -92,6 +92,14 @@ msgstr "您没有操作权限!"
|
|||||||
msgid "Only the creator or administrator has permission!"
|
msgid "Only the creator or administrator has permission!"
|
||||||
msgstr "只有创建人或者管理员才有权限!"
|
msgstr "只有创建人或者管理员才有权限!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/const.py:133
|
||||||
|
msgid "Update Time"
|
||||||
|
msgstr "更新时间"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/const.py:134
|
||||||
|
msgid "Updated By"
|
||||||
|
msgstr "更新人"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:9
|
#: api/lib/cmdb/resp_format.py:9
|
||||||
msgid "CI Model"
|
msgid "CI Model"
|
||||||
msgstr "模型配置"
|
msgstr "模型配置"
|
||||||
@@ -474,11 +482,11 @@ msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
|||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:150
|
#: api/lib/cmdb/resp_format.py:150
|
||||||
msgid "CMDB data reconciliation results"
|
msgid "CMDB data reconciliation results"
|
||||||
msgstr ""
|
msgstr "CMDB数据合规检查结果"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:151
|
#: api/lib/cmdb/resp_format.py:151
|
||||||
msgid "Number of {} illegal: {}"
|
msgid "Number of {} illegal: {}"
|
||||||
msgstr ""
|
msgstr "{} 不合规数: {}"
|
||||||
|
|
||||||
#: api/lib/cmdb/resp_format.py:153
|
#: api/lib/cmdb/resp_format.py:153
|
||||||
msgid "Topology view {} already exists"
|
msgid "Topology view {} already exists"
|
||||||
@@ -492,6 +500,62 @@ msgstr "拓扑视图分组 {} 已经存在"
|
|||||||
msgid "The group cannot be deleted because the topology view already exists"
|
msgid "The group cannot be deleted because the topology view already exists"
|
||||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:158
|
||||||
|
msgid "Both the source model and the target model must be selected"
|
||||||
|
msgstr "源模型和目标模型不能为空!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:160
|
||||||
|
msgid "The names of built-in models cannot be changed"
|
||||||
|
msgstr "内置模型的名字不能修改"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:162
|
||||||
|
msgid "The subnet model {} does not exist"
|
||||||
|
msgstr "子网模型 {} 不存在!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:163
|
||||||
|
msgid "The IP Address model {} does not exist"
|
||||||
|
msgstr "IP地址模型 {} 不存在!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:164
|
||||||
|
msgid "CIDR {} is an invalid notation"
|
||||||
|
msgstr "CIDR {} 写法不正确!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:165
|
||||||
|
msgid "Invalid CIDR: {}, available subnets: {}"
|
||||||
|
msgstr "无效的CIDR: {}, 可用的子网: {}"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:166
|
||||||
|
msgid "Invalid subnet prefix length: {}"
|
||||||
|
msgstr "无效的子网前缀长度: {}"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:167
|
||||||
|
msgid "parent node cidr must be required"
|
||||||
|
msgstr "必须要有父节点"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:168
|
||||||
|
msgid "{} and {} overlap"
|
||||||
|
msgstr "{} 和 {} 有重叠"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:169 api/lib/cmdb/resp_format.py:171
|
||||||
|
msgid "Cannot delete because child nodes exist"
|
||||||
|
msgstr "因为子节点已经存在,不能删除"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:170
|
||||||
|
msgid "Subnet is not found"
|
||||||
|
msgstr "子网不存在"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:174
|
||||||
|
msgid "The dcim model {} does not exist"
|
||||||
|
msgstr "DCIM模型 {} 不存在!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:175
|
||||||
|
msgid "Irregularities in Rack Units"
|
||||||
|
msgstr "机架U位异常!"
|
||||||
|
|
||||||
|
#: api/lib/cmdb/resp_format.py:176
|
||||||
|
msgid "The device's position is greater than the rack unit height"
|
||||||
|
msgstr "有设备的位置大于机柜的U数!"
|
||||||
|
|
||||||
#: api/lib/common_setting/resp_format.py:8
|
#: api/lib/common_setting/resp_format.py:8
|
||||||
msgid "Company info already existed"
|
msgid "Company info already existed"
|
||||||
msgstr "公司信息已存在,无法创建!"
|
msgstr "公司信息已存在,无法创建!"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import six
|
import six
|
||||||
from flask import abort
|
from flask import abort
|
||||||
@@ -17,10 +16,12 @@ from api.lib.decorator import args_required
|
|||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.audit import AuditCRUD
|
from api.lib.perm.acl.audit import AuditCRUD
|
||||||
|
from api.lib.perm.acl.cache import AppCache
|
||||||
from api.lib.perm.acl.cache import RoleCache
|
from api.lib.perm.acl.cache import RoleCache
|
||||||
from api.lib.perm.acl.cache import User
|
from api.lib.perm.acl.cache import User
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.perm.acl.resp_format import ErrFormat
|
from api.lib.perm.acl.resp_format import ErrFormat
|
||||||
|
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||||
from api.lib.perm.auth import auth_abandoned
|
from api.lib.perm.auth import auth_abandoned
|
||||||
from api.lib.perm.auth import auth_with_app_token
|
from api.lib.perm.auth import auth_with_app_token
|
||||||
from api.models.acl import Role
|
from api.models.acl import Role
|
||||||
@@ -124,11 +125,18 @@ class AuthWithKeyView(APIView):
|
|||||||
if not user.get('username'):
|
if not user.get('username'):
|
||||||
user['username'] = user.get('name')
|
user['username'] = user.get('name')
|
||||||
|
|
||||||
return self.jsonify(user=user,
|
result = dict(user=user,
|
||||||
authenticated=authenticated,
|
authenticated=authenticated,
|
||||||
rid=role and role.id,
|
rid=role and role.id,
|
||||||
can_proxy=can_proxy)
|
can_proxy=can_proxy)
|
||||||
|
|
||||||
|
if request.values.get('need_parentRoles') in current_app.config.get('BOOL_TRUE'):
|
||||||
|
app_id = AppCache.get(request.values.get('app_id'))
|
||||||
|
parent_ids = RoleRelationCRUD.recursive_parent_ids(role and role.id, app_id and app_id.id)
|
||||||
|
result['user']['parentRoles'] = [RoleCache.get(rid).name for rid in set(parent_ids) if RoleCache.get(rid)]
|
||||||
|
|
||||||
|
return self.jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
class AuthWithTokenView(APIView):
|
class AuthWithTokenView(APIView):
|
||||||
url_prefix = ("/auth_with_token", "/req_token")
|
url_prefix = ("/auth_with_token", "/req_token")
|
||||||
@@ -184,6 +192,8 @@ class LogoutView(APIView):
|
|||||||
def post(self):
|
def post(self):
|
||||||
logout_user()
|
logout_user()
|
||||||
|
|
||||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
AuditCRUD.add_login_log(None, None, None,
|
||||||
|
_id=session.get('LOGIN_ID') or request.values.get('LOGIN_ID'),
|
||||||
|
logout_at=datetime.datetime.now())
|
||||||
|
|
||||||
self.jsonify(code=200)
|
self.jsonify(code=200)
|
||||||
|
@@ -11,6 +11,7 @@ from flask_login import current_user
|
|||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.decorator import args_validate
|
from api.lib.decorator import args_validate
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
|
from api.lib.perm.acl.acl import AuditCRUD
|
||||||
from api.lib.perm.acl.acl import role_required
|
from api.lib.perm.acl.acl import role_required
|
||||||
from api.lib.perm.acl.cache import AppCache
|
from api.lib.perm.acl.cache import AppCache
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
@@ -48,6 +49,13 @@ class GetUserInfoView(APIView):
|
|||||||
role=dict(permissions=user_info.get('parents')),
|
role=dict(permissions=user_info.get('parents')),
|
||||||
avatar=user_info.get('avatar'))
|
avatar=user_info.get('avatar'))
|
||||||
|
|
||||||
|
if request.values.get('channel'):
|
||||||
|
_id = AuditCRUD.add_login_log(name, True, ErrFormat.login_succeed,
|
||||||
|
ip=request.values.get('ip'),
|
||||||
|
browser=request.values.get('browser'))
|
||||||
|
session['LOGIN_ID'] = _id
|
||||||
|
result['LOGIN_ID'] = _id
|
||||||
|
|
||||||
current_app.logger.info("get user info for3: {}".format(result))
|
current_app.logger.info("get user info for3: {}".format(result))
|
||||||
return self.jsonify(result=result)
|
return self.jsonify(result=result)
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
|
|||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.ipam.subnet import SubnetManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
from api.lib.cmdb.search.ci import search as ci_search
|
from api.lib.cmdb.search.ci import search as ci_search
|
||||||
@@ -293,9 +294,13 @@ class AutoDiscoveryRuleSyncView(APIView):
|
|||||||
|
|
||||||
return self.jsonify(rules=rules, last_update_at=last_update_at)
|
return self.jsonify(rules=rules, last_update_at=last_update_at)
|
||||||
|
|
||||||
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
|
rules, last_update_at1 = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
|
||||||
|
|
||||||
return self.jsonify(rules=rules, last_update_at=last_update_at)
|
subnet_scan_rules, last_update_at2 = SubnetManager().scan_rules(oneagent_id, last_update_at)
|
||||||
|
|
||||||
|
return self.jsonify(rules=rules,
|
||||||
|
subnet_scan_rules=subnet_scan_rules,
|
||||||
|
last_update_at=max(last_update_at1 or "", last_update_at2 or ""))
|
||||||
|
|
||||||
|
|
||||||
class AutoDiscoveryRuleSyncHistoryView(APIView):
|
class AutoDiscoveryRuleSyncHistoryView(APIView):
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
@@ -65,6 +64,42 @@ class CIRelationSearchView(APIView):
|
|||||||
result=response)
|
result=response)
|
||||||
|
|
||||||
|
|
||||||
|
class CIRelationSearchPathView(APIView):
|
||||||
|
url_prefix = ("/ci_relations/path/s", "/ci_relations/path/search")
|
||||||
|
|
||||||
|
@args_required("source", "target", "path")
|
||||||
|
def post(self):
|
||||||
|
"""@params: page: page number
|
||||||
|
page_size | count: page size
|
||||||
|
source: source CIType, e.g. {type_id: 1, q: `search expr`}
|
||||||
|
target: target CIType, e.g. {type_ids: [2], q: `search expr`}
|
||||||
|
path: Path from the Source CIType to the Target CIType, e.g. [1, ..., 2]
|
||||||
|
"""
|
||||||
|
|
||||||
|
page = get_page(request.values.get("page", 1))
|
||||||
|
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||||
|
|
||||||
|
source = request.values.get("source")
|
||||||
|
target = request.values.get("target")
|
||||||
|
path = request.values.get("path")
|
||||||
|
|
||||||
|
s = Search(page=page, count=count)
|
||||||
|
try:
|
||||||
|
(response, counter, total, page, numfound, id2ci,
|
||||||
|
relation_types, type2show_key) = s.search_by_path(source, target, path)
|
||||||
|
except SearchError as e:
|
||||||
|
return abort(400, str(e))
|
||||||
|
|
||||||
|
return self.jsonify(numfound=numfound,
|
||||||
|
total=total,
|
||||||
|
page=page,
|
||||||
|
counter=counter,
|
||||||
|
paths=response,
|
||||||
|
id2ci=id2ci,
|
||||||
|
relation_types=relation_types,
|
||||||
|
type2show_key=type2show_key)
|
||||||
|
|
||||||
|
|
||||||
class CIRelationStatisticsView(APIView):
|
class CIRelationStatisticsView(APIView):
|
||||||
url_prefix = "/ci_relations/statistics"
|
url_prefix = "/ci_relations/statistics"
|
||||||
|
|
||||||
|
@@ -8,7 +8,6 @@ from api.lib.cmdb.ci_type import CITypeManager
|
|||||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RoleEnum
|
|
||||||
from api.lib.cmdb.preference import PreferenceManager
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.common_setting.decorator import perms_role_required
|
from api.lib.common_setting.decorator import perms_role_required
|
||||||
@@ -17,7 +16,7 @@ from api.lib.decorator import args_required
|
|||||||
from api.lib.perm.acl.acl import ACLManager
|
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 has_perm_from_args
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.lib.perm.acl.acl import role_required
|
from api.lib.utils import handle_arg_list
|
||||||
from api.resource import APIView
|
from api.resource import APIView
|
||||||
|
|
||||||
app_cli = CMDBApp()
|
app_cli = CMDBApp()
|
||||||
@@ -42,6 +41,19 @@ class GetParentsView(APIView):
|
|||||||
return self.jsonify(parents=CITypeRelationManager.get_parents(child_id))
|
return self.jsonify(parents=CITypeRelationManager.get_parents(child_id))
|
||||||
|
|
||||||
|
|
||||||
|
class CITypeRelationPathView(APIView):
|
||||||
|
url_prefix = ("/ci_type_relations/path",)
|
||||||
|
|
||||||
|
@args_required("source_type_id", "target_type_ids")
|
||||||
|
def get(self):
|
||||||
|
source_type_id = request.values.get("source_type_id")
|
||||||
|
target_type_ids = handle_arg_list(request.values.get("target_type_ids"))
|
||||||
|
|
||||||
|
paths = CITypeRelationManager.find_path(source_type_id, target_type_ids)
|
||||||
|
|
||||||
|
return self.jsonify(paths=paths)
|
||||||
|
|
||||||
|
|
||||||
class CITypeRelationView(APIView):
|
class CITypeRelationView(APIView):
|
||||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||||
|
|
||||||
|
1
cmdb-api/api/views/cmdb/dcim/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
30
cmdb-api/api/views/cmdb/dcim/dcim_history.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.history import OperateHistoryManager
|
||||||
|
from api.lib.common_setting.decorator import perms_role_required
|
||||||
|
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||||
|
from api.lib.utils import get_page
|
||||||
|
from api.lib.utils import get_page_size
|
||||||
|
from api.lib.utils import handle_arg_list
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class DCIMOperateHistoryView(APIView):
|
||||||
|
url_prefix = ("/dcim/history/operate",)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
page = get_page(request.values.pop("page", 1))
|
||||||
|
page_size = get_page_size(request.values.pop("page_size", None))
|
||||||
|
operate_type = handle_arg_list(request.values.pop('operate_type', []))
|
||||||
|
if operate_type:
|
||||||
|
request.values["operate_type"] = operate_type
|
||||||
|
|
||||||
|
numfound, result, id2ci, type2show_key = OperateHistoryManager.search(page, page_size, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(numfound=numfound, result=result, id2ci=id2ci, type2show_key=type2show_key)
|
35
cmdb-api/api/views/cmdb/dcim/idc.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.idc import IDCManager
|
||||||
|
from api.lib.common_setting.decorator import perms_role_required
|
||||||
|
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class IDCView(APIView):
|
||||||
|
url_prefix = ("/dcim/idc", "/dcim/idc/<int:_id>")
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
parent_id = request.values.pop("parent_id")
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=IDCManager().add(parent_id, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
IDCManager().update(_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
IDCManager().delete(_id)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
89
cmdb-api/api/views/cmdb/dcim/rack.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
|
||||||
|
from api.lib.cmdb.dcim.rack import RackManager
|
||||||
|
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.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class RackView(APIView):
|
||||||
|
url_prefix = ("/dcim/rack", "/dcim/rack/<int:_id>")
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
@args_required("parent_id")
|
||||||
|
def post(self):
|
||||||
|
parent_id = request.values.pop("parent_id")
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=RackManager().add(parent_id, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
RackManager().update(_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
RackManager().delete(_id)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
||||||
|
|
||||||
|
|
||||||
|
class RackDetailView(APIView):
|
||||||
|
url_prefix = ("/dcim/rack/<int:rack_id>/device/<int:device_id>",)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
@args_required(RackBuiltinAttributes.U_START)
|
||||||
|
def post(self, rack_id, device_id):
|
||||||
|
u_start = request.values.pop(RackBuiltinAttributes.U_START)
|
||||||
|
u_count = request.values.get(RackBuiltinAttributes.U_COUNT)
|
||||||
|
|
||||||
|
RackManager().add_device(rack_id, device_id, u_start, u_count)
|
||||||
|
|
||||||
|
return self.jsonify(rack_id=rack_id, device_id=device_id)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
@args_required("to_u_start")
|
||||||
|
def put(self, rack_id, device_id):
|
||||||
|
to_u_start = request.values.pop("to_u_start")
|
||||||
|
|
||||||
|
RackManager().move_device(rack_id, device_id, to_u_start)
|
||||||
|
|
||||||
|
return self.jsonify(rack_id=rack_id, device_id=device_id, to_u_start=to_u_start)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, rack_id, device_id):
|
||||||
|
RackManager().remove_device(rack_id, device_id)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class RackDeviceMigrateView(APIView):
|
||||||
|
url_prefix = ("/dcim/rack/<int:rack_id>/device/<int:device_id>/migrate",)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
@args_required("to_rack_id")
|
||||||
|
@args_required("to_u_start")
|
||||||
|
def put(self, rack_id, device_id):
|
||||||
|
to_rack_id = request.values.pop("to_rack_id")
|
||||||
|
to_u_start = request.values.pop("to_u_start")
|
||||||
|
|
||||||
|
RackManager().migrate_device(rack_id, device_id, to_rack_id, to_u_start)
|
||||||
|
|
||||||
|
return self.jsonify(rack_id=rack_id,
|
||||||
|
device_id=device_id,
|
||||||
|
to_u_start=to_u_start,
|
||||||
|
to_rack_id=to_rack_id)
|
33
cmdb-api/api/views/cmdb/dcim/region.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.region import RegionManager
|
||||||
|
from api.lib.common_setting.decorator import perms_role_required
|
||||||
|
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class RegionView(APIView):
|
||||||
|
url_prefix = ("/dcim/region", "/dcim/region/<int:_id>")
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
return self.jsonify(ci_id=RegionManager().add(**request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
RegionManager().update(_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
RegionManager().delete(_id)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
43
cmdb-api/api/views/cmdb/dcim/server_room.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.server_room import ServerRoomManager
|
||||||
|
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.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class ServerRoomView(APIView):
|
||||||
|
url_prefix = ("/dcim/server_room", "/dcim/server_room/<int:_id>", "/dcim/server_room/<int:_id>/racks")
|
||||||
|
|
||||||
|
def get(self, _id):
|
||||||
|
q = request.values.get('q')
|
||||||
|
counter, result = ServerRoomManager.get_racks(_id, q)
|
||||||
|
|
||||||
|
return self.jsonify(counter=counter, result=result)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
@args_required("parent_id")
|
||||||
|
def post(self):
|
||||||
|
parent_id = request.values.pop("parent_id")
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=ServerRoomManager().add(parent_id, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
ServerRoomManager().update(_id, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
ServerRoomManager().delete(_id)
|
||||||
|
|
||||||
|
return self.jsonify(ci_id=_id)
|
19
cmdb-api/api/views/cmdb/dcim/tree_view.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from api.lib.cmdb.dcim.tree_view import TreeViewManager
|
||||||
|
from api.lib.common_setting.decorator import perms_role_required
|
||||||
|
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class DCIMTreeView(APIView):
|
||||||
|
url_prefix = "/dcim/tree_view"
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
result, type2name = TreeViewManager.get()
|
||||||
|
|
||||||
|
return self.jsonify(result=result, type2name=type2name)
|
1
cmdb-api/api/views/cmdb/ipam/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
39
cmdb-api/api/views/cmdb/ipam/address.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.address import IpAddressManager
|
||||||
|
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.utils import handle_arg_list
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressView(APIView):
|
||||||
|
url_prefix = ("/ipam/address",)
|
||||||
|
|
||||||
|
@args_required("parent_id")
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
parent_id = request.args.get("parent_id")
|
||||||
|
|
||||||
|
numfound, result = IpAddressManager.list_ip_address(parent_id)
|
||||||
|
|
||||||
|
return self.jsonify(numfound=numfound, result=result)
|
||||||
|
|
||||||
|
@args_required("ips")
|
||||||
|
@args_required("assign_status", value_required=False)
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
ips = handle_arg_list(request.values.pop("ips"))
|
||||||
|
parent_id = request.values.pop("parent_id", None)
|
||||||
|
cidr = request.values.pop("cidr", None)
|
||||||
|
|
||||||
|
IpAddressManager().assign_ips(ips, parent_id, cidr, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
53
cmdb-api/api/views/cmdb/ipam/ipam_history.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||||
|
from api.lib.cmdb.ipam.history import ScanHistoryManager
|
||||||
|
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.utils import get_page
|
||||||
|
from api.lib.utils import get_page_size
|
||||||
|
from api.lib.utils import handle_arg_list
|
||||||
|
from api.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMOperateHistoryView(APIView):
|
||||||
|
url_prefix = ("/ipam/history/operate",)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
page = get_page(request.values.pop("page", 1))
|
||||||
|
page_size = get_page_size(request.values.pop("page_size", None))
|
||||||
|
operate_type = handle_arg_list(request.values.pop('operate_type', []))
|
||||||
|
if operate_type:
|
||||||
|
request.values["operate_type"] = operate_type
|
||||||
|
|
||||||
|
numfound, result = OperateHistoryManager.search(page, page_size, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(numfound=numfound, result=result)
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMScanHistoryView(APIView):
|
||||||
|
url_prefix = ("/ipam/history/scan",)
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
page = get_page(request.values.pop("page", 1))
|
||||||
|
page_size = get_page_size(request.values.pop("page_size", None))
|
||||||
|
|
||||||
|
numfound, result = ScanHistoryManager.search(page, page_size, **request.values)
|
||||||
|
|
||||||
|
return self.jsonify(numfound=numfound, result=result)
|
||||||
|
|
||||||
|
@args_required("exec_id")
|
||||||
|
def post(self):
|
||||||
|
|
||||||
|
ScanHistoryManager().add(**request.values)
|
||||||
|
|
||||||
|
return self.jsonify(code=200)
|
24
cmdb-api/api/views/cmdb/ipam/ipam_stats.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.stats import Stats
|
||||||
|
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.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class IPAMStatsView(APIView):
|
||||||
|
url_prefix = '/ipam/stats'
|
||||||
|
|
||||||
|
@args_required("parent_id")
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self):
|
||||||
|
parent_id = request.values.get("parent_id")
|
||||||
|
|
||||||
|
return self.jsonify(Stats().summary(parent_id))
|
75
cmdb-api/api/views/cmdb/ipam/subnet.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from api.lib.cmdb.ipam.subnet import SubnetManager
|
||||||
|
from api.lib.cmdb.ipam.subnet import SubnetScopeManager
|
||||||
|
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.resource import APIView
|
||||||
|
|
||||||
|
app_cli = CMDBApp()
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetView(APIView):
|
||||||
|
url_prefix = ("/ipam/subnet", "/ipam/subnet/hosts", "/ipam/subnet/<int:_id>")
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def get(self, _id=None):
|
||||||
|
if "hosts" in request.url:
|
||||||
|
return self.jsonify(SubnetManager.get_hosts(request.values.get('cidr')))
|
||||||
|
|
||||||
|
if _id is not None:
|
||||||
|
return self.jsonify(SubnetManager().get_by_id(_id))
|
||||||
|
|
||||||
|
result, type2name = SubnetManager().tree_view()
|
||||||
|
|
||||||
|
return self.jsonify(result=result, type2name=type2name)
|
||||||
|
|
||||||
|
@args_required("cidr")
|
||||||
|
@args_required("parent_id", value_required=False)
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
cidr = request.values.pop("cidr")
|
||||||
|
parent_id = request.values.pop("parent_id")
|
||||||
|
agent_id = request.values.pop("agent_id", None)
|
||||||
|
cron = request.values.pop("cron", None)
|
||||||
|
|
||||||
|
return self.jsonify(SubnetManager().add(cidr, parent_id, agent_id, cron, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
return self.jsonify(id=SubnetManager().update(_id, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
return self.jsonify(id=SubnetManager().delete(_id))
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetScopeView(APIView):
|
||||||
|
url_prefix = ("/ipam/scope", "/ipam/scope/<int:_id>")
|
||||||
|
|
||||||
|
@args_required("parent_id", value_required=False)
|
||||||
|
@args_required("name")
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def post(self):
|
||||||
|
parent_id = request.values.pop("parent_id")
|
||||||
|
name = request.values.pop("name")
|
||||||
|
|
||||||
|
return self.jsonify(SubnetScopeManager().add(parent_id, name))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def put(self, _id):
|
||||||
|
return self.jsonify(id=SubnetScopeManager().update(_id, **request.values))
|
||||||
|
|
||||||
|
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||||
|
app_cli.op.read, app_cli.admin_name)
|
||||||
|
def delete(self, _id):
|
||||||
|
return self.jsonify(id=SubnetScopeManager.delete(_id))
|
@@ -16,7 +16,7 @@ Flask-Cors==4.0.0
|
|||||||
Flask-Login>=0.6.2
|
Flask-Login>=0.6.2
|
||||||
Flask-Migrate==2.5.2
|
Flask-Migrate==2.5.2
|
||||||
Flask-RESTful==0.3.10
|
Flask-RESTful==0.3.10
|
||||||
Flask-SQLAlchemy==2.5.0
|
Flask-SQLAlchemy==3.0.5
|
||||||
future==0.18.3
|
future==0.18.3
|
||||||
gunicorn==21.0.1
|
gunicorn==21.0.1
|
||||||
hvac==2.0.0
|
hvac==2.0.0
|
||||||
@@ -56,3 +56,5 @@ colorama>=0.4.6
|
|||||||
lz4>=4.3.2
|
lz4>=4.3.2
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
jsonpath==0.82.2
|
jsonpath==0.82.2
|
||||||
|
networkx>=3.1
|
||||||
|
ipaddress>=1.0.23
|
||||||
|
@@ -39,9 +39,9 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
|||||||
|
|
||||||
# # cache
|
# # cache
|
||||||
CACHE_TYPE = 'redis'
|
CACHE_TYPE = 'redis'
|
||||||
CACHE_REDIS_HOST = '127.0.0.1'
|
CACHE_REDIS_HOST = env.str('CACHE_REDIS_HOST', default='redis')
|
||||||
CACHE_REDIS_PORT = 6379
|
CACHE_REDIS_PORT = env.str('CACHE_REDIS_PORT', default='6379')
|
||||||
CACHE_REDIS_PASSWORD = ''
|
CACHE_REDIS_PASSWORD = env.str('CACHE_REDIS_PASSWORD', default='')
|
||||||
CACHE_KEY_PREFIX = 'CMDB::'
|
CACHE_KEY_PREFIX = 'CMDB::'
|
||||||
CACHE_DEFAULT_TIMEOUT = 3000
|
CACHE_DEFAULT_TIMEOUT = 3000
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3857903 */
|
font-family: "iconfont"; /* Project id 3857903 */
|
||||||
src: url('iconfont.woff2?t=1725331691589') format('woff2'),
|
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
|
||||||
url('iconfont.woff?t=1725331691589') format('woff'),
|
url('iconfont.woff?t=1732673294759') format('woff'),
|
||||||
url('iconfont.ttf?t=1725331691589') format('truetype');
|
url('iconfont.ttf?t=1732673294759') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,314 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.veops-rear:before {
|
||||||
|
content: "\ea02";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-front:before {
|
||||||
|
content: "\ea03";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-xianggang:before {
|
||||||
|
content: "\ea01";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-veops-device2:before {
|
||||||
|
content: "\ea00";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-veops-room1:before {
|
||||||
|
content: "\e9ff";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-IDC:before {
|
||||||
|
content: "\e9fe";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-region:before {
|
||||||
|
content: "\e9fd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-device:before {
|
||||||
|
content: "\e9fb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-cabinet:before {
|
||||||
|
content: "\e9fc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-data_center:before {
|
||||||
|
content: "\e9f9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-holidays:before {
|
||||||
|
content: "\e9fa";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-itsm-logs:before {
|
||||||
|
content: "\e9f8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-workday:before {
|
||||||
|
content: "\e9f6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-holiday:before {
|
||||||
|
content: "\e9f7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-festival:before {
|
||||||
|
content: "\e9f5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-calc:before {
|
||||||
|
content: "\e9f4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-reports_4:before {
|
||||||
|
content: "\e9f3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-folder:before {
|
||||||
|
content: "\e9f2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-entire_network_:before {
|
||||||
|
content: "\e9f1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-subnet:before {
|
||||||
|
content: "\e9f0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-map_view:before {
|
||||||
|
content: "\e9ef";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-recycle:before {
|
||||||
|
content: "\e9ee";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-catalog:before {
|
||||||
|
content: "\e9ed";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-ipam:before {
|
||||||
|
content: "\e9ec";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmdb-calc:before {
|
||||||
|
content: "\e9eb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-users:before {
|
||||||
|
content: "\e9ea";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tokens:before {
|
||||||
|
content: "\e9e9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-mysql:before {
|
||||||
|
content: "\e9e8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-redis:before {
|
||||||
|
content: "\e9e7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-sign_out:before {
|
||||||
|
content: "\e9e6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-company:before {
|
||||||
|
content: "\e9e5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-emails:before {
|
||||||
|
content: "\e9e4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-switch:before {
|
||||||
|
content: "\e9e3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.qiyeweixin:before {
|
||||||
|
content: "\e9e2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-progress:before {
|
||||||
|
content: "\e9e1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-completed:before {
|
||||||
|
content: "\e9e0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-ticketTime:before {
|
||||||
|
content: "\e9df";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-notification:before {
|
||||||
|
content: "\e9dc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-veops-account1:before {
|
||||||
|
content: "\e9dd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-personal:before {
|
||||||
|
content: "\e9de";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-customer_satisfaction2:before {
|
||||||
|
content: "\e9da";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-over2:before {
|
||||||
|
content: "\e9db";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-search1:before {
|
||||||
|
content: "\e9d9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-customer_satisfaction:before {
|
||||||
|
content: "\e9d8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-over:before {
|
||||||
|
content: "\e9d7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-request:before {
|
||||||
|
content: "\e9d6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-release:before {
|
||||||
|
content: "\e9d5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-link:before {
|
||||||
|
content: "\e9d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-command_record:before {
|
||||||
|
content: "\e9d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-question:before {
|
||||||
|
content: "\e9d2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-sending:before {
|
||||||
|
content: "\e9d1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-dialogue:before {
|
||||||
|
content: "\e9d0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-report2:before {
|
||||||
|
content: "\e9cf";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-delete:before {
|
||||||
|
content: "\e9cd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-knowledge:before {
|
||||||
|
content: "\e9ce";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-article:before {
|
||||||
|
content: "\e9cc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-model_setup1:before {
|
||||||
|
content: "\e9cb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-report:before {
|
||||||
|
content: "\e9ca";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-customer_service:before {
|
||||||
|
content: "\e9c9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-connect1:before {
|
||||||
|
content: "\e9c6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-session1:before {
|
||||||
|
content: "\e9c7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-assets:before {
|
||||||
|
content: "\e9c8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-oneterm-ssh1:before {
|
||||||
|
content: "\e9c3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-oneterm-ssh2:before {
|
||||||
|
content: "\e9c4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-rdp:before {
|
||||||
|
content: "\e9c5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-websphere:before {
|
||||||
|
content: "\e9c2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-vps:before {
|
||||||
|
content: "\e9c1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-F5:before {
|
||||||
|
content: "\e9c0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-HAProxy:before {
|
||||||
|
content: "\e9bf";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-JBoss:before {
|
||||||
|
content: "\e9be";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-dongfangtong:before {
|
||||||
|
content: "\e9bd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-kafka:before {
|
||||||
|
content: "\e9b7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-weblogic:before {
|
||||||
|
content: "\e9b8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-TDSQL:before {
|
||||||
|
content: "\e9b9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-kingbase:before {
|
||||||
|
content: "\e9ba";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-dameng:before {
|
||||||
|
content: "\e9bb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-TIDB:before {
|
||||||
|
content: "\e9bc";
|
||||||
|
}
|
||||||
|
|
||||||
.veops-expand:before {
|
.veops-expand:before {
|
||||||
content: "\e9b6";
|
content: "\e9b6";
|
||||||
}
|
}
|
||||||
@@ -761,11 +1069,11 @@
|
|||||||
content: "\e914";
|
content: "\e914";
|
||||||
}
|
}
|
||||||
|
|
||||||
.itsm-duration:before {
|
.itsm-reports_3:before {
|
||||||
content: "\e913";
|
content: "\e913";
|
||||||
}
|
}
|
||||||
|
|
||||||
.itsm-workload:before {
|
.itsm-reports_2:before {
|
||||||
content: "\e912";
|
content: "\e912";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,6 +5,545 @@
|
|||||||
"css_prefix_text": "",
|
"css_prefix_text": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "42510712",
|
||||||
|
"name": "veops-rear",
|
||||||
|
"font_class": "veops-rear",
|
||||||
|
"unicode": "ea02",
|
||||||
|
"unicode_decimal": 59906
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42510708",
|
||||||
|
"name": "veops-front",
|
||||||
|
"font_class": "veops-front",
|
||||||
|
"unicode": "ea03",
|
||||||
|
"unicode_decimal": 59907
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42497603",
|
||||||
|
"name": "veops-xianggang",
|
||||||
|
"font_class": "veops-xianggang",
|
||||||
|
"unicode": "ea01",
|
||||||
|
"unicode_decimal": 59905
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42485038",
|
||||||
|
"name": "veops-device (2)",
|
||||||
|
"font_class": "a-veops-device2",
|
||||||
|
"unicode": "ea00",
|
||||||
|
"unicode_decimal": 59904
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42455620",
|
||||||
|
"name": "veops-room (1)",
|
||||||
|
"font_class": "a-veops-room1",
|
||||||
|
"unicode": "e9ff",
|
||||||
|
"unicode_decimal": 59903
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42455607",
|
||||||
|
"name": "veops-IDC",
|
||||||
|
"font_class": "veops-IDC",
|
||||||
|
"unicode": "e9fe",
|
||||||
|
"unicode_decimal": 59902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42455609",
|
||||||
|
"name": "veops-region",
|
||||||
|
"font_class": "veops-region",
|
||||||
|
"unicode": "e9fd",
|
||||||
|
"unicode_decimal": 59901
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42448953",
|
||||||
|
"name": "veops-device",
|
||||||
|
"font_class": "veops-device",
|
||||||
|
"unicode": "e9fb",
|
||||||
|
"unicode_decimal": 59899
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42448948",
|
||||||
|
"name": "veops-cabinet",
|
||||||
|
"font_class": "veops-cabinet",
|
||||||
|
"unicode": "e9fc",
|
||||||
|
"unicode_decimal": 59900
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42433324",
|
||||||
|
"name": "veops-data_center",
|
||||||
|
"font_class": "veops-data_center",
|
||||||
|
"unicode": "e9f9",
|
||||||
|
"unicode_decimal": 59897
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42337844",
|
||||||
|
"name": "ops-setting-holiday_management-copy",
|
||||||
|
"font_class": "ops-setting-holidays",
|
||||||
|
"unicode": "e9fa",
|
||||||
|
"unicode_decimal": 59898
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42335414",
|
||||||
|
"name": "itsm-system_log",
|
||||||
|
"font_class": "ops-itsm-logs",
|
||||||
|
"unicode": "e9f8",
|
||||||
|
"unicode_decimal": 59896
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334782",
|
||||||
|
"name": "ops-setting-adjustday",
|
||||||
|
"font_class": "ops-setting-workday",
|
||||||
|
"unicode": "e9f6",
|
||||||
|
"unicode_decimal": 59894
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334768",
|
||||||
|
"name": "ops-setting-holiday",
|
||||||
|
"font_class": "ops-setting-holiday",
|
||||||
|
"unicode": "e9f7",
|
||||||
|
"unicode_decimal": 59895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334734",
|
||||||
|
"name": "ops-setting-festival",
|
||||||
|
"font_class": "ops-setting-festival",
|
||||||
|
"unicode": "e9f5",
|
||||||
|
"unicode_decimal": 59893
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42281202",
|
||||||
|
"name": "itsm-count",
|
||||||
|
"font_class": "itsm-calc",
|
||||||
|
"unicode": "e9f4",
|
||||||
|
"unicode_decimal": 59892
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42270632",
|
||||||
|
"name": "itsm-satisfaction",
|
||||||
|
"font_class": "itsm-reports_4",
|
||||||
|
"unicode": "e9f3",
|
||||||
|
"unicode_decimal": 59891
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205149",
|
||||||
|
"name": "veops-folder",
|
||||||
|
"font_class": "veops-folder",
|
||||||
|
"unicode": "e9f2",
|
||||||
|
"unicode_decimal": 59890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205128",
|
||||||
|
"name": "veops-entire_network_",
|
||||||
|
"font_class": "veops-entire_network_",
|
||||||
|
"unicode": "e9f1",
|
||||||
|
"unicode_decimal": 59889
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205094",
|
||||||
|
"name": "veops-subnet",
|
||||||
|
"font_class": "veops-subnet",
|
||||||
|
"unicode": "e9f0",
|
||||||
|
"unicode_decimal": 59888
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201912",
|
||||||
|
"name": "veops-map_view",
|
||||||
|
"font_class": "veops-map_view",
|
||||||
|
"unicode": "e9ef",
|
||||||
|
"unicode_decimal": 59887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201676",
|
||||||
|
"name": "veops-recycle",
|
||||||
|
"font_class": "veops-recycle",
|
||||||
|
"unicode": "e9ee",
|
||||||
|
"unicode_decimal": 59886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201586",
|
||||||
|
"name": "veops-catalog",
|
||||||
|
"font_class": "veops-catalog",
|
||||||
|
"unicode": "e9ed",
|
||||||
|
"unicode_decimal": 59885
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201534",
|
||||||
|
"name": "veops-ipam",
|
||||||
|
"font_class": "veops-ipam",
|
||||||
|
"unicode": "e9ec",
|
||||||
|
"unicode_decimal": 59884
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42179262",
|
||||||
|
"name": "cmdb-calc",
|
||||||
|
"font_class": "cmdb-calc",
|
||||||
|
"unicode": "e9eb",
|
||||||
|
"unicode_decimal": 59883
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42161413",
|
||||||
|
"name": "ai-users",
|
||||||
|
"font_class": "ai-users",
|
||||||
|
"unicode": "e9ea",
|
||||||
|
"unicode_decimal": 59882
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42161417",
|
||||||
|
"name": "ai-tokens",
|
||||||
|
"font_class": "ai-tokens",
|
||||||
|
"unicode": "e9e9",
|
||||||
|
"unicode_decimal": 59881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42155223",
|
||||||
|
"name": "oneterm-mysql",
|
||||||
|
"font_class": "oneterm-mysql",
|
||||||
|
"unicode": "e9e8",
|
||||||
|
"unicode_decimal": 59880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42155225",
|
||||||
|
"name": "oneterm-redis",
|
||||||
|
"font_class": "oneterm-redis",
|
||||||
|
"unicode": "e9e7",
|
||||||
|
"unicode_decimal": 59879
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154436",
|
||||||
|
"name": "veops-sign_out",
|
||||||
|
"font_class": "veops-sign_out",
|
||||||
|
"unicode": "e9e6",
|
||||||
|
"unicode_decimal": 59878
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154310",
|
||||||
|
"name": "veops-company",
|
||||||
|
"font_class": "veops-company",
|
||||||
|
"unicode": "e9e5",
|
||||||
|
"unicode_decimal": 59877
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154325",
|
||||||
|
"name": "veops-emails",
|
||||||
|
"font_class": "veops-emails",
|
||||||
|
"unicode": "e9e4",
|
||||||
|
"unicode_decimal": 59876
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154350",
|
||||||
|
"name": "veops-switch",
|
||||||
|
"font_class": "veops-switch",
|
||||||
|
"unicode": "e9e3",
|
||||||
|
"unicode_decimal": 59875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154370",
|
||||||
|
"name": "veops-qiyeweixin",
|
||||||
|
"font_class": "qiyeweixin",
|
||||||
|
"unicode": "e9e2",
|
||||||
|
"unicode_decimal": 59874
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42134185",
|
||||||
|
"name": "veops-progress",
|
||||||
|
"font_class": "veops-progress",
|
||||||
|
"unicode": "e9e1",
|
||||||
|
"unicode_decimal": 59873
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42134110",
|
||||||
|
"name": "veops-completed",
|
||||||
|
"font_class": "veops-completed",
|
||||||
|
"unicode": "e9e0",
|
||||||
|
"unicode_decimal": 59872
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42133882",
|
||||||
|
"name": "itsm-ticketTime",
|
||||||
|
"font_class": "itsm-ticketTime",
|
||||||
|
"unicode": "e9df",
|
||||||
|
"unicode_decimal": 59871
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122869",
|
||||||
|
"name": "veops-notification",
|
||||||
|
"font_class": "veops-notification",
|
||||||
|
"unicode": "e9dc",
|
||||||
|
"unicode_decimal": 59868
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122868",
|
||||||
|
"name": "veops-account_password",
|
||||||
|
"font_class": "a-veops-account1",
|
||||||
|
"unicode": "e9dd",
|
||||||
|
"unicode_decimal": 59869
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122861",
|
||||||
|
"name": "veops-personal",
|
||||||
|
"font_class": "veops-personal",
|
||||||
|
"unicode": "e9de",
|
||||||
|
"unicode_decimal": 59870
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42101103",
|
||||||
|
"name": "itsm-evaluation2",
|
||||||
|
"font_class": "itsm-customer_satisfaction2",
|
||||||
|
"unicode": "e9da",
|
||||||
|
"unicode_decimal": 59866
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42101098",
|
||||||
|
"name": "itsm-over2",
|
||||||
|
"font_class": "itsm-over2",
|
||||||
|
"unicode": "e9db",
|
||||||
|
"unicode_decimal": 59867
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42065574",
|
||||||
|
"name": "veops-search",
|
||||||
|
"font_class": "veops-search1",
|
||||||
|
"unicode": "e9d9",
|
||||||
|
"unicode_decimal": 59865
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42063479",
|
||||||
|
"name": "itsm-evaluation",
|
||||||
|
"font_class": "itsm-customer_satisfaction",
|
||||||
|
"unicode": "e9d8",
|
||||||
|
"unicode_decimal": 59864
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42062436",
|
||||||
|
"name": "itsm-over",
|
||||||
|
"font_class": "itsm-over",
|
||||||
|
"unicode": "e9d7",
|
||||||
|
"unicode_decimal": 59863
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42050642",
|
||||||
|
"name": "itsm-requirement",
|
||||||
|
"font_class": "itsm-request",
|
||||||
|
"unicode": "e9d6",
|
||||||
|
"unicode_decimal": 59862
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42050622",
|
||||||
|
"name": "itsm-release",
|
||||||
|
"font_class": "itsm-release",
|
||||||
|
"unicode": "e9d5",
|
||||||
|
"unicode_decimal": 59861
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41903314",
|
||||||
|
"name": "veops-link",
|
||||||
|
"font_class": "veops-link",
|
||||||
|
"unicode": "e9d4",
|
||||||
|
"unicode_decimal": 59860
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41876664",
|
||||||
|
"name": "oneterm-command_record",
|
||||||
|
"font_class": "oneterm-command_record",
|
||||||
|
"unicode": "e9d3",
|
||||||
|
"unicode_decimal": 59859
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859436",
|
||||||
|
"name": "ai-question",
|
||||||
|
"font_class": "ai-question",
|
||||||
|
"unicode": "e9d2",
|
||||||
|
"unicode_decimal": 59858
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859414",
|
||||||
|
"name": "ai-sending",
|
||||||
|
"font_class": "ai-sending",
|
||||||
|
"unicode": "e9d1",
|
||||||
|
"unicode_decimal": 59857
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859374",
|
||||||
|
"name": "ai-dialogue",
|
||||||
|
"font_class": "ai-dialogue",
|
||||||
|
"unicode": "e9d0",
|
||||||
|
"unicode_decimal": 59856
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859191",
|
||||||
|
"name": "ai-report2",
|
||||||
|
"font_class": "ai-report2",
|
||||||
|
"unicode": "e9cf",
|
||||||
|
"unicode_decimal": 59855
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41858720",
|
||||||
|
"name": "ai-delete",
|
||||||
|
"font_class": "ai-delete",
|
||||||
|
"unicode": "e9cd",
|
||||||
|
"unicode_decimal": 59853
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41858484",
|
||||||
|
"name": "caise-knowledge",
|
||||||
|
"font_class": "caise-knowledge",
|
||||||
|
"unicode": "e9ce",
|
||||||
|
"unicode_decimal": 59854
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41833445",
|
||||||
|
"name": "ai-article",
|
||||||
|
"font_class": "ai-article",
|
||||||
|
"unicode": "e9cc",
|
||||||
|
"unicode_decimal": 59852
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811974",
|
||||||
|
"name": "ai-model_setup (1)",
|
||||||
|
"font_class": "ai-model_setup1",
|
||||||
|
"unicode": "e9cb",
|
||||||
|
"unicode_decimal": 59851
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811980",
|
||||||
|
"name": "ai-report",
|
||||||
|
"font_class": "ai-report",
|
||||||
|
"unicode": "e9ca",
|
||||||
|
"unicode_decimal": 59850
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811915",
|
||||||
|
"name": "ai-customer_service",
|
||||||
|
"font_class": "ai-customer_service",
|
||||||
|
"unicode": "e9c9",
|
||||||
|
"unicode_decimal": 59849
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41735717",
|
||||||
|
"name": "oneterm-connect",
|
||||||
|
"font_class": "oneterm-connect1",
|
||||||
|
"unicode": "e9c6",
|
||||||
|
"unicode_decimal": 59846
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41735716",
|
||||||
|
"name": "oneterm-session",
|
||||||
|
"font_class": "oneterm-session1",
|
||||||
|
"unicode": "e9c7",
|
||||||
|
"unicode_decimal": 59847
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41735703",
|
||||||
|
"name": "oneterm-assets",
|
||||||
|
"font_class": "oneterm-assets",
|
||||||
|
"unicode": "e9c8",
|
||||||
|
"unicode_decimal": 59848
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41725683",
|
||||||
|
"name": "oneterm-RDP",
|
||||||
|
"font_class": "a-oneterm-ssh1",
|
||||||
|
"unicode": "e9c3",
|
||||||
|
"unicode_decimal": 59843
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41725684",
|
||||||
|
"name": "oneterm-SSH",
|
||||||
|
"font_class": "a-oneterm-ssh2",
|
||||||
|
"unicode": "e9c4",
|
||||||
|
"unicode_decimal": 59844
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41725685",
|
||||||
|
"name": "oneterm-VNC",
|
||||||
|
"font_class": "oneterm-rdp",
|
||||||
|
"unicode": "e9c5",
|
||||||
|
"unicode_decimal": 59845
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41724497",
|
||||||
|
"name": "caise-websphere",
|
||||||
|
"font_class": "caise-websphere",
|
||||||
|
"unicode": "e9c2",
|
||||||
|
"unicode_decimal": 59842
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41724575",
|
||||||
|
"name": "caise-vps",
|
||||||
|
"font_class": "caise-vps",
|
||||||
|
"unicode": "e9c1",
|
||||||
|
"unicode_decimal": 59841
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41724631",
|
||||||
|
"name": "caise-F5",
|
||||||
|
"font_class": "caise-F5",
|
||||||
|
"unicode": "e9c0",
|
||||||
|
"unicode_decimal": 59840
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41724653",
|
||||||
|
"name": "caise-HAProxy",
|
||||||
|
"font_class": "caise-HAProxy",
|
||||||
|
"unicode": "e9bf",
|
||||||
|
"unicode_decimal": 59839
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722953",
|
||||||
|
"name": "caise-JBoss",
|
||||||
|
"font_class": "caise-JBoss",
|
||||||
|
"unicode": "e9be",
|
||||||
|
"unicode_decimal": 59838
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722960",
|
||||||
|
"name": "caise-dongfangtong",
|
||||||
|
"font_class": "caise-dongfangtong",
|
||||||
|
"unicode": "e9bd",
|
||||||
|
"unicode_decimal": 59837
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722681",
|
||||||
|
"name": "caise-kafka",
|
||||||
|
"font_class": "caise-kafka",
|
||||||
|
"unicode": "e9b7",
|
||||||
|
"unicode_decimal": 59831
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722680",
|
||||||
|
"name": "caise-weblogic",
|
||||||
|
"font_class": "caise-weblogic",
|
||||||
|
"unicode": "e9b8",
|
||||||
|
"unicode_decimal": 59832
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722679",
|
||||||
|
"name": "caise-TDSQL",
|
||||||
|
"font_class": "caise-TDSQL",
|
||||||
|
"unicode": "e9b9",
|
||||||
|
"unicode_decimal": 59833
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722678",
|
||||||
|
"name": "caise-kingbase",
|
||||||
|
"font_class": "caise-kingbase",
|
||||||
|
"unicode": "e9ba",
|
||||||
|
"unicode_decimal": 59834
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722677",
|
||||||
|
"name": "达梦",
|
||||||
|
"font_class": "caise-dameng",
|
||||||
|
"unicode": "e9bb",
|
||||||
|
"unicode_decimal": 59835
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41722675",
|
||||||
|
"name": "caise-TIDB",
|
||||||
|
"font_class": "caise-TIDB",
|
||||||
|
"unicode": "e9bc",
|
||||||
|
"unicode_decimal": 59836
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "41681675",
|
"icon_id": "41681675",
|
||||||
"name": "veops-expand",
|
"name": "veops-expand",
|
||||||
@@ -1317,14 +1856,14 @@
|
|||||||
{
|
{
|
||||||
"icon_id": "39926816",
|
"icon_id": "39926816",
|
||||||
"name": "itsm-duration",
|
"name": "itsm-duration",
|
||||||
"font_class": "itsm-duration",
|
"font_class": "itsm-reports_3",
|
||||||
"unicode": "e913",
|
"unicode": "e913",
|
||||||
"unicode_decimal": 59667
|
"unicode_decimal": 59667
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "39926833",
|
"icon_id": "39926833",
|
||||||
"name": "itsm-workload (1)",
|
"name": "itsm-workload (1)",
|
||||||
"font_class": "itsm-workload",
|
"font_class": "itsm-reports_2",
|
||||||
"unicode": "e912",
|
"unicode": "e912",
|
||||||
"unicode_decimal": 59666
|
"unicode_decimal": 59666
|
||||||
},
|
},
|
||||||
|
@@ -139,9 +139,9 @@
|
|||||||
:normalizer="
|
:normalizer="
|
||||||
(node) => {
|
(node) => {
|
||||||
return {
|
return {
|
||||||
id: String(node[0] || ''),
|
id: node.id,
|
||||||
label: node[1] ? node[1].label || node[0] : node[0],
|
label: node.label,
|
||||||
children: node.children && node.children.length ? node.children : undefined,
|
children: node.children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -352,8 +352,12 @@ export default {
|
|||||||
},
|
},
|
||||||
getChoiceValueByProperty(property) {
|
getChoiceValueByProperty(property) {
|
||||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||||
if (_find) {
|
if (_find?.choice_value?.length) {
|
||||||
return _find.choice_value
|
return _find.choice_value.map((node) => ({
|
||||||
|
id: String(node?.[0] ?? ''),
|
||||||
|
label: node?.[1]?.label || node?.[0] || '',
|
||||||
|
children: node?.children?.length ? node.children : undefined
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
|
@@ -177,7 +177,7 @@ export const linearIconList = [
|
|||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'icon-xianxing-application',
|
value: 'icon-xianxing-application',
|
||||||
label: '应用',
|
label: '常用组件',
|
||||||
list: [{
|
list: [{
|
||||||
value: 'icon-xianxing-yilianjie',
|
value: 'icon-xianxing-yilianjie',
|
||||||
label: '已连接'
|
label: '已连接'
|
||||||
@@ -517,7 +517,7 @@ export const fillIconList = [
|
|||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'icon-shidi-application',
|
value: 'icon-shidi-application',
|
||||||
label: '应用',
|
label: '常用组件',
|
||||||
list: [{
|
list: [{
|
||||||
value: 'icon-shidi-yilianjie',
|
value: 'icon-shidi-yilianjie',
|
||||||
label: '已连接'
|
label: '已连接'
|
||||||
@@ -729,6 +729,18 @@ export const multicolorIconList = [
|
|||||||
value: 'database',
|
value: 'database',
|
||||||
label: '数据库',
|
label: '数据库',
|
||||||
list: [{
|
list: [{
|
||||||
|
value: 'caise-TIDB',
|
||||||
|
label: 'TIDB'
|
||||||
|
}, {
|
||||||
|
value: 'caise-dameng',
|
||||||
|
label: '达梦'
|
||||||
|
}, {
|
||||||
|
value: 'caise-kingbase',
|
||||||
|
label: 'KingBase'
|
||||||
|
}, {
|
||||||
|
value: 'caise-TDSQL',
|
||||||
|
label: 'TDSQL'
|
||||||
|
}, {
|
||||||
value: 'caise-DB2',
|
value: 'caise-DB2',
|
||||||
label: 'DB2'
|
label: 'DB2'
|
||||||
}, {
|
}, {
|
||||||
@@ -809,6 +821,9 @@ export const multicolorIconList = [
|
|||||||
value: 'system',
|
value: 'system',
|
||||||
label: '操作系统',
|
label: '操作系统',
|
||||||
list: [{
|
list: [{
|
||||||
|
value: 'ciase-aix',
|
||||||
|
label: 'aix'
|
||||||
|
}, {
|
||||||
value: 'caise-Windows',
|
value: 'caise-Windows',
|
||||||
label: 'Windows'
|
label: 'Windows'
|
||||||
}, {
|
}, {
|
||||||
@@ -903,8 +918,38 @@ export const multicolorIconList = [
|
|||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
value: 'caise-application',
|
value: 'caise-application',
|
||||||
label: '应用',
|
label: '常用组件',
|
||||||
list: [{
|
list: [{
|
||||||
|
value: 'caise-websphere',
|
||||||
|
label: 'WebSphere'
|
||||||
|
}, {
|
||||||
|
value: 'caise-vps',
|
||||||
|
label: 'VPS'
|
||||||
|
}, {
|
||||||
|
value: 'caise-F5',
|
||||||
|
label: 'F5'
|
||||||
|
}, {
|
||||||
|
value: 'caise-HAProxy',
|
||||||
|
label: 'HAProxy'
|
||||||
|
}, {
|
||||||
|
value: 'caise-kafka',
|
||||||
|
label: 'kafka'
|
||||||
|
}, {
|
||||||
|
value: 'caise-dongfangtong',
|
||||||
|
label: '东方通'
|
||||||
|
}, {
|
||||||
|
value: 'cmdb-vcenter',
|
||||||
|
label: 'VCenter'
|
||||||
|
}, {
|
||||||
|
value: 'ops-KVM',
|
||||||
|
label: 'KVM'
|
||||||
|
}, {
|
||||||
|
value: 'caise-JBoss',
|
||||||
|
label: 'JBoss'
|
||||||
|
}, {
|
||||||
|
value: 'caise-weblogic',
|
||||||
|
label: 'WebLogic'
|
||||||
|
}, {
|
||||||
value: 'caise-disk_array',
|
value: 'caise-disk_array',
|
||||||
label: '磁盘阵列'
|
label: '磁盘阵列'
|
||||||
}, {
|
}, {
|
||||||
@@ -928,9 +973,6 @@ export const multicolorIconList = [
|
|||||||
}, {
|
}, {
|
||||||
value: 'caise_pool',
|
value: 'caise_pool',
|
||||||
label: 'ip池'
|
label: 'ip池'
|
||||||
}, {
|
|
||||||
value: 'ciase-aix',
|
|
||||||
label: 'aix'
|
|
||||||
}, {
|
}, {
|
||||||
value: 'caise-storage_volume1',
|
value: 'caise-storage_volume1',
|
||||||
label: '存储卷'
|
label: '存储卷'
|
||||||
|
24
cmdb-ui/src/components/Menu/index.module.less
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.cmdb-side-menu-search {
|
||||||
|
background-color: #FFFFFF !important;
|
||||||
|
cursor: auto !important;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
max-width: 170px !important;
|
||||||
|
width: 170px;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-suffix {
|
||||||
|
right: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,8 @@ import {
|
|||||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||||
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||||
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
|
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
|
||||||
|
import styles from './index.module.less'
|
||||||
|
import { mapActions } from 'vuex'
|
||||||
|
|
||||||
const { Item, SubMenu } = Menu
|
const { Item, SubMenu } = Menu
|
||||||
|
|
||||||
@@ -40,7 +42,8 @@ export default {
|
|||||||
openKeys: [],
|
openKeys: [],
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
cachedOpenKeys: [],
|
cachedOpenKeys: [],
|
||||||
resource_type: {}
|
resource_type: {},
|
||||||
|
currentAppRoute: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -64,6 +67,7 @@ export default {
|
|||||||
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
||||||
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
||||||
})
|
})
|
||||||
|
this.currentAppRoute = this.$route?.matched?.[0]?.name || ''
|
||||||
this.updateMenu()
|
this.updateMenu()
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -75,12 +79,14 @@ export default {
|
|||||||
this.openKeys = this.cachedOpenKeys
|
this.openKeys = this.cachedOpenKeys
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
$route: function () {
|
$route: function (route) {
|
||||||
|
this.currentAppRoute = route?.matched?.[0]?.name
|
||||||
this.updateMenu()
|
this.updateMenu()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inject: ['reload'],
|
inject: ['reload'],
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(['UpdateCMDBSEarchValue']),
|
||||||
cancelAttributes(e, menu) {
|
cancelAttributes(e, menu) {
|
||||||
const that = this
|
const that = this
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -286,6 +292,47 @@ export default {
|
|||||||
this.$message.error(this.$t('noPermission'))
|
this.$message.error(this.$t('noPermission'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
jumpCMDBSearch(value) {
|
||||||
|
this.UpdateCMDBSEarchValue(value)
|
||||||
|
|
||||||
|
if (this.$route.name !== 'cmdb_resource_search') {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'cmdb_resource_search',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCMDBSearch() {
|
||||||
|
if (this.currentAppRoute !== 'cmdb' || this.collapsed) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item class={styles['cmdb-side-menu-search']}>
|
||||||
|
<a-input
|
||||||
|
ref="cmdbSideMenuSearchInputRef"
|
||||||
|
class={styles['cmdb-side-menu-search-input']}
|
||||||
|
style={{
|
||||||
|
border: this.$route.name === 'cmdb_resource_search' ? 'solid 1px #B1C9FF' : ''
|
||||||
|
}}
|
||||||
|
placeholder={this.$t('cmdbSearch')}
|
||||||
|
onPressEnter={(e) => {
|
||||||
|
this.jumpCMDBSearch(e.target.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
slot="suffix"
|
||||||
|
type="veops-search1"
|
||||||
|
onClick={() => {
|
||||||
|
const value = this.$refs?.cmdbSideMenuSearchInputRef?.$refs?.input?.value || ''
|
||||||
|
this.jumpCMDBSearch(value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a-input>
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -313,6 +360,7 @@ export default {
|
|||||||
// {...{ props, on: on }}
|
// {...{ props, on: on }}
|
||||||
return (
|
return (
|
||||||
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
||||||
|
{this.renderCMDBSearch()}
|
||||||
{menuTree}
|
{menuTree}
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
|
@@ -66,6 +66,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '#f7f8fa',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
|
calcBasedParent: {
|
||||||
|
type: Boolean,
|
||||||
|
defualt: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -95,7 +99,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paneLengthPercent() {
|
paneLengthPercent() {
|
||||||
const clientRectWidth = this.parentContainer
|
const clientRectWidth = this.parentContainer && this.calcBasedParent
|
||||||
? this.parentContainer.clientWidth
|
? this.parentContainer.clientWidth
|
||||||
: document.documentElement.getBoundingClientRect().width
|
: document.documentElement.getBoundingClientRect().width
|
||||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
:paneLengthPixel.sync="paneLengthPixel"
|
:paneLengthPixel.sync="paneLengthPixel"
|
||||||
:appName="appName"
|
:appName="appName"
|
||||||
:triggerColor="triggerColor"
|
:triggerColor="triggerColor"
|
||||||
:triggerLength="18"
|
:triggerLength="triggerLength"
|
||||||
|
:calcBasedParent="calcBasedParent"
|
||||||
>
|
>
|
||||||
<template #one>
|
<template #one>
|
||||||
<div class="two-column-layout-sidebar">
|
<div class="two-column-layout-sidebar">
|
||||||
@@ -37,6 +38,14 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '#f7f8fa',
|
default: '#f7f8fa',
|
||||||
},
|
},
|
||||||
|
triggerLength: {
|
||||||
|
type: Number,
|
||||||
|
default: 18
|
||||||
|
},
|
||||||
|
calcBasedParent: {
|
||||||
|
type: Boolean,
|
||||||
|
defualt: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -11,38 +11,11 @@
|
|||||||
<span class="common-settings-btn-text">{{ $t('settings') }}</span>
|
<span class="common-settings-btn-text">{{ $t('settings') }}</span>
|
||||||
</span>
|
</span>
|
||||||
<a-popover
|
<a-popover
|
||||||
overlayClassName="lang-popover-wrap"
|
|
||||||
placement="bottomRight"
|
|
||||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
|
||||||
>
|
|
||||||
<span class="locale">{{ languageList.find((lang) => lang.key === locale).title }}</span>
|
|
||||||
<div class="lang-menu" slot="content">
|
|
||||||
<a
|
|
||||||
v-for="(lang) in languageList"
|
|
||||||
:key="lang.key"
|
|
||||||
:class="['lang-menu-item', lang.key === locale ? 'lang-menu-item_active' : '']"
|
|
||||||
@click="changeLang(lang.key)"
|
|
||||||
>
|
|
||||||
{{ lang.title }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</a-popover>
|
|
||||||
<a-popover
|
|
||||||
:overlayStyle="{ width: '130px' }"
|
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
overlayClassName="custom-user"
|
overlayClassName="custom-user"
|
||||||
>
|
>
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }">
|
<UserPanel />
|
||||||
<div class="custom-user-item">
|
|
||||||
<a-icon type="user" :style="{ marginRight: '10px' }" />
|
|
||||||
<span>{{ $t('topMenu.personalCenter') }}</span>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
<div @click="handleLogout" class="custom-user-item">
|
|
||||||
<a-icon type="logout" :style="{ marginRight: '10px' }" />
|
|
||||||
<span>{{ $t('topMenu.logout') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<span class="action ant-dropdown-link user-dropdown-menu user-info-wrap">
|
<span class="action ant-dropdown-link user-dropdown-menu user-info-wrap">
|
||||||
<a-avatar
|
<a-avatar
|
||||||
@@ -63,11 +36,13 @@
|
|||||||
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
|
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
|
||||||
import DocumentLink from './DocumentLink.vue'
|
import DocumentLink from './DocumentLink.vue'
|
||||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||||
|
import UserPanel from './userPanel.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserMenu',
|
name: 'UserMenu',
|
||||||
components: {
|
components: {
|
||||||
DocumentLink,
|
DocumentLink,
|
||||||
|
UserPanel
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
487
cmdb-ui/src/components/tools/userPanel.vue
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
<template>
|
||||||
|
<div class="user-panel">
|
||||||
|
<a-avatar
|
||||||
|
class="user-panel-avatar"
|
||||||
|
size="small"
|
||||||
|
icon="user"
|
||||||
|
:src="avatarSrc"
|
||||||
|
/>
|
||||||
|
<div class="user-panel-nickname">
|
||||||
|
{{ userInfo.nickname }}
|
||||||
|
</div>
|
||||||
|
<div class="user-panel-info">
|
||||||
|
<ops-icon
|
||||||
|
type="veops-company"
|
||||||
|
class="user-panel-info-icon"
|
||||||
|
/>
|
||||||
|
<div class="user-panel-info-text">
|
||||||
|
{{ companyName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-panel-info">
|
||||||
|
<ops-icon
|
||||||
|
type="veops-emails"
|
||||||
|
class="user-panel-info-icon"
|
||||||
|
/>
|
||||||
|
<div class="user-panel-info-text">
|
||||||
|
{{ email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-btn">
|
||||||
|
<div
|
||||||
|
v-for="(item) in userBtnGroup"
|
||||||
|
:key="item.type"
|
||||||
|
class="user-panel-btn-item"
|
||||||
|
@click="clickBtnGroup(item.type)"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
:type="item.icon"
|
||||||
|
class="user-panel-btn-icon"
|
||||||
|
/>
|
||||||
|
<span class="user-panel-btn-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-row">
|
||||||
|
<div class="user-panel-row-label">
|
||||||
|
{{ $t('userPanel.switchLanguage') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-lang">
|
||||||
|
<div
|
||||||
|
v-for="(lang, index) in languageList"
|
||||||
|
:key="index"
|
||||||
|
:class="['user-panel-lang-item', lang.key === locale ? 'user-panel-lang-item_active' : '']"
|
||||||
|
@click="changeLang(lang.key)"
|
||||||
|
>
|
||||||
|
{{ lang.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-row">
|
||||||
|
<div class="user-panel-row-label">
|
||||||
|
{{ $t('userPanel.bindAccount') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-bind">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="(item) in bindList"
|
||||||
|
:key="item.type"
|
||||||
|
:title="$t(item.title)"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
class="user-panel-bind-item"
|
||||||
|
:type="userInfo.notice_info && userInfo.notice_info[item.type] ? item.existedIcon : item.icon"
|
||||||
|
@click="handleBindInfo(item.type)"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-panel-account">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in accountActions"
|
||||||
|
:key="index"
|
||||||
|
class="user-panel-account-item"
|
||||||
|
@click="handleLogout"
|
||||||
|
>
|
||||||
|
<ops-icon class="user-panel-account-icon" :type="item.icon" />
|
||||||
|
<span class="user-panel-account-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapState, mapMutations } from 'vuex'
|
||||||
|
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||||
|
import {
|
||||||
|
bindPlatformByUid,
|
||||||
|
unbindPlatformByUid,
|
||||||
|
} from '@/api/employee'
|
||||||
|
import { getCompanyInfo } from '@/api/company'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UserPanel',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userBtnGroup: [
|
||||||
|
{
|
||||||
|
icon: 'veops-personal',
|
||||||
|
title: 'userPanel.myProfile',
|
||||||
|
type: 'myProfile'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'a-veops-account1',
|
||||||
|
title: 'userPanel.accountPassword',
|
||||||
|
type: 'accountPassword'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
languageList: [
|
||||||
|
{
|
||||||
|
title: '简中',
|
||||||
|
key: 'zh'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'EN',
|
||||||
|
key: 'en'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bindList: [
|
||||||
|
{
|
||||||
|
type: 'wechatApp',
|
||||||
|
icon: 'qiyeweixin',
|
||||||
|
existedIcon: 'wechatApp',
|
||||||
|
title: 'wechat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'feishuApp',
|
||||||
|
icon: 'ops-setting-notice-feishu-selected',
|
||||||
|
existedIcon: 'feishuApp',
|
||||||
|
title: 'feishu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dingdingApp',
|
||||||
|
icon: 'ops-setting-notice-dingding-selected',
|
||||||
|
existedIcon: 'dingdingApp',
|
||||||
|
title: 'dingding'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accountActions: [
|
||||||
|
{
|
||||||
|
icon: 'veops-switch',
|
||||||
|
title: 'userPanel.switchAccount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'veops-sign_out',
|
||||||
|
title: 'userPanel.logout'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hoverBindAccountList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
email: (state) => state.user.email,
|
||||||
|
locale: (state) => state.locale,
|
||||||
|
userInfo: (state) => state.user,
|
||||||
|
companyName: (state) => state.company.name
|
||||||
|
}),
|
||||||
|
avatarSrc() {
|
||||||
|
const avatar = this.userInfo.avatar
|
||||||
|
if (!avatar) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatar.startsWith('https') ? avatar : `/api/common-setting/v1/file/${avatar}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.companyName === undefined) {
|
||||||
|
this.getCompanyInfo()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['Logout', 'GetInfo']),
|
||||||
|
...mapMutations(['SET_LOCALE', 'SET_COMPANY_NAME']),
|
||||||
|
async getCompanyInfo() {
|
||||||
|
const res = await getCompanyInfo()
|
||||||
|
const name = res?.info?.name || ''
|
||||||
|
this.SET_COMPANY_NAME(name)
|
||||||
|
},
|
||||||
|
|
||||||
|
changeLang(lang) {
|
||||||
|
this.SET_LOCALE(lang)
|
||||||
|
this.$i18n.locale = lang
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleBindInfo(platform) {
|
||||||
|
const isBind = this?.userInfo?.notice_info?.[platform]
|
||||||
|
const uid = this?.userInfo?.uid
|
||||||
|
|
||||||
|
if (isBind) {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('warning'),
|
||||||
|
content: this.$t('cs.person.confirmUnbind'),
|
||||||
|
onOk: () => {
|
||||||
|
unbindPlatformByUid(platform, uid)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('cs.person.unbindSuccess'))
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.GetInfo()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bindPlatformByUid(platform, uid)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('cs.person.bindSuccess'))
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.GetInfo()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleLogout() {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('tip'),
|
||||||
|
content: this.$t('topMenu.confirmLogout'),
|
||||||
|
onOk: () => {
|
||||||
|
this.Logout()
|
||||||
|
},
|
||||||
|
onCancel() {},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clickBtnGroup(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'myProfile':
|
||||||
|
if (this.$route.name === 'setting_person') {
|
||||||
|
this.$bus.$emit('changeSettingPersonCurrent', '1')
|
||||||
|
} else {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'setting_person',
|
||||||
|
query: {
|
||||||
|
current: '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'accountPassword':
|
||||||
|
if (this.$route.name === 'setting_person') {
|
||||||
|
this.$bus.$emit('changeSettingPersonCurrent', '2')
|
||||||
|
} else {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'setting_person',
|
||||||
|
query: {
|
||||||
|
current: '2'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBindAccountMouse(type, isHover) {
|
||||||
|
const index = this.hoverBindAccountList.findIndex((item) => item === type)
|
||||||
|
if (isHover && index === -1) {
|
||||||
|
this.hoverBindAccountList.push(type)
|
||||||
|
} else if (!isHover && index !== -1) {
|
||||||
|
this.hoverBindAccountList.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.user-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 350px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
&-avatar {
|
||||||
|
width: 62px;
|
||||||
|
height: 62px;
|
||||||
|
border-radius: 62px;
|
||||||
|
margin-top: 13px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #000000;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
font-size: 48px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-nickname {
|
||||||
|
color: #1D2129;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 6px;
|
||||||
|
margin-top: 6px;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 72px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 11px;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #CACDD9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1D2129;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
|
||||||
|
.user-panel-btn-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-panel-btn-title {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-row {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-lang {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 28px;
|
||||||
|
width: 108px;
|
||||||
|
border-radius: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex: 1;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-right: solid 1px #E4E7ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_active {
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-bind {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 22px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-account {
|
||||||
|
margin-top: 22px;
|
||||||
|
padding-top: 13px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-top: solid 1px #F0F1F5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #CACDD9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #86909C;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.user-panel-account-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-panel-account-title {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -108,6 +108,7 @@ export default {
|
|||||||
visual: 'Visual',
|
visual: 'Visual',
|
||||||
default: 'default',
|
default: 'default',
|
||||||
tip: 'Tip',
|
tip: 'Tip',
|
||||||
|
cmdbSearch: 'Search',
|
||||||
pagination: {
|
pagination: {
|
||||||
total: '{range0}-{range1} of {total} items'
|
total: '{range0}-{range1} of {total} items'
|
||||||
},
|
},
|
||||||
@@ -167,6 +168,15 @@ export default {
|
|||||||
monetaryAmount: 'monetary amount',
|
monetaryAmount: 'monetary amount',
|
||||||
custom: 'custom',
|
custom: 'custom',
|
||||||
},
|
},
|
||||||
|
userPanel: {
|
||||||
|
myProfile: 'My Profile',
|
||||||
|
accountPassword: 'Password',
|
||||||
|
notice: 'Notice',
|
||||||
|
switchLanguage: 'Switch Language',
|
||||||
|
bindAccount: 'Bind Account',
|
||||||
|
switchAccount: 'Switch Account',
|
||||||
|
logout: 'Logout'
|
||||||
|
},
|
||||||
cmdb: cmdb_en,
|
cmdb: cmdb_en,
|
||||||
cs: cs_en,
|
cs: cs_en,
|
||||||
acl: acl_en,
|
acl: acl_en,
|
||||||
|
@@ -108,6 +108,7 @@ export default {
|
|||||||
visual: '虚拟',
|
visual: '虚拟',
|
||||||
default: '默认',
|
default: '默认',
|
||||||
tip: '提示',
|
tip: '提示',
|
||||||
|
cmdbSearch: '搜索一下',
|
||||||
pagination: {
|
pagination: {
|
||||||
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
||||||
},
|
},
|
||||||
@@ -167,6 +168,15 @@ export default {
|
|||||||
monetaryAmount: '货币金额',
|
monetaryAmount: '货币金额',
|
||||||
custom: '自定义',
|
custom: '自定义',
|
||||||
},
|
},
|
||||||
|
userPanel: {
|
||||||
|
myProfile: '个人中心',
|
||||||
|
accountPassword: '账号密码',
|
||||||
|
notice: '通知中心',
|
||||||
|
switchLanguage: '切换语言',
|
||||||
|
bindAccount: '绑定账号',
|
||||||
|
switchAccount: '切换账号',
|
||||||
|
logout: '退出账号'
|
||||||
|
},
|
||||||
cmdb: cmdb_zh,
|
cmdb: cmdb_zh,
|
||||||
cs: cs_zh,
|
cs: cs_zh,
|
||||||
acl: acl_zh,
|
acl: acl_zh,
|
||||||
|
@@ -81,3 +81,11 @@ export function searchCIRelationFull(params) {
|
|||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function searchCIRelationPath(data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_relations/path/s`,
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -74,3 +74,11 @@ export function getCanEditByParentIdChildId(parent_id, child_id) {
|
|||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCITypeRelationPath(params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_type_relations/path`,
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
86
cmdb-ui/src/modules/cmdb/api/dcim.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
|
export function getDCIMTreeView(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/dcim/tree_view ',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDCIMById(type, id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/${type}/${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postDCIM(type, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/${type}`,
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putDCIM(type, id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/${type}/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDCIM(type, id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/${type}/${id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDCIMRacks(id, params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/server_room/${id}/racks`,
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postDevice(rackId, deviceId, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/rack/${rackId}/device/${deviceId}`,
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDevice(rackId, deviceId) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/rack/${rackId}/device/${deviceId}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putDevice(rackId, deviceId, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/rack/${rackId}/device/${deviceId}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function migrateDevice(rackId, deviceId, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/rack/${rackId}/device/${deviceId}/migrate`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDCIMHistoryOperate(params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/dcim/history/operate`,
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
109
cmdb-ui/src/modules/cmdb/api/ipam.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
|
export function getIPAMSubnet() {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMSubnet(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMSubnetById(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putIPAMSubnet(id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIPAMSubnet(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMScope(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/scope',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putIPAMScope(id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/scope/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIPAMScope(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/scope/${id}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMAddress(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/address',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHosts(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet/hosts',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMAddress(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/address',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHistoryOperate(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/history/operate',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHistoryScan(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/history/scan',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMStats(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/stats',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
import { axios } from '@/utils/request'
|
import { axios } from '@/utils/request'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
import i18n from '@/lang'
|
||||||
|
|
||||||
export function getPreference(instance = true, tree = null) {
|
export function getPreference(instance = true, tree = null) {
|
||||||
return axios({
|
return axios({
|
||||||
@@ -16,11 +18,35 @@ export function getPreference2(instance = true, tree = null) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubscribeAttributes(ciTypeId) {
|
export function getSubscribeAttributes(ciTypeId, formatDefaultAttr = true) {
|
||||||
return axios({
|
return new Promise(async (resolve) => {
|
||||||
|
const res = await axios({
|
||||||
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
|
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
formatDefaultAttr &&
|
||||||
|
res?.attributes?.length
|
||||||
|
) {
|
||||||
|
res.attributes.forEach((item) => {
|
||||||
|
switch (item.name) {
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_USER:
|
||||||
|
item.id = item.name
|
||||||
|
item.alias = i18n.t('cmdb.components.updater')
|
||||||
|
break
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_TIME:
|
||||||
|
item.id = item.name
|
||||||
|
item.alias = i18n.t('cmdb.components.updateTime')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubscribeTreeView() {
|
export function getSubscribeTreeView() {
|
||||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/dcim_null.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/firewall_front.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/firewall_rear.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/raid_front.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/raid_rear.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/router_front.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/router_rear.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/server_front.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/server_rear.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/switch_front.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device/switch_rear.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/device_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/rack.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/rack_front_part.png
Normal file
After Width: | Height: | Size: 554 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/dcim/rack_rear_part.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
cmdb-ui/src/modules/cmdb/assets/ipam_address_null.png
Normal file
After Width: | Height: | Size: 25 KiB |
@@ -22,7 +22,7 @@
|
|||||||
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
|
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
|
||||||
<div
|
<div
|
||||||
@dblclick="changeSingleItem(item)"
|
@dblclick="changeSingleItem(item)"
|
||||||
v-for="item in filteredItems"
|
v-for="item in filterDefaultAttr(filteredItems)"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:style="{ height: '38px' }"
|
:style="{ height: '38px' }"
|
||||||
>
|
>
|
||||||
@@ -54,11 +54,44 @@
|
|||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="rightDefaultAttrList.length"
|
||||||
|
class="default-attr"
|
||||||
|
>
|
||||||
|
<a-divider>
|
||||||
|
<span class="default-attr-divider">
|
||||||
|
{{ $t('cmdb.components.default') }}
|
||||||
|
</span>
|
||||||
|
</a-divider>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(item) in rightDefaultAttrList"
|
||||||
|
:key="item.key"
|
||||||
|
:class="['default-attr-item', selectedKeys.includes(item.key) ? 'default-attr-item-selected' : '']"
|
||||||
|
@click="setSelectedKeys(item)"
|
||||||
|
@dblclick="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="default-attr-arrow"
|
||||||
|
style="left: 17px"
|
||||||
|
@click.stop="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<a-icon type="left" />
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
||||||
<div
|
<div
|
||||||
@dblclick="changeSingleItem(item)"
|
@dblclick="changeSingleItem(item)"
|
||||||
v-for="item in filteredItems"
|
v-for="item in filterDefaultAttr(filteredItems)"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:style="{ height: '38px' }"
|
:style="{ height: '38px' }"
|
||||||
>
|
>
|
||||||
@@ -82,6 +115,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="leftDefaultAttrList.length"
|
||||||
|
class="default-attr"
|
||||||
|
>
|
||||||
|
<a-divider>
|
||||||
|
<span class="default-attr-divider">
|
||||||
|
{{ $t('cmdb.components.default') }}
|
||||||
|
</span>
|
||||||
|
</a-divider>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(item) in leftDefaultAttrList"
|
||||||
|
:key="item.key"
|
||||||
|
:class="['default-attr-item', selectedKeys.includes(item.key) ? 'default-attr-item-selected' : '']"
|
||||||
|
@click="setSelectedKeys(item)"
|
||||||
|
@dblclick="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="default-attr-arrow"
|
||||||
|
style="left: 2px"
|
||||||
|
@click.stop="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<a-icon type="right" />
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-transfer>
|
</a-transfer>
|
||||||
@@ -95,6 +161,7 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AttributesTransfer',
|
name: 'AttributesTransfer',
|
||||||
@@ -130,10 +197,41 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 400,
|
default: 400,
|
||||||
},
|
},
|
||||||
|
showDefaultAttr: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
|
defaultAttrList: [
|
||||||
|
{
|
||||||
|
title: 'cmdb.components.updater',
|
||||||
|
name: 'updater',
|
||||||
|
key: CI_DEFAULT_ATTR.UPDATE_USER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'cmdb.components.updateTime',
|
||||||
|
name: 'update time',
|
||||||
|
key: CI_DEFAULT_ATTR.UPDATE_TIME
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rightDefaultAttrList() {
|
||||||
|
if (!this.showDefaultAttr) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.defaultAttrList.filter((item) => this.targetKeys.includes(item.key))
|
||||||
|
},
|
||||||
|
|
||||||
|
leftDefaultAttrList() {
|
||||||
|
if (!this.showDefaultAttr) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.defaultAttrList.filter((item) => !this.targetKeys.includes(item.key))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -216,6 +314,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$emit('setFixedList', _fixedList)
|
this.$emit('setFixedList', _fixedList)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
filterDefaultAttr(list) {
|
||||||
|
return this.showDefaultAttr ? list.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.key)) : list
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -296,5 +398,67 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-attr {
|
||||||
|
.ant-divider {
|
||||||
|
margin: 7px 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
|
||||||
|
.ant-divider-inner-text {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-divider {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 12px;
|
||||||
|
color: rgb(163, 163, 163);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
display: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: @primary-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
padding-left: 34px;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
position: relative;
|
||||||
|
border-left: solid 2px transparent;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
&-selected {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
border-color: #2f54eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
|
||||||
|
.default-attr-arrow {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -13,7 +13,10 @@
|
|||||||
v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
|
v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('cmdb.components.saveType')">
|
<a-form-item
|
||||||
|
v-if="showFileTypeSelect"
|
||||||
|
:label="$t('cmdb.components.saveType')"
|
||||||
|
>
|
||||||
<a-select
|
<a-select
|
||||||
:placeholder="$t('cmdb.components.saveTypeTips')"
|
:placeholder="$t('cmdb.components.saveTypeTips')"
|
||||||
v-decorator="[
|
v-decorator="[
|
||||||
@@ -83,6 +86,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
|
showFileTypeSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
highlight-hover-row
|
highlight-hover-row
|
||||||
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
||||||
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
|
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
|
||||||
:sort-config="{ remote: true, trigger: 'cell' }"
|
:sort-config="sortConfig"
|
||||||
:row-key="true"
|
:row-key="true"
|
||||||
:column-key="true"
|
:column-key="true"
|
||||||
:cell-style="getCellStyle"
|
:cell-style="getCellStyle"
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</vxe-table-column>
|
</vxe-table-column>
|
||||||
<vxe-column align="left" field="operate" fixed="right" width="80">
|
<vxe-column v-if="showOperation" align="left" field="operate" fixed="right" width="80">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>{{ $t('operation') }}</span>
|
<span>{{ $t('operation') }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -272,6 +272,18 @@ export default {
|
|||||||
data: {
|
data: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
},
|
||||||
|
sortConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
remote: true,
|
||||||
|
trigger: 'cell'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 是否展示操作列
|
||||||
|
showOperation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -129,6 +129,8 @@ import Treeselect from '@riophae/vue-treeselect'
|
|||||||
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
||||||
import FilterComp from '@/components/CMDBFilterComp'
|
import FilterComp from '@/components/CMDBFilterComp'
|
||||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchForm',
|
name: 'SearchForm',
|
||||||
components: { MetadataDrawer, FilterComp, Treeselect },
|
components: { MetadataDrawer, FilterComp, Treeselect },
|
||||||
@@ -176,7 +178,9 @@ export default {
|
|||||||
return '200px'
|
return '200px'
|
||||||
},
|
},
|
||||||
canSearchPreferenceAttrList() {
|
canSearchPreferenceAttrList() {
|
||||||
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
|
return this.preferenceAttrList.filter((item) => {
|
||||||
|
return item.value_type !== '6' && ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.name)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
:fixedList="fixedList"
|
:fixedList="fixedList"
|
||||||
@setFixedList="setFixedList"
|
@setFixedList="setFixedList"
|
||||||
:height="windowHeight - 170"
|
:height="windowHeight - 170"
|
||||||
|
:showDefaultAttr="true"
|
||||||
/>
|
/>
|
||||||
<div class="custom-drawer-bottom-action">
|
<div class="custom-drawer-bottom-action">
|
||||||
<a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
|
<a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cmdb-subscribe-drawer-tree-main" :style="{ maxHeight: `${((windowHeight - 170) * 2) / 3}px` }">
|
<div class="cmdb-subscribe-drawer-tree-main" :style="{ maxHeight: `${((windowHeight - 170) * 2) / 3}px` }">
|
||||||
<div @click="changeTreeViews(attr)" v-for="attr in attrList" :key="attr.name">
|
<div @click="changeTreeViews(attr)" v-for="attr in treeViewAttrList" :key="attr.name">
|
||||||
<a-checkbox :checked="treeViews.includes(attr.name)" />
|
<a-checkbox :checked="treeViews.includes(attr.name)" />
|
||||||
{{ attr.title }}
|
{{ attr.title }}
|
||||||
</div>
|
</div>
|
||||||
@@ -90,6 +91,8 @@ import {
|
|||||||
} from '@/modules/cmdb/api/preference'
|
} from '@/modules/cmdb/api/preference'
|
||||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import AttributesTransfer from '../attributesTransfer'
|
import AttributesTransfer from '../attributesTransfer'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubscribeSetting',
|
name: 'SubscribeSetting',
|
||||||
components: { AttributesTransfer },
|
components: { AttributesTransfer },
|
||||||
@@ -110,16 +113,32 @@ export default {
|
|||||||
...mapState({
|
...mapState({
|
||||||
windowHeight: (state) => state.windowHeight,
|
windowHeight: (state) => state.windowHeight,
|
||||||
}),
|
}),
|
||||||
|
treeViewAttrList() {
|
||||||
|
return this.attrList.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.name))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open(ciType = {}, activeKey = '1') {
|
open(ciType = {}, activeKey = '1') {
|
||||||
this.ciType = ciType
|
this.ciType = ciType
|
||||||
this.activeKey = activeKey
|
this.activeKey = activeKey
|
||||||
|
const updatedByKey = CI_DEFAULT_ATTR.UPDATE_USER
|
||||||
|
const updatedAtKey = CI_DEFAULT_ATTR.UPDATE_TIME
|
||||||
|
|
||||||
getCITypeAttributesByName(ciType.type_id).then((res) => {
|
getCITypeAttributesByName(ciType.type_id).then((res) => {
|
||||||
const attributes = res.attributes
|
const attributes = res.attributes.filter((item) => ![updatedByKey, updatedAtKey].includes(item.name))
|
||||||
|
|
||||||
|
;[updatedByKey, updatedAtKey].map((key) => {
|
||||||
|
attributes.push({
|
||||||
|
alias: key,
|
||||||
|
name: key,
|
||||||
|
id: key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
getSubscribeAttributes(ciType.type_id).then((_res) => {
|
getSubscribeAttributes(ciType.type_id).then((_res) => {
|
||||||
this.instanceSubscribed = _res.is_subscribed
|
this.instanceSubscribed = _res.is_subscribed
|
||||||
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
||||||
|
|
||||||
const attrList = attributes.map((item) => {
|
const attrList = attributes.map((item) => {
|
||||||
return {
|
return {
|
||||||
key: item.id.toString(),
|
key: item.id.toString(),
|
||||||
@@ -188,9 +207,20 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
subInstanceSubmit() {
|
subInstanceSubmit() {
|
||||||
|
const customAttr = []
|
||||||
|
const defaultAttr = []
|
||||||
|
this.selectedAttrList.map((attr) => {
|
||||||
|
if ([CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(attr)) {
|
||||||
|
defaultAttr.push(attr)
|
||||||
|
} else {
|
||||||
|
customAttr.push(attr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const selectedAttrList = [...customAttr, ...defaultAttr]
|
||||||
|
|
||||||
subscribeCIType(
|
subscribeCIType(
|
||||||
this.ciType.type_id,
|
this.ciType.type_id,
|
||||||
this.selectedAttrList.map((item) => {
|
selectedAttrList.map((item) => {
|
||||||
return [item, !!this.fixedList.includes(item)]
|
return [item, !!this.fixedList.includes(item)]
|
||||||
})
|
})
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
|
@@ -24,7 +24,9 @@ const cmdb_en = {
|
|||||||
operationHistory: 'Operation Audit',
|
operationHistory: 'Operation Audit',
|
||||||
relationType: 'Relation Type',
|
relationType: 'Relation Type',
|
||||||
ad: 'AutoDiscovery',
|
ad: 'AutoDiscovery',
|
||||||
cidetail: 'CI Detail'
|
cidetail: 'CI Detail',
|
||||||
|
scene: 'Scene',
|
||||||
|
dcim: 'DCIM'
|
||||||
},
|
},
|
||||||
ciType: {
|
ciType: {
|
||||||
ciType: 'CIType',
|
ciType: 'CIType',
|
||||||
@@ -286,6 +288,7 @@ const cmdb_en = {
|
|||||||
attrCode: 'Attr Code',
|
attrCode: 'Attr Code',
|
||||||
computedAttrTip1: 'Reference attributes follow jinja2 syntax',
|
computedAttrTip1: 'Reference attributes follow jinja2 syntax',
|
||||||
computedAttrTip2: `Multi-valued attributes (lists) are rendered with [ ] included by default, if you want to remove it, the reference method is: """{{ attr_name | join(',') }}""" where commas are separators`,
|
computedAttrTip2: `Multi-valued attributes (lists) are rendered with [ ] included by default, if you want to remove it, the reference method is: """{{ attr_name | join(',') }}""" where commas are separators`,
|
||||||
|
computedAttrTip3: `Cannot refer to other computed attributes`,
|
||||||
example: 'Example',
|
example: 'Example',
|
||||||
attrFilterTip: `The third column of values allows you to select attributes of this model to cascade attributes`,
|
attrFilterTip: `The third column of values allows you to select attributes of this model to cascade attributes`,
|
||||||
rule: 'Rule',
|
rule: 'Rule',
|
||||||
@@ -376,6 +379,9 @@ const cmdb_en = {
|
|||||||
param: 'Parameter{param}',
|
param: 'Parameter{param}',
|
||||||
value: 'Value{value}',
|
value: 'Value{value}',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
|
updater: 'Update User',
|
||||||
|
updateTime: 'Update Time',
|
||||||
|
default: 'Default'
|
||||||
},
|
},
|
||||||
batch: {
|
batch: {
|
||||||
downloadFailed: 'Download failed',
|
downloadFailed: 'Download failed',
|
||||||
@@ -740,5 +746,155 @@ if __name__ == "__main__":
|
|||||||
topoViewSearchPlaceholder: 'Please enter the node name.',
|
topoViewSearchPlaceholder: 'Please enter the node name.',
|
||||||
moreBtn: 'Show more({count})'
|
moreBtn: 'Show more({count})'
|
||||||
},
|
},
|
||||||
|
relationSearch: {
|
||||||
|
relationSearch: 'Relation Search',
|
||||||
|
sourceCIType: 'Source CIType',
|
||||||
|
sourceCITypeTip: 'Please input or select',
|
||||||
|
sourceCITYpeInput: 'Please input keywords',
|
||||||
|
targetCIType: 'Target CIType',
|
||||||
|
targetCITypeTip: 'Please input or select, multiple choices available',
|
||||||
|
pathSelect: 'Path Select',
|
||||||
|
pathSelectTip: 'Please select source CIType and target CIType first',
|
||||||
|
saveCondition: 'Save Condition',
|
||||||
|
conditionFilter: 'Condition Filter',
|
||||||
|
level: 'Level',
|
||||||
|
returnPath: 'Return Path',
|
||||||
|
conditionName: 'Condition Name',
|
||||||
|
path: 'Path',
|
||||||
|
expandCondition: 'Expand Condition',
|
||||||
|
},
|
||||||
|
ipam: {
|
||||||
|
overview: 'Overview',
|
||||||
|
addressAssign: 'Address Assign',
|
||||||
|
ipSearch: 'IP Search',
|
||||||
|
subnetList: 'Subnet List',
|
||||||
|
history: 'History Log',
|
||||||
|
ticket: 'Related Tickets',
|
||||||
|
addSubnet: 'Add Subnet',
|
||||||
|
editSubnet: 'Edit Subnet',
|
||||||
|
addCatalog: 'Add Catalog',
|
||||||
|
editCatalog: 'Edit Catalog',
|
||||||
|
catalogName: 'Catalog Name',
|
||||||
|
editName: 'Edit Name',
|
||||||
|
editNode: 'Edit Node',
|
||||||
|
deleteNode: 'Delete Node',
|
||||||
|
basicInfo: 'Basic Info',
|
||||||
|
scanRule: 'Scan Rule',
|
||||||
|
adExecTarget: 'Execute targets',
|
||||||
|
masterMachineTip: 'The machine where OneMaster is installed',
|
||||||
|
oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x ID',
|
||||||
|
selectFromCMDBTips: 'Select from CMDB',
|
||||||
|
adInterval: 'Collection frequency',
|
||||||
|
cronTips: 'The format is the same as crontab, for example: 0 15 * * 1-5',
|
||||||
|
masterMachine: 'Master machine',
|
||||||
|
specifyMachine: 'Specify machine',
|
||||||
|
specifyMachineTips: 'Please fill in the specify machine!',
|
||||||
|
cronRequiredTip: 'Acquisition frequency cannot be null',
|
||||||
|
addressNullTip: ' Please select the leaf node of the left subnet tree first',
|
||||||
|
addressNullTip2: 'Subnet prefix length must be >= 16',
|
||||||
|
assignedOnline: 'Assigned Online',
|
||||||
|
assignedOffline: 'Assigned Offline',
|
||||||
|
unassignedOnline: 'Unassigned Online',
|
||||||
|
unused: 'Unused',
|
||||||
|
allStatus: 'All Status',
|
||||||
|
editAssignAddress: 'Edit Assign Address',
|
||||||
|
assign: 'Assign',
|
||||||
|
recycle: 'Recycle',
|
||||||
|
assignStatus: 'Assign Status',
|
||||||
|
reserved: 'Reserved',
|
||||||
|
assigned: 'Assigned',
|
||||||
|
recycleTip: 'Confirmed for recycle? After recycling, the status of the segment will be changed to unassigned.',
|
||||||
|
recycleSuccess: '{ip} Recycled successfully, status changed to: unassigned.',
|
||||||
|
operationLog: 'Operation Log',
|
||||||
|
scanLog: 'Scan Log',
|
||||||
|
updateCatalog: 'Update Catalog',
|
||||||
|
deleteCatalog: 'Delete Catalog',
|
||||||
|
updateSubnet: 'Update Subnet',
|
||||||
|
deleteSubnet: 'Delete Subnet',
|
||||||
|
revokeAddress: 'Revoke Address',
|
||||||
|
operateTime: 'Operate Time',
|
||||||
|
operateUser: 'Operate User',
|
||||||
|
operateType: 'Operate Type',
|
||||||
|
subnet: 'Subnet',
|
||||||
|
description: 'Description',
|
||||||
|
ipNumber: 'Number of online IP',
|
||||||
|
startTime: 'Start Time',
|
||||||
|
endTime: 'End Time',
|
||||||
|
scanningTime: 'Scanning Time',
|
||||||
|
viewResult: 'View Result',
|
||||||
|
scannedIP: 'Scanned IP',
|
||||||
|
subnetStats: 'Subnet Stats',
|
||||||
|
addressStats: 'Address Stats',
|
||||||
|
onlineStats: 'Online Stats',
|
||||||
|
assignStats: 'Assign Stats',
|
||||||
|
total: 'Total',
|
||||||
|
free: 'Free',
|
||||||
|
unassigned: 'Unassigned',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
onlineUsageStats: 'Subnet Online Stats',
|
||||||
|
subnetName: 'Subnet Name',
|
||||||
|
addressCount: 'Address Count',
|
||||||
|
onlineRatio: 'Online Ratio',
|
||||||
|
scanEnable: 'Scan Enable',
|
||||||
|
lastScanTime: 'Last Scan Time',
|
||||||
|
isSuccess: 'Is Success',
|
||||||
|
batchAssign: 'Batch Assign',
|
||||||
|
batchAssignInProgress: 'Assign in batches, {total} in total, {successNum} successful, {errorNum} failed',
|
||||||
|
batchAssignCompleted: 'Batch Assign Completed',
|
||||||
|
batchRecycle: 'Batch Recycle',
|
||||||
|
batchRecycleInProgress: 'Recycle in batches, {total} in total, {successNum} successful, {errorNum} failed',
|
||||||
|
batchRecycleCompleted: 'Batch Recycle Completed',
|
||||||
|
},
|
||||||
|
dcim: {
|
||||||
|
addRegion: 'Add Region',
|
||||||
|
addIDC: 'Add IDC',
|
||||||
|
addServerRoom: 'Add Server Room',
|
||||||
|
addRack: 'Add Rack',
|
||||||
|
editRegion: 'Edit Region',
|
||||||
|
editIDC: 'Edit IDC',
|
||||||
|
editServerRoom: 'Edit Server Room',
|
||||||
|
editRack: 'Edit Rack',
|
||||||
|
rackCount: 'Rack Count',
|
||||||
|
total: 'Total',
|
||||||
|
deviceCount: 'Device Count',
|
||||||
|
utilizationRation: 'Utilization Ration',
|
||||||
|
used: 'Used',
|
||||||
|
unused: 'Unused',
|
||||||
|
rackSearchTip: 'Please search for rack name',
|
||||||
|
viewDetail: 'View Detail',
|
||||||
|
deleteNode: 'Delete Node',
|
||||||
|
editNode: 'Edit Node',
|
||||||
|
roomNullTip: 'Please select the server room on the left first',
|
||||||
|
unitAbnormal: 'Unit Abnormal',
|
||||||
|
rack: 'Rack',
|
||||||
|
unitCount: 'Unit Count',
|
||||||
|
rackView: 'Rack View',
|
||||||
|
deviceList: 'Device List',
|
||||||
|
operationLog: 'Operation Log',
|
||||||
|
frontView: 'Front View',
|
||||||
|
rearView: 'Rear View',
|
||||||
|
addDevice: 'Add Device',
|
||||||
|
device: 'Device',
|
||||||
|
ciType: 'CIType',
|
||||||
|
unitStart: 'Unit Start',
|
||||||
|
toChange: 'To Change',
|
||||||
|
abnormalModalTip1: 'and',
|
||||||
|
abnormalModalTip2: ' location duplication',
|
||||||
|
abnormalModalTip3: 'Please select one of the devices to change',
|
||||||
|
remove: 'Remove',
|
||||||
|
migrate: 'Migrate',
|
||||||
|
deviceMigrate: 'Device Migrate',
|
||||||
|
migrationSuccess: 'Migration Success',
|
||||||
|
removeDeviceTip: 'Confirmed to remove {deviceName} device?',
|
||||||
|
operationTime: 'Operation Time',
|
||||||
|
operationUser: 'Operation User',
|
||||||
|
operationType: 'Operation Type',
|
||||||
|
deviceType: 'Device Type',
|
||||||
|
deviceName: 'Device Name',
|
||||||
|
removeDevice: 'Remove Device',
|
||||||
|
moveDevice: 'Move Device',
|
||||||
|
rackDetail: 'Rack Detail'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default cmdb_en
|
export default cmdb_en
|
||||||
|