Compare commits

...

16 Commits

Author SHA1 Message Date
pycook
c1813f525d chore: update docker compose 2024-06-27 21:33:19 +08:00
pycook
b405e28498 chore: release v2.4.7 2024-06-27 21:30:02 +08:00
pycook
fa32758462 perf(api): CIType templates download (#567) 2024-06-27 20:54:38 +08:00
Leo Song
29995b660a Merge pull request #566 from veops/dev_ui_0627
feat: update model config
2024-06-27 19:41:58 +08:00
songlh
b96fc06a62 feat: update model config 2024-06-27 19:41:24 +08:00
Leo Song
c7f30b63ff Merge pull request #565 from veops/dev_ui_0625
fix(ui): auto discovery
2024-06-25 17:36:00 +08:00
songlh
5bff69a8a8 fix(ui): auto discovery 2024-06-25 17:35:29 +08:00
pycook
d5e60fab88 chore: update ui dockerfile 2024-06-24 21:40:38 +08:00
Jared Tan
190f452118 chore: fix UI docker build and makes UI/API docker build parallel execution (#563)
* try fix UI docker build

* parallel execution

* polish

* polish

* polish

* update
2024-06-24 21:00:57 +08:00
Leo Song
98a4824364 Merge pull request #564 from veops/dev_ui_ad_0624
feat: update ad ui
2024-06-24 14:26:33 +08:00
songlh
c0f9baea79 feat: update ad ui 2024-06-24 14:25:56 +08:00
pycook
d4b661c77f fix(api): commands cmdb-patch 2024-06-21 18:22:56 +08:00
pycook
75cd7bde77 fix(api): auto discovery permission 2024-06-21 12:47:12 +08:00
Leo Song
ec912d3a65 Merge pull request #562 from veops/fix_ui_2.4.6
fix(ui): some bugs
2024-06-21 11:49:53 +08:00
songlh
42f02b4986 fix(ui): some bugs 2024-06-21 11:49:12 +08:00
simontigers
a13b999820 Merge pull request #561 from veops/dev_common_perm
fix(api): auto_discovery add new perms
2024-06-21 10:25:35 +08:00
40 changed files with 4908 additions and 3401 deletions

View File

@@ -1,4 +1,4 @@
name: api-docker-images-build-and-release
name: docker-images-build-and-release
on:
push:
@@ -12,31 +12,25 @@ on:
env:
# Use docker.io for Docker Hub if empty
REGISTRY_SERVER_ADDRESS: ghcr.io/veops
TAG: ${{ github.sha }}
jobs:
setup-environment:
timeout-minutes: 30
runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
release-images:
release-api-images:
runs-on: ubuntu-latest
needs: [setup-environment]
permissions:
contents: read
packages: write
timeout-minutes: 90
env:
TAG: ${{ github.sha }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.21.8"
cache: false
- name: Login to GitHub Package Registry
uses: docker/login-action@v2
with:
@@ -55,6 +49,26 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-api:${{ env.TAG }}
release-ui-images:
runs-on: ubuntu-latest
needs: [setup-environment]
permissions:
contents: read
packages: write
timeout-minutes: 90
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to GitHub Package Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push CMDB-UI Docker image
uses: docker/build-push-action@v6
with:

View File

@@ -515,31 +515,47 @@ def cmdb_patch(version):
version = version[1:] if version.lower().startswith("v") else version
if version >= '2.4.6':
try:
if version >= '2.4.6':
from api.models.cmdb import CITypeRelation
for cr in CITypeRelation.get_by(to_dict=False):
if hasattr(cr, 'parent_attr_id') and cr.parent_attr_id and not cr.parent_attr_ids:
parent_attr_ids, child_attr_ids = [cr.parent_attr_id], [cr.child_attr_id]
cr.update(parent_attr_ids=parent_attr_ids, child_attr_ids=child_attr_ids, commit=False)
db.session.commit()
from api.models.cmdb import CITypeRelation
for cr in CITypeRelation.get_by(to_dict=False):
if hasattr(cr, 'parent_attr_id') and cr.parent_attr_id and not cr.parent_attr_ids:
parent_attr_ids, child_attr_ids = [cr.parent_attr_id], [cr.child_attr_id]
cr.update(parent_attr_ids=parent_attr_ids, child_attr_ids=child_attr_ids, commit=False)
db.session.commit()
from api.models.cmdb import AutoDiscoveryCIType, AutoDiscoveryCITypeRelation
from api.lib.cmdb.cache import CITypeCache, AttributeCache
for adt in AutoDiscoveryCIType.get_by(to_dict=False):
if adt.relation:
if not AutoDiscoveryCITypeRelation.get_by(ad_type_id=adt.type_id):
peer_type = CITypeCache.get(list(adt.relation.values())['type_name'])
peer_type_id = peer_type and peer_type.id
peer_attr = AttributeCache.get(list(adt.relation.values())['attr_name'])
peer_attr_id = peer_attr and peer_attr.id
if peer_type_id and peer_attr_id:
AutoDiscoveryCITypeRelation.create(ad_type_id=adt.type_id,
ad_key=list(adt.relation.keys())[0],
peer_type_id=peer_type_id,
peer_attr_id=peer_attr_id,
commit=False)
if hasattr(adt, 'interval') and adt.interval and not adt.cron:
adt.cron = "*/{} * * * *".format(adt.interval // 60)
from api.models.cmdb import AutoDiscoveryCIType, AutoDiscoveryCITypeRelation
from api.lib.cmdb.cache import CITypeCache, AttributeCache
for adt in AutoDiscoveryCIType.get_by(to_dict=False):
if adt.relation:
if not AutoDiscoveryCITypeRelation.get_by(ad_type_id=adt.type_id):
peer_type = CITypeCache.get(list(adt.relation.values())[0]['type_name'])
peer_type_id = peer_type and peer_type.id
peer_attr = AttributeCache.get(list(adt.relation.values())[0]['attr_name'])
peer_attr_id = peer_attr and peer_attr.id
if peer_type_id and peer_attr_id:
AutoDiscoveryCITypeRelation.create(ad_type_id=adt.type_id,
ad_key=list(adt.relation.keys())[0],
peer_type_id=peer_type_id,
peer_attr_id=peer_attr_id,
commit=False)
if hasattr(adt, 'interval') and adt.interval and not adt.cron:
adt.cron = "*/{} * * * *".format(adt.interval // 60 or 1)
db.session.commit()
db.session.commit()
if version >= "2.4.7":
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.models.cmdb import AutoDiscoveryRule
for i in DEFAULT_INNER:
existed = AutoDiscoveryRule.get_by(name=i['name'], first=True, to_dict=False)
if existed is not None:
if "en" in i['option'] and 'en' not in (existed.option or {}):
option = copy.deepcopy(existed.option)
option['en'] = i['option']['en']
existed.update(option=option, commit=False)
db.session.commit()
except Exception as e:
print("cmdb patch failed: {}".format(e))

View File

@@ -3,7 +3,6 @@ import copy
import datetime
import json
import os
from flask import abort
from flask import current_app
from flask_login import current_user
@@ -11,7 +10,8 @@ from sqlalchemy import func
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributeCache
from api.lib.cmdb.cache import CITypeCache
@@ -22,6 +22,7 @@ from api.lib.cmdb.const import AutoDiscoveryType
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.custom_dashboard import SystemConfigManager
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
@@ -109,14 +110,22 @@ class AutoDiscoveryRuleCRUD(DBMixin):
else:
self.cls.create(**rule)
def _can_add(self, **kwargs):
def _can_add(self, valid=True, **kwargs):
self.cls.get_by(name=kwargs['name']) and abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
if kwargs.get('is_plugin') and kwargs.get('plugin_script') and valid:
kwargs = check_plugin_script(**kwargs)
acl = ACLManager(app_cli.app_name)
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.create_plugin) and not is_app_admin(app_cli.app_name):
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.create_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.create_plugin))
@@ -124,7 +133,7 @@ class AutoDiscoveryRuleCRUD(DBMixin):
return kwargs
def _can_update(self, **kwargs):
def _can_update(self, valid=True, **kwargs):
existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
@@ -136,11 +145,19 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if other and other.id != existed.id:
return abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
if existed.is_plugin:
if existed.is_plugin and valid:
acl = ACLManager(app_cli.app_name)
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.update_plugin) and not is_app_admin(app_cli.app_name):
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.update_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.update_plugin))
@@ -165,9 +182,17 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if existed.is_plugin:
acl = ACLManager(app_cli.app_name)
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.delete_plugin) and not is_app_admin(app_cli.app_name):
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.delete_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.delete_plugin))
@@ -178,8 +203,9 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
cls = AutoDiscoveryCIType
@classmethod
def get_all(cls):
return cls.cls.get_by(to_dict=False)
def get_all(cls, type_ids=None):
res = cls.cls.get_by(to_dict=False)
return [i for i in res if type_ids is None or i.type_id in type_ids]
@classmethod
def get_by_id(cls, _id):
@@ -198,7 +224,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if not adr:
continue
if adr.type == "http":
for i in DEFAULT_HTTP:
for i in DEFAULT_INNER:
if adr.name == i['name']:
attrs = AutoDiscoveryHTTPManager.get_attributes(
i['en'], (adt.extra_option or {}).get('category')) or []
@@ -219,11 +245,17 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
for rule in rules:
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'):
if not (current_user.username == "cmdb_agent" or current_user.uid == rule['uid']):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == rule['uid']):
rule['extra_option'].pop('secret', None)
else:
rule['extra_option']['secret'] = AESCrypto.decrypt(rule['extra_option']['secret'])
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('password'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == rule['uid']):
rule['extra_option'].pop('password', None)
else:
rule['extra_option']['password'] = AESCrypto.decrypt(rule['extra_option']['password'])
if oneagent_id and rule['agent_id'] == oneagent_id:
result.append(rule)
elif rule['query_expr']:
@@ -247,17 +279,19 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
result.append(rule)
ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or ""
new_last_update_at = ""
for i in result:
i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict()
i['adr'].pop("attributes", None)
__last_update_at = max([i['updated_at'] or "", i['created_at'] or "",
i['adr']['created_at'] or "", i['adr']['updated_at'] or ""])
i['adr']['created_at'] or "", i['adr']['updated_at'] or "", ad_rules_updated_at])
if new_last_update_at < __last_update_at:
new_last_update_at = __last_update_at
write_ad_rule_sync_history.apply_async(args=(result, oneagent_id, oneagent_name, datetime.datetime.now()),
queue=CMDB_QUEUE)
if not last_update_at or new_last_update_at > last_update_at:
return result, new_last_update_at
else:
@@ -322,7 +356,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if adr.type == "http":
kwargs.setdefault('extra_option', dict)
en_name = None
for i in DEFAULT_HTTP:
for i in DEFAULT_INNER:
if i['name'] == adr.name:
en_name = i['en']
break
@@ -338,10 +372,13 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('password'):
kwargs['extra_option']['password'] = AESCrypto.encrypt(kwargs['extra_option']['password'])
ci_type = CITypeCache.get(kwargs['type_id'])
unique = AttributeCache.get(ci_type.unique_id)
if unique and unique.name not in (kwargs.get('attributes') or {}).values():
current_app.logger.warning((unique.name, kwargs.get('attributes'), ci_type.alias))
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
kwargs['uid'] = current_user.uid
@@ -357,7 +394,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if adr.type == "http":
kwargs.setdefault('extra_option', dict)
en_name = None
for i in DEFAULT_HTTP:
for i in DEFAULT_INNER:
if i['name'] == adr.name:
en_name = i['en']
break
@@ -374,11 +411,15 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
ci_type = CITypeCache.get(existed.type_id)
unique = AttributeCache.get(ci_type.unique_id)
if unique and unique.name not in (kwargs.get('attributes') or {}).values():
current_app.logger.warning((unique.name, kwargs.get('attributes'), ci_type.alias))
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('password'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
return existed
@@ -389,6 +430,8 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('password'):
kwargs['extra_option']['password'] = AESCrypto.encrypt(kwargs['extra_option']['password'])
inst = self._can_update(_id=_id, **kwargs)
if inst.agent_id != kwargs.get('agent_id') or inst.query_expr != kwargs.get('query_expr'):
@@ -396,6 +439,9 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
item.delete(commit=False)
db.session.commit()
SystemConfigManager.create_or_update("ad_rules_updated_at",
dict(v=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
obj = inst.update(_id=_id, filter_none=False, **kwargs)
return obj
@@ -429,6 +475,10 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
class AutoDiscoveryCITypeRelationCRUD(DBMixin):
cls = AutoDiscoveryCITypeRelation
@classmethod
def get_all(cls, type_ids=None):
res = cls.cls.get_by(to_dict=False)
return [i for i in res if type_ids is None or i.ad_type_id in type_ids]
@classmethod
def get_by_type_id(cls, type_id, to_dict=False):
return cls.cls.get_by(ad_type_id=type_id, to_dict=to_dict)
@@ -668,12 +718,13 @@ class AutoDiscoveryHTTPManager(object):
categories = (ClOUD_MAP.get(name) or {}) or []
for item in copy.deepcopy(categories):
item.pop('map', None)
item.pop('collect_key_map', None)
return categories
def get_resources(self, name):
en_name = None
for i in DEFAULT_HTTP:
for i in DEFAULT_INNER:
if i['name'] == name:
en_name = i['en']
break
@@ -709,6 +760,17 @@ class AutoDiscoverySNMPManager(object):
return []
class AutoDiscoveryComponentsManager(object):
@staticmethod
def get_attributes(name):
if os.path.exists(os.path.join(PWD, "templates/{}.json".format(name))):
with open(os.path.join(PWD, "templates/{}.json".format(name))) as f:
return json.loads(f.read())
return []
class AutoDiscoveryRuleSyncHistoryCRUD(DBMixin):
cls = AutoDiscoveryRuleSyncHistory

View File

@@ -2,16 +2,28 @@
from api.lib.cmdb.const import AutoDiscoveryType
DEFAULT_HTTP = [
PRIVILEGED_USERS = ("cmdb_agent", "worker", "admin")
DEFAULT_INNER = [
dict(name="阿里云", en="aliyun", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aliyun'}}),
option={'icon': {'name': 'caise-aliyun'}, "en": "aliyun"}),
dict(name="腾讯云", en="tencentcloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-tengxunyun'}}),
option={'icon': {'name': 'caise-tengxunyun'}, "en": "tencentcloud"}),
dict(name="华为云", en="huaweicloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-huaweiyun'}}),
option={'icon': {'name': 'caise-huaweiyun'}, "en": "huaweicloud"}),
dict(name="AWS", en="aws", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aws'}}),
dict(name="VCenter", en="vcenter", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'cmdb-vcenter'}, "category": "private_cloud", "en": "vcenter"}),
dict(name="KVM", en="kvm", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'ops-KVM'}, "category": "private_cloud", "en": "kvm"}),
dict(name="Nginx", en="nginx", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-nginx'}, "en": "nginx"}),
dict(name="Redis", en="redis", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-redis'}, "en": "redis"}),
dict(name="交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-jiaohuanji'}}),
dict(name="路由器", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
@@ -23,47 +35,306 @@ DEFAULT_HTTP = [
]
ClOUD_MAP = {
"aliyun": [{
"category": "计算",
"items": ["云服务器 ECS"],
"map": {
"云服务器 ECS": "templates/aliyun_ecs.json",
"aliyun": [
{
"category": "计算",
"items": ["云服务器 ECS", "云服务器 Disk"],
"map": {
"云服务器 ECS": "templates/aliyun_ecs.json",
"云服务器 Disk": "templates/aliyun_ecs_disk2.json",
},
"collect_key_map": {
"云服务器 ECS": "ali.ecs",
"云服务器 Disk": "ali.ecs_disk",
},
},
"collect_key_map": {
"云服务器 ECS": "ali.ecs",
}
}],
"tencentcloud": [{
"category": "计算",
"items": ["云服务器 CVM"],
"map": {
"云服务器 CVM": "templates/tencent_cvm.json",
{
"category": "网络与CDN",
"items": [
"内容分发CDN",
"负载均衡SLB",
"专有网络VPC",
"交换机Switch",
],
"map": {
"内容分发CDN": "templates/aliyun_cdn.json",
"负载均衡SLB": "templates/aliyun_slb.json",
"专有网络VPC": "templates/aliyun_vpc.json",
"交换机Switch": "templates/aliyun_switch.json",
},
"collect_key_map": {
"内容分发CDN": "ali.cdn",
"负载均衡SLB": "ali.slb",
"专有网络VPC": "ali.vpc",
"交换机Switch": "ali.switch",
},
},
"collect_key_map": {
"云服务器 CVM": "tencent.cvm",
}
}],
"huaweicloud": [{
"category": "计算",
"items": ["云服务器 ECS"],
"map": {
"云服务器 ECS": "templates/huaweicloud_ecs.json",
{
"category": "存储",
"items": ["块存储EBS", "对象存储OSS"],
"map": {
"块存储EBS": "templates/aliyun_ebs.json",
"对象存储OSS": "templates/aliyun_oss.json",
},
"collect_key_map": {
"块存储EBS": "ali.ebs",
"对象存储OSS": "ali.oss",
},
},
"collect_key_map": {
"云服务器 ECS": "huawei.ecs",
}
}],
"aws": [{
"category": "计算",
"items": ["云服务器 EC2"],
"map": {
"服务器 EC2": "templates/aws_ec2.json",
{
"category": "数据库",
"items": ["云数据库RDS MySQL", "云数据库RDS PostgreSQL", "云数据库 Redis"],
"map": {
"云数据库RDS MySQL": "templates/aliyun_rds_mysql.json",
"云数据库RDS PostgreSQL": "templates/aliyun_rds_postgre.json",
"云数据库 Redis": "templates/aliyun_redis.json",
},
"collect_key_map": {
"数据库RDS MySQL": "ali.rds_mysql",
"云数据库RDS PostgreSQL": "ali.rds_postgre",
"云数据库 Redis": "ali.redis",
},
},
"collect_key_map": {
"云服务器 EC2": "aws.ec2",
}
}],
],
"tencentcloud": [
{
"category": "计算",
"items": ["云服务器 CVM"],
"map": {
"云服务器 CVM": "templates/tencent_cvm.json",
},
"collect_key_map": {
"云服务器 CVM": "tencent.cvm",
},
},
{
"category": "CDN与边缘",
"items": ["内容分发CDN"],
"map": {
"内容分发CDN": "templates/tencent_cdn.json",
},
"collect_key_map": {
"内容分发CDN": "tencent.cdn",
},
},
{
"category": "网络",
"items": ["负载均衡CLB", "私有网络VPC", "子网"],
"map": {
"负载均衡CLB": "templates/tencent_clb.json",
"私有网络VPC": "templates/tencent_vpc.json",
"子网": "templates/tencent_subnet.json",
},
"collect_key_map": {
"负载均衡CLB": "tencent.clb",
"私有网络VPC": "tencent.vpc",
"子网": "tencent.subnet",
},
},
{
"category": "存储",
"items": ["云硬盘CBS", "对象存储COS"],
"map": {
"云硬盘CBS": "templates/tencent_cbs.json",
"对象存储OSS": "templates/tencent_cos.json",
},
"collect_key_map": {
"云硬盘CBS": "tencent.cbs",
"对象存储OSS": "tencent.cos",
},
},
{
"category": "数据库",
"items": ["云数据库 MySQL", "云数据库 PostgreSQL", "云数据库 Redis"],
"map": {
"云数据库 MySQL": "templates/tencent_rdb.json",
"云数据库 PostgreSQL": "templates/tencent_postgres.json",
"云数据库 Redis": "templates/tencent_redis.json",
},
"collect_key_map": {
"云数据库 MySQL": "tencent.rdb",
"云数据库 PostgreSQL": "tencent.rds_postgres",
"云数据库 Redis": "tencent.redis",
},
},
],
"huaweicloud": [
{
"category": "计算",
"items": ["云服务器 ECS"],
"map": {
"云服务器 ECS": "templates/huaweicloud_ecs.json",
},
"collect_key_map": {
"云服务器 ECS": "huawei.ecs",
},
},
{
"category": "CDN与智能边缘",
"items": ["内容分发网络CDN"],
"map": {
"内容分发网络CDN": "templates/huawei_cdn.json",
},
"collect_key_map": {
"内容分发网络CDN": "huawei.cdn",
},
},
{
"category": "网络",
"items": ["弹性负载均衡ELB", "虚拟私有云VPC", "子网"],
"map": {
"弹性负载均衡ELB": "templates/huawei_elb.json",
"虚拟私有云VPC": "templates/huawei_vpc.json",
"子网": "templates/huawei_subnet.json",
},
"collect_key_map": {
"弹性负载均衡ELB": "huawei.elb",
"虚拟私有云VPC": "huawei.vpc",
"子网": "huawei.subnet",
},
},
{
"category": "存储",
"items": ["云硬盘EVS", "对象存储OBS"],
"map": {
"云硬盘EVS": "templates/huawei_evs.json",
"对象存储OBS": "templates/huawei_obs.json",
},
"collect_key_map": {
"云硬盘EVS": "huawei.evs",
"对象存储OBS": "huawei.obs",
},
},
{
"category": "数据库",
"items": ["云数据库RDS MySQL", "云数据库RDS PostgreSQL"],
"map": {
"云数据库RDS MySQL": "templates/huawei_rds_mysql.json",
"云数据库RDSPostgreSQL": "templates/huaweirds_postgre.json",
},
"collect_key_map": {
"云数据库RDS MySQL": "huawei.rds_mysql",
"云数据库RDS PostgreSQL": "huawei.rds_postgre",
},
},
{
"category": "应用中间件",
"items": ["分布式缓存Redis"],
"map": {
"分布式缓存Redis": "templates/huawei_dcs.json",
},
"collect_key_map": {
"分布式缓存Redis": "huawei.dcs",
},
},
],
"aws": [
{
"category": "计算",
"items": ["云服务器 EC2"],
"map": {
"云服务器 EC2": "templates/aws_ec2.json",
},
"collect_key_map": {
"云服务器 EC2": "aws.ec2",
},
},
{"category": "网络与CDN", "items": [], "map": {}, "collect_key_map": {}},
],
"vcenter": [
{
"category": "计算",
"items": [
"主机",
"虚拟机",
"主机集群"
],
"map": {
"主机": "templates/vsphere_host.json",
"虚拟机": "templates/vsphere_vm.json",
"主机集群": "templates/vsphere_cluster.json",
},
"collect_key_map": {
"主机": "vsphere.host",
"虚拟机": "vsphere.vm",
"主机集群": "vsphere.cluster",
},
},
{
"category": "网络",
"items": [
"网络",
"标准交换机",
"分布式交换机",
],
"map": {
"网络": "templates/vsphere_network.json",
"标准交换机": "templates/vsphere_standard_switch.json",
"分布式交换机": "templates/vsphere_distributed_switch.json",
},
"collect_key_map": {
"网络": "vsphere.network",
"标准交换机": "vsphere.standard_switch",
"分布式交换机": "vsphere.distributed_switch",
},
},
{
"category": "存储",
"items": ["数据存储", "数据存储集群"],
"map": {
"数据存储": "templates/vsphere_datastore.json",
"数据存储集群": "templates/vsphere.storage_pod.json",
},
"collect_key_map": {
"数据存储": "vsphere.datastore",
"数据存储集群": "vsphere.storage_pod",
},
},
{
"category": "其他",
"items": ["资源池", "数据中心", "文件夹"],
"map": {
"资源池": "templates/vsphere_datastore.json",
"数据中心": "templates/vsphere_datacenter.json",
"文件夹": "templates/vsphere_folder.json",
},
"collect_key_map": {
"资源池": "vsphere.pool",
"数据中心": "vsphere.datacenter",
"文件夹": "vsphere.folder",
},
},
],
"kvm": [
{
"category": "计算",
"items": ["虚拟机"],
"map": {
"虚拟机": "templates/kvm_vm.json",
},
"collect_key_map": {
"虚拟机": "kvm.vm",
},
},
{
"category": "存储",
"items": ["存储"],
"map": {
"存储": "templates/kvm_storage.json",
},
"collect_key_map": {
"存储": "kvm.storage",
},
},
{
"category": "network",
"items": ["网络"],
"map": {
"网络": "templates/kvm_network.json",
},
"collect_key_map": {
"网络": "kvm.network",
},
},
],
}

View File

@@ -1,7 +1,6 @@
# -*- coding:utf-8 -*-
import copy
import toposort
from flask import abort
from flask import current_app
@@ -84,7 +83,7 @@ class CITypeManager(object):
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
@staticmethod
def get_ci_types(type_name=None, like=True):
def get_ci_types(type_name=None, like=True, type_ids=None):
resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
@@ -93,6 +92,9 @@ class CITypeManager(object):
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
res = list()
for type_dict in ci_types:
if type_ids is not None and type_dict['id'] not in type_ids:
continue
attr = AttributeCache.get(type_dict["unique_id"])
type_dict["unique_key"] = attr and attr.name
if type_dict.get('show_id'):
@@ -292,6 +294,12 @@ class CITypeManager(object):
class CITypeInheritanceManager(object):
cls = CITypeInheritance
@classmethod
def get_all(cls, type_ids=None):
res = cls.cls.get_by(to_dict=True)
return [i for i in res if type_ids is None or (i['parent_id'] in type_ids and i['child_id'] in type_ids)]
@classmethod
def get_parents(cls, type_id):
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
@@ -387,7 +395,7 @@ class CITypeGroupManager(object):
cls = CITypeGroup
@staticmethod
def get(need_other=None, config_required=True):
def get(need_other=None, config_required=True, type_ids=None, ci_types=None):
resources = None
if current_app.config.get('USE_ACL'):
resources = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI)
@@ -401,6 +409,8 @@ class CITypeGroupManager(object):
for group in groups:
for t in sorted(CITypeGroupItem.get_by(group_id=group['id']), key=lambda x: x['order'] or 0):
ci_type = CITypeCache.get(t['type_id']).to_dict()
if type_ids is not None and ci_type['id'] not in type_ids:
continue
if resources is None or (ci_type and ci_type['name'] in resources):
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
@@ -408,7 +418,7 @@ class CITypeGroupManager(object):
group_types.add(t["type_id"])
if need_other:
ci_types = CITypeManager.get_ci_types()
ci_types = CITypeManager.get_ci_types(type_ids=type_ids) if ci_types is None else ci_types
other_types = dict(ci_types=[])
for ci_type in ci_types:
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
@@ -529,6 +539,8 @@ class CITypeAttributeManager(object):
attrs = CITypeAttributesCache.get(_type_id)
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
if not attr_dict:
continue
attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show
@@ -537,7 +549,6 @@ class CITypeAttributeManager(object):
if not has_config_perm:
attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None)
if attr_dict['id'] not in id2pos:
id2pos[attr_dict['id']] = len(result)
result.append(attr_dict)
@@ -602,7 +613,6 @@ class CITypeAttributeManager(object):
if existed is not None:
continue
current_app.logger.debug(attr_id)
CITypeAttribute.create(type_id=type_id, attr_id=attr_id, **kwargs)
attr = AttributeCache.get(attr_id)
@@ -769,11 +779,15 @@ class CITypeRelationManager(object):
"""
@staticmethod
def get():
def get(type_ids=None):
res = CITypeRelation.get_by(to_dict=False)
type2attributes = dict()
result = []
for idx, item in enumerate(res):
_item = item.to_dict()
if type_ids is not None and _item['parent_id'] not in type_ids and _item['child_id'] not in type_ids:
continue
res[idx] = _item
res[idx]['parent'] = item.parent.to_dict()
if item.parent_id not in type2attributes:
@@ -785,7 +799,9 @@ class CITypeRelationManager(object):
CITypeAttributeManager.get_all_attributes(item.child_id)]
res[idx]['relation_type'] = item.relation_type.to_dict()
return res, type2attributes
result.append(res[idx])
return result, type2attributes
@staticmethod
def get_child_type_ids(type_id, level):
@@ -1034,7 +1050,8 @@ class CITypeAttributeGroupManager(object):
parent_ids = CITypeInheritanceManager.base(type_id)
groups = []
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
id2type = {i: CITypeCache.get(i) for i in parent_ids}
id2type = {k: v.alias for k, v in id2type.items() if v}
for _type_id in parent_ids + [type_id]:
_groups = CITypeAttributeGroup.get_by(type_id=_type_id)
_groups = sorted(_groups, key=lambda x: x["order"] or 0)
@@ -1308,13 +1325,14 @@ class CITypeTemplateManager(object):
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
attrs = []
for i in copy.deepcopy(attributes):
if i.pop('inherited', None):
continue
i.pop('default_show', None)
i.pop('is_required', None)
i.pop('order', None)
i.pop('choice_web_hook', None)
i.pop('choice_other', None)
i.pop('order', None)
i.pop('inherited', None)
i.pop('inherited_from', None)
choice_value = i.pop('choice_value', None)
if not choice_value:
@@ -1334,6 +1352,7 @@ class CITypeTemplateManager(object):
for i in ci_types:
i.pop("unique_key", None)
i.pop("show_name", None)
i.pop("parent_ids", None)
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
if i.get('show_id'):
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
@@ -1371,7 +1390,7 @@ class CITypeTemplateManager(object):
return self.__import(RelationType, relation_types)
@staticmethod
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map):
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map, attr_id_map):
for i in ci_type_relations:
i.pop('parent', None)
i.pop('child', None)
@@ -1381,15 +1400,32 @@ class CITypeTemplateManager(object):
i['child_id'] = type_id_map.get(i['child_id'], i['child_id'])
i['relation_type_id'] = relation_type_id_map.get(i['relation_type_id'], i['relation_type_id'])
i['parent_attr_ids'] = [attr_id_map.get(attr_id, attr_id) for attr_id in i.get('parent_attr_ids') or []]
i['child_attr_ids'] = [attr_id_map.get(attr_id, attr_id) for attr_id in i.get('child_attr_ids') or []]
try:
CITypeRelationManager.add(i.get('parent_id'),
i.get('child_id'),
i.get('relation_type_id'),
i.get('constraint'),
parent_attr_ids=i.get('parent_attr_ids', []),
child_attr_ids=i.get('child_attr_ids', []),
)
except BadRequest:
except Exception:
pass
@staticmethod
def _import_ci_type_inheritance(ci_type_inheritance, type_id_map):
for i in ci_type_inheritance:
i['parent_id'] = type_id_map.get(i['parent_id'])
i['child_id'] = type_id_map.get(i['child_id'])
if i['parent_id'] and i['child_id']:
try:
CITypeInheritanceManager.add([i.get('parent_id')], i.get('child_id'))
except BadRequest:
pass
@staticmethod
def _import_type_attributes(type2attributes, type_id_map, attr_id_map):
for type_id in type2attributes:
@@ -1401,6 +1437,9 @@ class CITypeTemplateManager(object):
handled = set()
for attr in type2attributes[type_id]:
if attr.get('inherited'):
continue
payload = dict(type_id=type_id_map.get(int(type_id), type_id),
attr_id=attr_id_map.get(attr['id'], attr['id']),
default_show=attr['default_show'],
@@ -1464,6 +1503,9 @@ class CITypeTemplateManager(object):
for rule in rules:
ci_type = CITypeCache.get(rule.pop('type_name', None))
if ci_type is None:
continue
adr = rule.pop('adr', {}) or {}
if ci_type:
@@ -1476,10 +1518,10 @@ class CITypeTemplateManager(object):
if ad_rule:
rule['adr_id'] = ad_rule.id
ad_rule.update(**adr)
ad_rule.update(valid=False, **adr)
elif adr:
ad_rule = AutoDiscoveryRuleCRUD().add(**adr)
ad_rule = AutoDiscoveryRuleCRUD().add(valid=False, **adr)
rule['adr_id'] = ad_rule.id
else:
continue
@@ -1508,6 +1550,23 @@ class CITypeTemplateManager(object):
except Exception as e:
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
@staticmethod
def _import_auto_discovery_relation_rules(rules):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
for rule in rules:
ad_ci_type = CITypeCache.get(rule.pop('ad_type_name', None))
peer_ci_type = CITypeCache.get(rule.pop('peer_type_name', None))
peer_attr = AttributeCache.get(rule.pop('peer_attr_name', None))
if ad_ci_type and peer_attr and peer_ci_type:
if not AutoDiscoveryCITypeRelation.get_by(
ad_type_id=ad_ci_type.id, ad_key=rule.get('ad_key'),
peer_attr_id=peer_attr.id, peer_type_id=peer_ci_type.id):
AutoDiscoveryCITypeRelationCRUD().add(ad_type_id=ad_ci_type.id,
ad_key=rule.get('ad_key'),
peer_attr_id=peer_attr.id,
peer_type_id=peer_ci_type.id)
@staticmethod
def _import_icons(icons):
from api.lib.common_setting.upload_file import CommonFileCRUD
@@ -1519,6 +1578,8 @@ class CITypeTemplateManager(object):
current_app.logger.warning("save icon failed: {}".format(e))
def import_template(self, tpt):
db.session.commit()
import time
s = time.time()
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
@@ -1537,9 +1598,14 @@ class CITypeTemplateManager(object):
current_app.logger.info('import relation_types cost: {}'.format(time.time() - s))
s = time.time()
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_id_map)
self._import_ci_type_relations(tpt.get('ci_type_relations') or [],
ci_type_id_map, relation_type_id_map, attr_id_map)
current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s))
s = time.time()
self._import_ci_type_inheritance(tpt.get('ci_type_inheritance') or [], ci_type_id_map)
current_app.logger.info('import ci_type_inheritance cost: {}'.format(time.time() - s))
s = time.time()
self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map)
current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s))
@@ -1552,26 +1618,73 @@ class CITypeTemplateManager(object):
self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or [])
current_app.logger.info('import ci_type_auto_discovery_rules cost: {}'.format(time.time() - s))
s = time.time()
self._import_auto_discovery_relation_rules(tpt.get('ci_type_auto_discovery_relation_rules') or [])
current_app.logger.info('import ci_type_auto_discovery_relation_rules cost: {}'.format(time.time() - s))
s = time.time()
self._import_icons(tpt.get('icons') or {})
current_app.logger.info('import icons cost: {}'.format(time.time() - s))
@staticmethod
def export_template():
def export_template(type_ids=None):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.common_setting.upload_file import CommonFileCRUD
ci_types = CITypeManager.get_ci_types(type_ids=type_ids)
extend_type_ids = []
for ci_type in ci_types:
if ci_type.get('parent_ids'):
extend_type_ids.extend(CITypeInheritanceManager.base(ci_type['id']))
extend_type_ids = list(set(extend_type_ids) - set(type_ids))
ci_type_relations = CITypeRelationManager.get(type_ids=type_ids)[0]
for i in ci_type_relations:
if i['parent_id'] not in type_ids:
extend_type_ids.append(i['parent_id'])
if i['child_id'] not in type_ids:
extend_type_ids.append(i['child_id'])
ad_relation_rules = AutoDiscoveryCITypeRelationCRUD.get_all(type_ids=type_ids)
rules = []
for r in ad_relation_rules:
if r.peer_type_id not in type_ids:
extend_type_ids.append(r.peer_type_id)
r = r.to_dict()
r['ad_type_name'] = CITypeCache.get(r.pop('ad_type_id')).name
peer_type_id = r.pop("peer_type_id")
peer_type_name = CITypeCache.get(peer_type_id).name
if not peer_type_name:
peer_type = CITypeCache.get(peer_type_id)
peer_type_name = peer_type and peer_type.name
r['peer_type_name'] = peer_type_name
peer_attr_id = r.pop("peer_attr_id")
peer_attr = AttributeCache.get(peer_attr_id)
r['peer_attr_name'] = peer_attr and peer_attr.name
rules.append(r)
ci_type_auto_discovery_relation_rules = rules
if extend_type_ids:
extend_type_ids = list(set(extend_type_ids))
type_ids.extend(extend_type_ids)
ci_types.extend(CITypeManager.get_ci_types(type_ids=extend_type_ids))
tpt = dict(
ci_types=CITypeManager.get_ci_types(),
ci_type_groups=CITypeGroupManager.get(),
ci_types=ci_types,
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
ci_type_relations=CITypeRelationManager.get()[0],
ci_type_relations=ci_type_relations,
ci_type_inheritance=CITypeInheritanceManager.get_all(type_ids=type_ids),
ci_type_auto_discovery_rules=list(),
ci_type_auto_discovery_relation_rules=ci_type_auto_discovery_relation_rules,
type2attributes=dict(),
type2attribute_group=dict(),
icons=dict()
)
tpt['ci_type_groups'] = CITypeGroupManager.get(ci_types=tpt['ci_types'], type_ids=type_ids)
def get_icon_value(icon):
try:
@@ -1579,12 +1692,13 @@ class CITypeTemplateManager(object):
except:
return ""
ad_rules = AutoDiscoveryCITypeCRUD.get_all()
type_id2name = {i['id']: i['name'] for i in tpt['ci_types']}
ad_rules = AutoDiscoveryCITypeCRUD.get_all(type_ids=type_ids)
rules = []
for r in ad_rules:
r = r.to_dict()
ci_type = CITypeCache.get(r.pop('type_id'))
r['type_name'] = ci_type and ci_type.name
r['type_name'] = type_id2name.get(r.pop('type_id'))
if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name
@@ -1618,65 +1732,6 @@ class CITypeTemplateManager(object):
return tpt
@staticmethod
def export_template_by_type(type_id):
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found2.format("id={}".format(type_id)))
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.common_setting.upload_file import CommonFileCRUD
tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
ci_type_auto_discovery_rules=list(),
type2attributes=dict(),
type2attribute_group=dict(),
icons=dict()
)
def get_icon_value(icon):
try:
return CommonFileCRUD().get_file_binary_str(icon)
except:
return ""
ad_rules = AutoDiscoveryCITypeCRUD.get_by_type_id(ci_type.id)
rules = []
for r in ad_rules:
r = r.to_dict()
r['type_name'] = ci_type and ci_type.name
if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name
r['adr'] = adr and adr.to_dict() or {}
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
if icon_url and icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
rules.append(r)
tpt['ci_type_auto_discovery_rules'] = rules
for ci_type in tpt['ci_types']:
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
icon_url = ci_type['icon'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
for attr in tpt['type2attributes'][ci_type['id']]:
for i in (attr.get('choice_value') or []):
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
icon_url = i[1]['icon']['url'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
return tpt
class CITypeUniqueConstraintManager(object):
@staticmethod

View File

@@ -93,7 +93,8 @@ class RoleEnum(BaseEnum):
class AutoDiscoveryType(BaseEnum):
AGENT = "agent"
SNMP = "snmp"
HTTP = "http"
HTTP = "http" # cloud
COMPONENTS = "components"
class AttributeDefaultValueEnum(BaseEnum):

View File

@@ -2,23 +2,24 @@
import copy
import json
import uuid
from io import BytesIO
from flask import abort
from flask import current_app
from flask import request
from flask_login import current_user
from io import BytesIO
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCICRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryComponentsManager
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCounterCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryExecHistoryCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryHTTPManager
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleSyncHistoryCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoverySNMPManager
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
@@ -42,7 +43,7 @@ class AutoDiscoveryRuleView(APIView):
rebuild = False
exists = {i['name'] for i in res}
for i in copy.deepcopy(DEFAULT_HTTP):
for i in copy.deepcopy(DEFAULT_INNER):
if i['name'] not in exists:
i.pop('en', None)
AutoDiscoveryRuleCRUD().add(**i)
@@ -110,12 +111,16 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
class AutoDiscoveryRuleHTTPView(APIView):
url_prefix = ("/adr/http/<string:name>/categories",
"/adr/http/<string:name>/attributes",
"/adr/snmp/<string:name>/attributes")
"/adr/snmp/<string:name>/attributes",
"/adr/components/<string:name>/attributes",)
def get(self, name):
if "snmp" in request.url:
return self.jsonify(AutoDiscoverySNMPManager.get_attributes())
if "components" in request.url:
return self.jsonify(AutoDiscoveryComponentsManager.get_attributes(name))
if "attributes" in request.url:
resource = request.values.get('resource')
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, resource))
@@ -250,7 +255,7 @@ class AutoDiscoveryRuleSyncView(APIView):
url_prefix = ("/adt/sync",)
def get(self):
if current_user.username not in ("cmdb_agent", "worker", "admin"):
if current_user.username not in PRIVILEGED_USERS:
return abort(403)
oneagent_name = request.values.get('oneagent_name')

View File

@@ -268,6 +268,7 @@ class CIBaselineView(APIView):
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
@args_required("before_date")
@has_perm_for_ci("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type)
def post(self, ci_id):
if 'rollback' in request.url:
before_date = request.values.get('before_date')

View File

@@ -51,7 +51,11 @@ class CITypeView(APIView):
q = request.args.get("type_name")
if type_id is not None:
ci_type = CITypeCache.get(type_id).to_dict()
ci_type = CITypeCache.get(type_id)
if ci_type is None:
return abort(404, ErrFormat.ci_type_not_found)
ci_type = ci_type.to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
ci_types = [ci_type]
elif type_name is not None:
@@ -357,15 +361,13 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def get(self, type_id=None): # export
if type_id is not None:
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
def get(self): # export
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids')))) or None
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template(type_ids=type_ids)))
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)

View File

@@ -59,7 +59,7 @@
"vue-template-compiler": "2.6.11",
"vuedraggable": "^2.23.0",
"vuex": "^3.1.1",
"vxe-table": "3.6.9",
"vxe-table": "3.7.10",
"vxe-table-plugin-export-xlsx": "2.0.0",
"xe-utils": "3",
"xlsx": "0.15.0",

View File

@@ -54,6 +54,78 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe96a;</span>
<div class="name">veops-markdown</div>
<div class="code-name">&amp;#xe96a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe860;</span>
<div class="name">veops-bar_horizontal</div>
<div class="code-name">&amp;#xe860;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe965;</span>
<div class="name">veops-gauge</div>
<div class="code-name">&amp;#xe965;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe966;</span>
<div class="name">veops-heatmap</div>
<div class="code-name">&amp;#xe966;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe967;</span>
<div class="name">veops-treemap</div>
<div class="code-name">&amp;#xe967;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe968;</span>
<div class="name">veops-radar</div>
<div class="code-name">&amp;#xe968;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe969;</span>
<div class="name">veops-data</div>
<div class="code-name">&amp;#xe969;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe963;</span>
<div class="name">veops-import</div>
<div class="code-name">&amp;#xe963;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe964;</span>
<div class="name">veops-batch_operation</div>
<div class="code-name">&amp;#xe964;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe962;</span>
<div class="name">cmdb-enterprise_edition</div>
<div class="code-name">&amp;#xe962;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe961;</span>
<div class="name">ops-KVM</div>
<div class="code-name">&amp;#xe961;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe960;</span>
<div class="name">cmdb-vcenter</div>
<div class="code-name">&amp;#xe960;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe95f;</span>
<div class="name">cmdb-manual_warehousing</div>
@@ -534,12 +606,6 @@
<div class="code-name">&amp;#xe862;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe860;</span>
<div class="name">veops-import</div>
<div class="code-name">&amp;#xe860;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe807;</span>
<div class="name">monitor-ip (1)</div>
@@ -5178,9 +5244,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1718872392430') format('woff2'),
url('iconfont.woff?t=1718872392430') format('woff'),
url('iconfont.ttf?t=1718872392430') format('truetype');
src: url('iconfont.woff2?t=1719487341033') format('woff2'),
url('iconfont.woff?t=1719487341033') format('woff'),
url('iconfont.ttf?t=1719487341033') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -5206,6 +5272,114 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont veops-markdown"></span>
<div class="name">
veops-markdown
</div>
<div class="code-name">.veops-markdown
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-bar_horizontal"></span>
<div class="name">
veops-bar_horizontal
</div>
<div class="code-name">.veops-bar_horizontal
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-gauge"></span>
<div class="name">
veops-gauge
</div>
<div class="code-name">.veops-gauge
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-heatmap"></span>
<div class="name">
veops-heatmap
</div>
<div class="code-name">.veops-heatmap
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-treemap"></span>
<div class="name">
veops-treemap
</div>
<div class="code-name">.veops-treemap
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-radar"></span>
<div class="name">
veops-radar
</div>
<div class="code-name">.veops-radar
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-data"></span>
<div class="name">
veops-data
</div>
<div class="code-name">.veops-data
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-import"></span>
<div class="name">
veops-import
</div>
<div class="code-name">.veops-import
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-batch_operation"></span>
<div class="name">
veops-batch_operation
</div>
<div class="code-name">.veops-batch_operation
</div>
</li>
<li class="dib">
<span class="icon iconfont cmdb-enterprise_edition"></span>
<div class="name">
cmdb-enterprise_edition
</div>
<div class="code-name">.cmdb-enterprise_edition
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-KVM"></span>
<div class="name">
ops-KVM
</div>
<div class="code-name">.ops-KVM
</div>
</li>
<li class="dib">
<span class="icon iconfont cmdb-vcenter"></span>
<div class="name">
cmdb-vcenter
</div>
<div class="code-name">.cmdb-vcenter
</div>
</li>
<li class="dib">
<span class="icon iconfont cmdb-manual_warehousing"></span>
<div class="name">
@@ -5693,11 +5867,11 @@
</li>
<li class="dib">
<span class="icon iconfont a-Group427319324"></span>
<span class="icon iconfont monitor-add2"></span>
<div class="name">
monitor-add2
</div>
<div class="code-name">.a-Group427319324
<div class="code-name">.monitor-add2
</div>
</li>
@@ -5926,15 +6100,6 @@
</div>
</li>
<li class="dib">
<span class="icon iconfont a-veops-import1"></span>
<div class="name">
veops-import
</div>
<div class="code-name">.a-veops-import1
</div>
</li>
<li class="dib">
<span class="icon iconfont monitor-ip"></span>
<div class="name">
@@ -12892,6 +13057,102 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-markdown"></use>
</svg>
<div class="name">veops-markdown</div>
<div class="code-name">#veops-markdown</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-bar_horizontal"></use>
</svg>
<div class="name">veops-bar_horizontal</div>
<div class="code-name">#veops-bar_horizontal</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-gauge"></use>
</svg>
<div class="name">veops-gauge</div>
<div class="code-name">#veops-gauge</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-heatmap"></use>
</svg>
<div class="name">veops-heatmap</div>
<div class="code-name">#veops-heatmap</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-treemap"></use>
</svg>
<div class="name">veops-treemap</div>
<div class="code-name">#veops-treemap</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-radar"></use>
</svg>
<div class="name">veops-radar</div>
<div class="code-name">#veops-radar</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-data"></use>
</svg>
<div class="name">veops-data</div>
<div class="code-name">#veops-data</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-import"></use>
</svg>
<div class="name">veops-import</div>
<div class="code-name">#veops-import</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-batch_operation"></use>
</svg>
<div class="name">veops-batch_operation</div>
<div class="code-name">#veops-batch_operation</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#cmdb-enterprise_edition"></use>
</svg>
<div class="name">cmdb-enterprise_edition</div>
<div class="code-name">#cmdb-enterprise_edition</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-KVM"></use>
</svg>
<div class="name">ops-KVM</div>
<div class="code-name">#ops-KVM</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#cmdb-vcenter"></use>
</svg>
<div class="name">cmdb-vcenter</div>
<div class="code-name">#cmdb-vcenter</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#cmdb-manual_warehousing"></use>
@@ -13326,10 +13587,10 @@
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-Group427319324"></use>
<use xlink:href="#monitor-add2"></use>
</svg>
<div class="name">monitor-add2</div>
<div class="code-name">#a-Group427319324</div>
<div class="code-name">#monitor-add2</div>
</li>
<li class="dib">
@@ -13532,14 +13793,6 @@
<div class="code-name">#veops-export</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-veops-import1"></use>
</svg>
<div class="name">veops-import</div>
<div class="code-name">#a-veops-import1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#monitor-ip"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1718872392430') format('woff2'),
url('iconfont.woff?t=1718872392430') format('woff'),
url('iconfont.ttf?t=1718872392430') format('truetype');
src: url('iconfont.woff2?t=1719487341033') format('woff2'),
url('iconfont.woff?t=1719487341033') format('woff'),
url('iconfont.ttf?t=1719487341033') format('truetype');
}
.iconfont {
@@ -13,6 +13,54 @@
-moz-osx-font-smoothing: grayscale;
}
.veops-markdown:before {
content: "\e96a";
}
.veops-bar_horizontal:before {
content: "\e860";
}
.veops-gauge:before {
content: "\e965";
}
.veops-heatmap:before {
content: "\e966";
}
.veops-treemap:before {
content: "\e967";
}
.veops-radar:before {
content: "\e968";
}
.veops-data:before {
content: "\e969";
}
.veops-import:before {
content: "\e963";
}
.veops-batch_operation:before {
content: "\e964";
}
.cmdb-enterprise_edition:before {
content: "\e962";
}
.ops-KVM:before {
content: "\e961";
}
.cmdb-vcenter:before {
content: "\e960";
}
.cmdb-manual_warehousing:before {
content: "\e95f";
}
@@ -229,7 +277,7 @@
content: "\e92a";
}
.a-Group427319324:before {
.monitor-add2:before {
content: "\e929";
}
@@ -333,10 +381,6 @@
content: "\e862";
}
.a-veops-import1:before {
content: "\e860";
}
.monitor-ip:before {
content: "\e807";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,90 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "40896913",
"name": "veops-markdown",
"font_class": "veops-markdown",
"unicode": "e96a",
"unicode_decimal": 59754
},
{
"icon_id": "40896859",
"name": "veops-bar_horizontal",
"font_class": "veops-bar_horizontal",
"unicode": "e860",
"unicode_decimal": 59488
},
{
"icon_id": "40896881",
"name": "veops-gauge",
"font_class": "veops-gauge",
"unicode": "e965",
"unicode_decimal": 59749
},
{
"icon_id": "40896882",
"name": "veops-heatmap",
"font_class": "veops-heatmap",
"unicode": "e966",
"unicode_decimal": 59750
},
{
"icon_id": "40896884",
"name": "veops-treemap",
"font_class": "veops-treemap",
"unicode": "e967",
"unicode_decimal": 59751
},
{
"icon_id": "40896887",
"name": "veops-radar",
"font_class": "veops-radar",
"unicode": "e968",
"unicode_decimal": 59752
},
{
"icon_id": "40896905",
"name": "veops-data",
"font_class": "veops-data",
"unicode": "e969",
"unicode_decimal": 59753
},
{
"icon_id": "40872369",
"name": "veops-import",
"font_class": "veops-import",
"unicode": "e963",
"unicode_decimal": 59747
},
{
"icon_id": "40872361",
"name": "veops-batch_operation",
"font_class": "veops-batch_operation",
"unicode": "e964",
"unicode_decimal": 59748
},
{
"icon_id": "40834860",
"name": "cmdb-enterprise_edition",
"font_class": "cmdb-enterprise_edition",
"unicode": "e962",
"unicode_decimal": 59746
},
{
"icon_id": "40832458",
"name": "ops-KVM",
"font_class": "ops-KVM",
"unicode": "e961",
"unicode_decimal": 59745
},
{
"icon_id": "40822644",
"name": "cmdb-vcenter",
"font_class": "cmdb-vcenter",
"unicode": "e960",
"unicode_decimal": 59744
},
{
"icon_id": "40795271",
"name": "cmdb-manual_warehousing",
@@ -386,7 +470,7 @@
{
"icon_id": "40372105",
"name": "monitor-add2",
"font_class": "a-Group427319324",
"font_class": "monitor-add2",
"unicode": "e929",
"unicode_decimal": 59689
},
@@ -565,13 +649,6 @@
"unicode": "e862",
"unicode_decimal": 59490
},
{
"icon_id": "40306881",
"name": "veops-import",
"font_class": "a-veops-import1",
"unicode": "e860",
"unicode_decimal": 59488
},
{
"icon_id": "40262335",
"name": "monitor-ip (1)",

Binary file not shown.

View File

@@ -50,3 +50,13 @@ export const putCITypeGroups = (data) => {
data: data
})
}
// 导出模型分组
export function exportCITypeGroups(params) {
return axios({
url: `${urlPrefix}/ci_types/template/export`,
method: 'GET',
params: params,
timeout: 30 * 1000,
})
}

View File

@@ -45,9 +45,9 @@ export function getHttpAttributes(name, params) {
})
}
export function getSnmpAttributes(name) {
export function getSnmpAttributes(type, name) {
return axios({
url: `/v0.1/adr/snmp/${name}/attributes`,
url: `/v0.1/adr/${type}/${name}/attributes`,
method: 'GET',
})
}

View File

@@ -1,21 +1,36 @@
<template>
<div class="http-ad-category">
<div class="http-ad-category-preview" v-if="currentCate">
<div class="http-ad-category-preview" v-if="currentCate && isPreviewDetail">
<div class="category-side">
<div
v-for="category in categories"
v-for="(category, categoryIndex) in categories"
:key="category.category"
class="category-side-item"
>
<div class="category-side-title">{{ category.category }}</div>
<div class="category-side-title">
<div class="category-side-title">
<a-icon
v-if="categoryIndex === 0"
type="left"
@click="clickBack"
/>
{{ category.category }}
</div>
</div>
<div class="category-side-children">
<div
v-for="item in category.items"
v-for="(item, itemIndex) in category.items"
:key="item"
:class="['category-side-children-item', item === currentCate ? 'category-side-children-item_active' : '']"
@click="clickCategory(item)"
>
{{ item }}
<span
class="category-side-children-item-corporate"
v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))"
>
</span>
</div>
</div>
</div>
@@ -35,19 +50,25 @@
/>
<div class="category-main">
<div
v-for="category in filterCategories"
v-for="(category, categoryIndex) in filterCategories"
:key="category.category"
class="category-item"
>
<div class="category-title">{{ category.category }}</div>
<div class="category-children">
<div
v-for="item in category.items"
v-for="(item, itemIndex) in category.items"
:key="item"
:class="['category-children-item', item === currentCate ? 'category-children-item_active' : '']"
@click="clickCategory(item)"
>
{{ item }}
<div
class="corporate-flag"
v-if="ruleType === 'private_cloud' || (ruleType === 'http' && (categoryIndex !== 0 || itemIndex !== 0))"
>
<span class="corporate-flag-text"></span>
</div>
</div>
</div>
</div>
@@ -81,10 +102,15 @@ export default {
type: Array,
default: () => [],
},
ruleType: {
type: String,
default: 'http',
},
},
data() {
return {
searchValue: ''
searchValue: '',
isPreviewDetail: false,
}
},
computed: {
@@ -105,6 +131,10 @@ export default {
},
clickCategory(item) {
this.$emit('clickCategory', item)
this.isPreviewDetail = true
},
clickBack() {
this.isPreviewDetail = false
}
}
}
@@ -127,8 +157,8 @@ export default {
padding-right: 10px;
&-item {
&:not(:first-child) {
margin-top: 24px;
&:not(:last-child) {
margin-bottom: 24px;
}
.category-side-title {
@@ -150,6 +180,12 @@ export default {
font-weight: 400;
cursor: pointer;
position: relative;
margin-top: 5px;
display: flex;
align-items: center;
justify-content: space-between;
&:hover {
background-color: @layout-sidebar-selected-color;
@@ -160,6 +196,20 @@ export default {
background-color: @layout-sidebar-selected-color;
color: @layout-header-font-selected-color;
}
&-corporate {
flex-shrink: 0;
width: 18px;
height: 18px;
background-color: #E1EFFF;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #2F54EB;
font-size: 12px;
}
}
}
}
@@ -201,6 +251,7 @@ export default {
font-weight: 400;
cursor: pointer;
position: relative;
&:hover {
background-color: @layout-sidebar-selected-color;
@@ -214,9 +265,33 @@ export default {
}
}
}
}
.corporate-tip {
margin-top: 20px;
.corporate-tip {
margin-top: 20px;
}
.corporate-flag {
position: absolute;
top: 0;
right: 0;
z-index: 4;
width: 38px;
height: 28px;
border-left: 38px solid transparent;
border-top: 28px solid @primary-color_4;
&-text {
width: 37px;
position: absolute;
top: -28px;
right: 3px;
text-align: right;
color: @primary-color;
font-size: 10px;
font-weight: 400;
}
}
}

View File

@@ -1,14 +1,15 @@
<template>
<div class="http-snmp-ad">
<HttpADCategory
v-if="!isEdit && ruleType === 'http'"
v-if="!isEdit && isCloud"
:categories="categories"
:currentCate="currentCate"
:tableData="tableData"
:ruleType="ruleType"
@clickCategory="setCurrentCate"
/>
<template v-else>
<a-select v-if="ruleType === 'http'" :style="{ marginBottom: '10px' }" v-model="currentCate">
<a-select v-if="isCloud" :style="{ marginBottom: '10px', minWidth: '120px' }" v-model="currentCate">
<a-select-option v-for="cate in categoriesSelect" :key="cate" :value="cate">{{ cate }}</a-select-option>
</a-select>
<AttrMapTable
@@ -89,15 +90,20 @@ export default {
腾讯云: { name: 'tencentcloud' },
华为云: { name: 'huaweicloud' },
AWS: { name: 'aws' },
VCenter: { name: 'vcenter' },
KVM: { name: 'kvm' },
}
},
isCloud() {
return ['http', 'private_cloud'].includes(this.ruleType)
}
},
watch: {
currentCate: {
immediate: true,
handler(newVal) {
if (newVal) {
getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { resource: newVal }).then((res) => {
getHttpAttributes(this.ruleName, { resource: newVal }).then((res) => {
if (this.isEdit) {
this.formatTableData(res)
} else {
@@ -113,8 +119,8 @@ export default {
this.currentCate = ''
this.$nextTick(() => {
const { ruleType, ruleName } = newVal
if (['snmp'].includes(ruleType) && ruleName) {
getSnmpAttributes(ruleName).then((res) => {
if (['snmp', 'components'].includes(ruleType) && ruleName) {
getSnmpAttributes(ruleType, ruleName).then((res) => {
if (this.isEdit) {
this.formatTableData(res)
} else {
@@ -122,8 +128,9 @@ export default {
}
})
}
if (ruleType === 'http' && ruleName) {
getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => {
if (this.isCloud && ruleName) {
getHttpCategories(this.ruleName).then((res) => {
this.categories = res
const categoriesSelect = []
res.forEach((category) => {

View File

@@ -37,6 +37,14 @@ const cmdb_en = {
editGroup: 'Edit Group',
group: 'Group',
attributeLibray: 'Attribute Library',
viewAttributeLibray: 'Attribute Library',
addGroup2: 'Add Group',
modelExport: 'Model Export',
filename: 'Filename',
filenameInputTips: 'Please enter filename',
selectModel: 'Select Model',
unselectModel: 'Unselected',
selectedModel: 'Selected',
addCITypeInGroup: 'Add a new CIType to the group',
addCIType: 'Add CIType',
editGroupName: 'Edit group name',
@@ -76,6 +84,8 @@ const cmdb_en = {
byInterval: 'by interval',
allNodes: 'All machines',
specifyNodes: 'Specify machine',
masterNode: 'Master machine',
masterNodeTip: 'The machine where OneMaster is installed',
specifyNodesTips: 'Please fill in the specify machine!',
username: 'Username',
password: 'Password',
@@ -243,6 +253,12 @@ const cmdb_en = {
relationADTip2: 'When an auto-discovered attribute matches an associated model attribute, the two instance models are automatically associated',
relationADTip3: 'If the value of the auto-discovered attribute is a list, multiple relationships are established with the association model',
deleteRelationAdTip: 'Cannot be deleted again',
cronTips: 'The format is the same as crontab, for example: 0 15 * * 1-5',
privateCloud: 'vSphere API Configuration',
host: 'Host',
account: 'Account',
insecure: 'Certificate Validation',
vcenterName: 'Platform Name',
},
components: {
unselectAttributes: 'Unselected',
@@ -479,6 +495,8 @@ const cmdb_en = {
snmp: 'Network Devices',
http: 'Public Clouds',
plugin: 'Plugin',
component: 'Databases & Middleware',
privateCloud: 'Private Clouds',
rule: 'AutoDiscovery Rules',
timeout: 'Timeout error',
mode: 'Mode',
@@ -648,7 +666,8 @@ if __name__ == "__main__":
confirmDeleteView: 'Are you sure you want to delete this view ?',
noInstancePerm: 'You do not have read permissions for this instance',
noPreferenceAttributes: 'This instance has no subscription attributes or no default displayed attributes',
topoViewSearchPlaceholder: 'Please enter the node name.'
topoViewSearchPlaceholder: 'Please enter the node name.',
moreBtn: 'Show more({count})'
},
}
export default cmdb_en

View File

@@ -37,6 +37,14 @@ const cmdb_zh = {
editGroup: '修改分组',
group: '分组',
attributeLibray: '属性库',
viewAttributeLibray: '查看属性库',
addGroup2: '添加分组',
modelExport: '模型导出',
filename: '文件名',
filenameInputTips: '请输入文件名',
selectModel: '请选择模型',
unselectModel: '未选',
selectedModel: '已选',
addCITypeInGroup: '在该组中新增CI模型',
addCIType: '新增CI模型',
editGroupName: '重命名分组',
@@ -76,6 +84,8 @@ const cmdb_zh = {
byInterval: '按间隔',
allNodes: '所有机器',
specifyNodes: '指定机器',
masterNode: 'Master机器',
masterNodeTip: '安装OneMaster的所在机器',
specifyNodesTips: '请填写指定机器!',
username: '用户名',
password: '密码',
@@ -243,6 +253,12 @@ const cmdb_zh = {
relationADTip2: '当自动发现属性与关联模型属性一致时,两实例模型则自动关联',
relationADTip3: '如果自动发现的属性值是列表,则会和关联模型建立多个关系',
deleteRelationAdTip: '不可再删除',
cronTips: '格式同crontab, 例如0 15 * * 1-5',
privateCloud: 'vSphere API配置',
host: '地址',
account: '账号',
insecure: '是否证书验证',
vcenterName: '虚拟平台名',
},
components: {
unselectAttributes: '未选属性',
@@ -478,7 +494,8 @@ const cmdb_zh = {
agent: '服务器',
snmp: '网络设备',
http: '公有云',
plugin: '插件',
component: '数据库 & 中间件',
privateCloud: '私有云',
rule: '自动发现规则',
timeout: '超时错误',
mode: '模式',
@@ -648,7 +665,8 @@ if __name__ == "__main__":
confirmDeleteView: '您确定要删除该视图吗?',
noInstancePerm: '您没有该实例的查看权限',
noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性',
topoViewSearchPlaceholder: '请输入节点名字'
topoViewSearchPlaceholder: '请输入节点名字',
moreBtn: '展示更多({count})'
},
}
export default cmdb_zh

View File

@@ -101,10 +101,14 @@
:cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }"
class="ops-unstripe-table"
class="ops-unstripe-table checkbox-hover-table"
:custom-config="{ storage: true }"
>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''">
<template #default="{row}">
{{ getRowSeq(row) }}
</template>
</vxe-column>
<vxe-table-column
v-for="(col, index) in columns"
:key="`${col.field}_${index}`"
@@ -1048,6 +1052,9 @@ export default {
this.visible = false
}
},
getRowSeq(row) {
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
}
},
}
</script>
@@ -1065,4 +1072,33 @@ export default {
overflow: auto;
margin-bottom: -24px;
}
.checkbox-hover-table {
/deep/ .vxe-table--body-wrapper {
.vxe-checkbox--label {
display: inline;
padding-left: 0px !important;
color: #bfbfbf;
}
.vxe-icon-checkbox-unchecked {
display: none;
}
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
display: none;
}
.vxe-cell--checkbox {
&:hover {
.vxe-icon-checkbox-unchecked {
display: inline;
}
.vxe-checkbox--label {
display: none;
}
}
}
}
}
</style>

View File

@@ -273,8 +273,12 @@ export default {
const adtIndex = this.clientCITypeList.findIndex((item) => item.id === id)
this.clientCITypeList[adtIndex].extra_option.alias = value
} else {
const adtIndex = this.adCITypeList.findIndex((item) => item.id === id)
const oldExtraOption = this.adCITypeList?.[adtIndex]?.extra_option
const params = {
extra_option: {
...(oldExtraOption || {}),
alias: value
}
}

View File

@@ -51,7 +51,7 @@
:wrapperCol="{ span: 14 }"
class="attr-ad-form"
>
<a-form-model-item :label="$t('cmdb.ciType.adExecTarget')">
<a-form-model-item :required="true" :label="$t('cmdb.ciType.adExecTarget')">
<CustomRadio v-model="agent_type" :radioList="agentTypeRadioList">
<a-input
:style="{ width: '300px' }"
@@ -69,6 +69,13 @@
>
<a @click="handleOpenCmdb" slot="suffix"><a-icon type="menu"/></a>
</a-input>
<span
v-show="agent_type === 'master'"
slot="extra_master"
class="radio-master-tip"
>
{{ $t('cmdb.ciType.masterNodeTip') }}
</span>
</CustomRadio>
</a-form-model-item>
<a-form-model-item
@@ -82,6 +89,7 @@
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
:label="$t('cmdb.ciType.adInterval')"
:required="true"
>
<el-popover v-model="cronVisible" trigger="click">
<template slot>
@@ -98,28 +106,59 @@
<a-input
v-model="cron"
slot="reference"
:placeholder="$t('cmdb.reconciliation.cronTips')"
:placeholder="$t('cmdb.ciType.cronTips')"
/>
</el-popover>
</a-form-model-item>
</a-form-model>
<template v-if="adrType === 'http'">
<div class="attr-ad-header attr-ad-header-margin">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
<div class="public-cloud-info">{{ $t('cmdb.ciType.cloudAccessKeyTip') }}</div>
<a-form-model
:model="form2"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item label="key">
<a-input-password v-model="form2.key" />
</a-form-model-item>
<a-form-model-item label="secret">
<a-input-password v-model="form2.secret" />
</a-form-model-item>
</a-form-model>
<template v-if="isPrivateCloud">
<template v-if="privateCloudName === PRIVATE_CLOUD_NAME.VCenter">
<div class="attr-ad-header">{{ $t('cmdb.ciType.privateCloud') }}</div>
<a-form-model
:model="privateCloudForm"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.host')">
<a-input v-model="privateCloudForm.host" />
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.account')">
<a-input v-model="privateCloudForm.account" />
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.password')">
<a-input-password v-model="privateCloudForm.password" />
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.ciType.insecure')">
<a-switch v-model="privateCloudForm.insecure" />
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.ciType.vcenterName')">
<a-input v-model="privateCloudForm.vcenterName" />
</a-form-model-item>
</a-form-model>
</template>
</template>
<template v-else>
<div class="attr-ad-header">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
<!-- <div class="public-cloud-info">{{ $t('cmdb.ciType.cloudAccessKeyTip') }}</div> -->
<a-form-model
:model="form2"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item :required="true" label="key">
<a-input-password v-model="form2.key" />
</a-form-model-item>
<a-form-model-item :required="true" label="secret">
<a-input-password v-model="form2.secret" />
</a-form-model-item>
</a-form-model>
</template>
</template>
<AttrADTest
@@ -138,6 +177,7 @@ import { v4 as uuidv4 } from 'uuid'
import { mapState } from 'vuex'
import Vcrontab from '@/components/Crontab'
import { putCITypeDiscovery, postCITypeDiscovery } from '../../api/discovery'
import { PRIVATE_CLOUD_NAME } from '@/modules/cmdb/views/discovery/constants.js'
import HttpSnmpAD from '../../components/httpSnmpAD'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
@@ -199,6 +239,13 @@ export default {
key: '',
secret: '',
},
privateCloudForm: {
host: '',
account: '',
password: '',
insecure: false,
vcenterName: '',
},
interval: 'cron', // interval cron
cron: '',
intervalValue: 3,
@@ -214,6 +261,9 @@ export default {
form3: this.$form.createForm(this, { name: 'snmp_form' }),
cronVisible: false,
uniqueKey: '',
isPrivateCloud: false,
privateCloudName: '',
PRIVATE_CLOUD_NAME
}
},
computed: {
@@ -225,7 +275,7 @@ export default {
return this.currentAdr?.type || ''
},
adrName() {
return this.currentAdr?.name || ''
return this?.currentAdr?.option?.en || this.currentAdr?.name || ''
},
adrIsInner() {
return this.currentAdr?.is_inner || ''
@@ -241,6 +291,10 @@ export default {
radios.unshift({ value: 'all', label: this.$t('cmdb.ciType.allNodes') })
}
if (this.adrType !== 'agent' || this?.currentAdr?.is_plugin) {
radios.unshift({ value: 'master', label: this.$t('cmdb.ciType.masterNode') })
}
return radios
},
radioList() {
@@ -250,7 +304,7 @@ export default {
]
},
labelCol() {
const span = this.$i18n.locale === 'en' ? 4 : 2
const span = this.$i18n.locale === 'en' ? 5 : 3
return {
span
}
@@ -264,11 +318,38 @@ export default {
this.uniqueKey = _find?.unique_key ?? ''
if (this.adrType === 'http') {
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
this.form2 = {
key,
secret,
const {
category = undefined,
key = '',
secret = '',
host = '',
account = '',
password = '',
insecure = false,
vcenterName = ''
} = _findADT?.extra_option ?? {}
if (_find?.option?.category === 'private_cloud') {
this.isPrivateCloud = true
this.privateCloudName = _find?.option?.en || ''
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
this.privateCloudForm = {
host,
account,
password,
insecure,
vcenterName,
}
}
} else {
this.isPrivateCloud = false
this.form2 = {
key,
secret,
}
}
this.$refs.httpSnmpAd.setCurrentCate(category)
}
if (this.adrType === 'snmp') {
@@ -308,13 +389,13 @@ export default {
}
this.form = {
auto_accept: _findADT?.auto_accept || false,
agent_id: _findADT.agent_id || '',
agent_id: _findADT?.agent_id && _findADT?.agent_id !== '0x0000' ? _findADT.agent_id : '',
query_expr: _findADT.query_expr || '',
}
if (_findADT.query_expr) {
this.agent_type = 'query_expr'
} else if (_findADT.agent_id) {
this.agent_type = 'agent_id'
this.agent_type = _findADT.agent_id === '0x0000' ? 'master' : 'agent_id'
} else {
this.agent_type = this.agentTypeRadioList[0].value
}
@@ -329,10 +410,25 @@ export default {
handleSave() {
const { currentAdt } = this
let params
const isError = this.validateForm()
if (isError) {
return
}
if (this.adrType === 'http') {
let cloudOption = {}
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
cloudOption = this.privateCloudForm
}
} else {
cloudOption = this.form2
}
params = {
extra_option: {
...this.form2,
...cloudOption,
category: this.$refs.httpSnmpAd.currentCate,
},
}
@@ -392,18 +488,23 @@ export default {
}
}
if (this.agent_type === 'master') {
params.agent_id = '0x0000'
}
if (!this.cron) {
this.$message.error(this.$t('cmdb.ciType.cronRequiredTip'))
return
}
if (currentAdt?.isClient) {
if (currentAdt?.extra_option) {
params.extra_option = {
...(params?.extra_option || {}),
...(currentAdt?.extra_option || {})
}
if (currentAdt?.extra_option) {
params.extra_option = {
...(currentAdt?.extra_option || {}),
...(params?.extra_option || {})
}
}
if (currentAdt?.isClient) {
postCITypeDiscovery(this.CITypeId, params).then((res) => {
this.$message.success(this.$t('saveSuccess'))
this.$emit('handleSave', res.id)
@@ -415,6 +516,40 @@ export default {
})
}
},
validateForm() {
let isError = false
if (this.adrType === 'http') {
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
const vcenterErros = {
'host': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.host')}`,
'account': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.account')}`,
'password': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.password')}`
}
const findError = Object.keys(this.privateCloudForm).find((key) => !this.privateCloudForm[key] && vcenterErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(vcenterErros[findError]))
}
}
} else {
const publicCloudErros = {
'key': `${this.$t('placeholder1')} key`,
'secret': `${this.$t('placeholder1')} secret`
}
const findError = Object.keys(this.form2).find((key) => !this.form2[key] && publicCloudErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(publicCloudErros[findError]))
}
}
}
return isError
},
handleOpenCmdb() {
this.$refs.cmdbDrawer.open()
},
@@ -458,6 +593,11 @@ export default {
margin-left: 17px;
margin-bottom: 20px;
}
.radio-master-tip {
font-size: 12px;
color: #86909c;
}
}
.attr-ad-snmp-form {
.ant-form-item {

View File

@@ -241,8 +241,8 @@ export default {
<style lang="less" scoped>
.attribute-card {
width: 182px;
height: 80px;
width: 172px;
height: 75px;
background: @primary-color_6;
border-radius: 2px;
position: relative;
@@ -308,7 +308,7 @@ export default {
}
}
.attribute-card-footer {
width: 182px;
width: 172px;
height: 30px;
padding: 0 8px;
position: absolute;

View File

@@ -104,7 +104,7 @@
:filter="'.filter-empty'"
:animation="300"
tag="div"
style="width: 100%; display: flex;flex-flow: wrap"
style="width: 100%; display: flex; flex-flow: wrap; column-gap: 10px;"
handle=".handle"
>
<AttributeCard
@@ -146,7 +146,7 @@
}
"
:animation="300"
style="min-height: 2rem; width: 100%; display: flex; flex-flow: wrap"
style="min-height: 2rem; width: 100%; display: flex; flex-flow: wrap; column-gap: 10px;"
handle=".handle"
>
<AttributeCard

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
<template>
<a-modal
:visible="visible"
:title="$t('cmdb.ciType.modelExport')"
:width="560"
@cancel="handleCancel"
@ok="handleOK"
>
<a-form
:form="form"
:labelCol="{ span: 5 }"
:wrapperCol="{ span: 19 }"
>
<a-form-item
:label="$t('cmdb.ciType.filename')"
>
<a-input v-decorator="['name', { rules: [{ required: true, message: $t('cmdb.ciType.filenameInputTips') }], initialValue: 'cmdb_template' }]" />
</a-form-item>
<a-form-item
:label="$t('cmdb.ciType.selectModel')"
>
<a-transfer
class="model-export-transfer"
:dataSource="transferDataSource"
:targetKeys="targetKeys"
:render="item => item.title"
:titles="[$t('cmdb.ciType.unselectModel'), $t('cmdb.ciType.selectedModel')]"
:listStyle="{
width: '180px',
height: `262px`,
}"
@change="onChange"
>
<template
slot="children"
slot-scope="{ props: { direction, selectedKeys }, on: { itemSelect } }"
>
<a-tree
v-if="direction === 'left'"
blockNode
checkable
:checkedKeys="[...selectedKeys, ...targetKeys]"
:treeData="treeData"
:checkStrictly="true"
@check="
(_, props) => {
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect);
}
"
@select="
(_, props) => {
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect);
}
"
/>
</template>
</a-transfer>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
import _ from 'lodash'
import { exportCITypeGroups } from '@/modules/cmdb/api/ciTypeGroup'
export default {
name: 'ModelExport',
props: {
visible: {
type: Boolean,
default: false,
},
CITypeGroups: {
type: Array,
default: () => []
}
},
data() {
return {
form: this.$form.createForm(this, { name: 'model-export' }),
targetKeys: [],
btnLoading: false,
}
},
computed: {
transferDataSource() {
const dataSource = this.CITypeGroups.reduce((acc, item) => {
const types = _.cloneDeep(item?.ci_types || [])
types.forEach((item) => {
item.key = String(item.id)
item.title = item?.alias || item?.name || this.$t('other')
})
return acc.concat(types)
}, [])
return dataSource
},
treeData() {
const treeData = _.cloneDeep(this.CITypeGroups)
let newTreeData = treeData.map((item) => {
const childrenKeys = []
const children = (item.ci_types || []).map((child) => {
const key = String(child?.id)
const disabled = this.targetKeys.includes(key)
childrenKeys.push(key)
return {
key,
title: child?.alias || child?.name || this.$t('other'),
disabled,
checkable: true,
children: []
}
})
return {
key: String(item?.id),
title: item?.name || this.$t('other'),
children,
childrenKeys,
disabled: children.every((item) => item.disabled),
checkable: false,
selectable: false
}
})
console.log('treeData', newTreeData)
newTreeData = newTreeData.filter((item) => item.children.length > 0)
return newTreeData
}
},
methods: {
onChange(targetKeys, direction, moveKeys) {
this.targetKeys = targetKeys
},
onChecked(_, e, checkedKeys, itemSelect) {
const { eventKey } = e.node
const selected = checkedKeys.indexOf(eventKey) === -1
itemSelect(eventKey, selected)
},
handleCancel() {
this.$emit('cancel')
this.form.resetFields()
this.targetKeys = []
},
handleOK() {
this.form.validateFields(async (err, values) => {
if (err || !this.targetKeys.length || this.btnLoading) {
return
}
this.btnLoading = true
const hide = this.$message.loading(this.$t('loading'), 0)
try {
const typeIds = this.targetKeys.join(',')
const res = await exportCITypeGroups({
type_ids: typeIds
})
console.log('exportCITypeGroups res', res)
if (res) {
const jsonStr = JSON.stringify(res)
const blob = new Blob([jsonStr], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
const fileName = values.name
a.download = fileName
a.click()
URL.revokeObjectURL(url)
}
} catch (error) {
console.log('exportCITypeGroups fail', error)
hide()
this.btnLoading = false
}
hide()
this.btnLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.model-export-transfer {
/deep/ .ant-transfer-list-body {
overflow: auto;
}
/deep/ .ant-transfer-list-header-title {
color: @primary-color;
font-weight: 400;
font-size: 12px;
}
}
</style>

View File

@@ -2,5 +2,11 @@ export const DISCOVERY_CATEGORY_TYPE = {
AGENT: 'agent',
SNMP: 'snmp',
HTTP: 'http',
PLUGIN: 'plugin'
PLUGIN: 'plugin',
COMPONENT: 'components',
PRIVATE_CLOUD: 'private_cloud'
}
export const PRIVATE_CLOUD_NAME = {
VCenter: 'vcenter'
}

View File

@@ -53,11 +53,12 @@
:key="index"
v-if="index < 2"
class="discovery-resources-item"
:style="{ maxWidth: rule.resources.length >= 2 ? '70px' : '160px' }"
>
{{ item }}
</span>
</template>
<span v-if="rule.resources.length >= 2" class="discovery-resources-item">
<span v-if="rule.resources.length > 2" class="discovery-resources-item">
<ops-icon type="veops-more" />
</span>
</div>
@@ -234,6 +235,7 @@ export default {
color: @text-color_3;
font-size: 12px;
font-weight: 400;
flex-shrink: 0;
}
&-right {
@@ -249,7 +251,6 @@ export default {
color: @text-color_3;
font-size: 11px;
font-weight: 400;
max-width: 95px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;

View File

@@ -112,7 +112,7 @@
</div>
</template>
<template v-else>
<HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleData.name" />
<HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleName" />
</template>
</CustomDrawer>
</template>
@@ -171,7 +171,7 @@ export default {
},
computed: {
title() {
if ([DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.AGENT].includes(this.adType)) {
if ([DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.AGENT, DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD, DISCOVERY_CATEGORY_TYPE.COMPONENT].includes(this.adType)) {
return this.ruleData.name
}
if (this.type === 'edit') {
@@ -179,6 +179,9 @@ export default {
}
return this.$t('new')
},
ruleName() {
return this?.ruleData?.option?.en || this?.ruleData?.name || ''
}
},
inject: {
getDiscovery: {
@@ -202,7 +205,7 @@ export default {
return
}
this.$nextTick(() => {
if (adType === DISCOVERY_CATEGORY_TYPE.AGENT) {
if ([DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD].includes(adType)) {
this.tableData = data?.attributes ?? []
return
}

View File

@@ -39,7 +39,10 @@
</a>
</div>
</div>
<div class="setting-discovery-body">
<div
class="setting-discovery-body"
:style="{ height: !isSelected ? `${windowHeight - 155}px` : '' }"
>
<template v-if="!showNullData">
<div v-for="{ type, label } in typeCategory" :key="type">
<template v-if="filterCategoryChildren[type] && (filterCategoryChildren[type].children.length || (showAddPlugin && type === DISCOVERY_CATEGORY_TYPE.PLUGIN))">
@@ -79,6 +82,7 @@
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
import { getDiscovery, deleteDiscovery } from '../../api/discovery'
import { DISCOVERY_CATEGORY_TYPE } from './constants.js'
@@ -103,16 +107,27 @@ export default {
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
typeCategory() {
return [
{
type: DISCOVERY_CATEGORY_TYPE.HTTP,
label: this.$t('cmdb.ad.http'),
},
{
type: DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD,
label: this.$t('cmdb.ad.privateCloud'),
},
{
type: DISCOVERY_CATEGORY_TYPE.AGENT,
label: this.$t('cmdb.ad.agent'),
},
{
type: DISCOVERY_CATEGORY_TYPE.COMPONENT,
label: this.$t('cmdb.ad.component'),
},
{
type: DISCOVERY_CATEGORY_TYPE.SNMP,
label: this.$t('cmdb.ad.snmp'),
@@ -162,10 +177,18 @@ export default {
type: DISCOVERY_CATEGORY_TYPE.HTTP,
children: []
},
[DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD]: {
type: DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD,
children: []
},
[DISCOVERY_CATEGORY_TYPE.AGENT]: {
type: DISCOVERY_CATEGORY_TYPE.AGENT,
children: []
},
[DISCOVERY_CATEGORY_TYPE.COMPONENT]: {
type: DISCOVERY_CATEGORY_TYPE.COMPONENT,
children: []
},
[DISCOVERY_CATEGORY_TYPE.SNMP]: {
type: DISCOVERY_CATEGORY_TYPE.SNMP,
children: []
@@ -179,6 +202,12 @@ export default {
this.typeCategory.forEach(({ type }) => {
let categoryChildren = []
switch (type) {
case DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD:
categoryChildren = res.filter((list) => list?.option?.category === 'private_cloud' && list?.type === 'http')
break
case DISCOVERY_CATEGORY_TYPE.HTTP:
categoryChildren = res.filter((list) => list?.option?.category !== 'private_cloud' && list?.type === 'http')
break
case DISCOVERY_CATEGORY_TYPE.PLUGIN:
categoryChildren = res.filter((list) => list.is_plugin)
break
@@ -269,6 +298,7 @@ export default {
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
&-btn {
display: flex;
@@ -284,6 +314,7 @@ export default {
&-search {
width: 254px;
flex-shrink: 0;
}
&-radio {
@@ -291,6 +322,7 @@ export default {
align-items: center;
margin-left: 15px;
gap: 15px;
overflow: auto;
&-item {
padding: 4px 14px;
@@ -298,6 +330,7 @@ export default {
font-weight: 400;
line-height: 24px;
cursor: pointer;
flex-shrink: 0;
&_active {
background-color: @primary-color_3;
@@ -312,6 +345,7 @@ export default {
box-shadow: 0px 0px 4px 0px rgba(158, 171, 190, 0.25);
padding: 20px;
margin-top: 24px;
overflow: auto;
.setting-discovery-add {
height: 105px;
@@ -339,6 +373,10 @@ export default {
&-text {
margin-top: 20px;
}
&-img {
width: 100px;
}
}
}

View File

@@ -58,7 +58,7 @@
ref="xTable"
size="mini"
stripe
class="ops-stripe-table"
class="ops-stripe-table checkbox-hover-table"
:data="filterTableData"
:height="tableHeight"
:scroll-y="{ enabled: true, gt: 50 }"
@@ -74,7 +74,11 @@
type="checkbox"
width="60"
fixed="left"
></vxe-column>
>
<template #default="{row}">
{{ getRowSeq(row) }}
</template>
</vxe-column>
<vxe-column
v-for="(col, index) in columns"
:key="`${col.field}_${index}`"
@@ -399,6 +403,9 @@ export default {
textEl.scrollTop = textEl.scrollHeight
}
})
},
getRowSeq(row) {
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
}
},
}
@@ -462,6 +469,36 @@ export default {
align-items: center;
gap: 6px;
}
.checkbox-hover-table {
/deep/ .vxe-table--body-wrapper {
.vxe-checkbox--label {
display: inline;
padding-left: 0px !important;
color: #bfbfbf;
}
.vxe-icon-checkbox-unchecked {
display: none;
}
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
display: none;
}
.vxe-cell--checkbox {
&:hover {
.vxe-icon-checkbox-unchecked {
display: inline;
}
.vxe-checkbox--label {
display: none;
}
}
}
}
}
}
.log-modal-title {

File diff suppressed because it is too large Load Diff

View File

@@ -32,8 +32,11 @@
ghost
@click="handleClickAddGroup"
class="ops-button-ghost"
><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
>
<ops-icon type="veops-increase" />
{{ $t('cmdb.ciType.group') }}
</a-button>
</div>
<draggable class="topo-left-content" :list="computedTopoGroups" @end="handleChangeGroups" filter=".undraggable">
<div v-for="group in computedTopoGroups" :key="group.id || group.name">
@@ -56,16 +59,16 @@
<a-space>
<a-tooltip>
<template slot="title">{{ $t('cmdb.topo.addTopoViewInGroup') }}</template>
<a><ops-icon type="veops-increase" @click="handleCreate(group)"/></a>
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"><ops-icon type="veops-increase" @click="handleCreate(group)"/></a>
</a-tooltip>
<template v-if="group.id">
<a-tooltip >
<template slot="title">{{ $t('cmdb.ciType.editGroup') }}</template>
<a><a-icon type="edit" @click="handleEditGroup(group)"/></a>
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"><a-icon type="edit" @click="handleEditGroup(group)"/></a>
</a-tooltip>
<a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
<a :style="{color: 'red'}"><a-icon type="delete" @click="handleDeleteGroup(group)"/></a>
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')" :style="{color: 'red'}"><a-icon type="delete" @click="handleDeleteGroup(group)"/></a>
</a-tooltip>
</template>
</a-space>
@@ -157,7 +160,9 @@
class="relation-graph-node-icon"
/>
</template>
<span class="relation-graph-node-text">{{ node.text }}</span>
<span class="relation-graph-node-text">
{{ node.data.btnType === 'more' ? $t('cmdb.topo.moreBtn', { count: node.text }) : node.text }}
</span>
</div>
</template>
<template #graph-plug>
@@ -957,7 +962,7 @@ export default {
const id = uuidv4()
jsonData.nodes.set(id, {
id,
text: `展示更多(${childs.length - showNodeCount})`,
text: childs.length - showNodeCount,
data: {
btnType: 'more'
},
@@ -1005,7 +1010,7 @@ export default {
if (showNodeCount === childs.length) {
moreBtnNode.isHide = true
} else {
moreBtnNode.text = `展示更多(${childs.length - showNodeCount})`
moreBtnNode.text = childs.length - showNodeCount
}
}

View File

@@ -172,10 +172,14 @@
@edit-closed="handleEditClose"
@edit-actived="handleEditActived"
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
class="ops-unstripe-table"
class="ops-unstripe-table checkbox-hover-table"
:custom-config="{ storage: true }"
>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''">
<template #default="{row}">
{{ getRowSeq(row) }}
</template>
</vxe-column>
<vxe-table-column
v-for="(col, index) in columns"
:key="`${col.field}_${index}`"
@@ -1229,6 +1233,9 @@ export default {
}
)
},
getRowSeq(row) {
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
}
},
}
</script>
@@ -1337,6 +1344,36 @@ export default {
overflow: auto;
width: 100%;
border-radius: @border-radius-box;
.checkbox-hover-table {
.vxe-table--body-wrapper {
.vxe-checkbox--label {
display: inline;
padding-left: 0px !important;
color: #bfbfbf;
}
.vxe-icon-checkbox-unchecked {
display: none;
}
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
display: none;
}
.vxe-cell--checkbox {
&:hover {
.vxe-icon-checkbox-unchecked {
display: inline;
}
.vxe-checkbox--label {
display: none;
}
}
}
}
}
}
}
</style>

View File

@@ -41,7 +41,7 @@ services:
- redis
cmdb-api:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.6
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.7
container_name: cmdb-api
env_file:
- .env
@@ -71,6 +71,7 @@ services:
flask cmdb-init-acl
flask init-import-user-from-acl
flask init-department
flask cmdb-patch -v 2.4.7
flask cmdb-counter > counter.log 2>&1
networks:
new:
@@ -83,7 +84,7 @@ services:
test: "ps aux|grep -v grep|grep -v '1 root'|grep gunicorn || exit 1"
cmdb-ui:
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.6
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.7
container_name: cmdb-ui
depends_on:
cmdb-api:

View File

@@ -1,5 +1,5 @@
# ================================= UI ================================
FROM node:14.17.3-alpine AS builder
FROM node:16.20 AS builder
LABEL description="cmdb-ui"
@@ -7,10 +7,10 @@ COPY cmdb-ui /data/apps/cmdb-ui
WORKDIR /data/apps/cmdb-ui
RUN sed -i "s#http://127.0.0.1:5000##g" .env && yarn install --ignore-engines && yarn build
RUN sed -i "s#http://127.0.0.1:5000##g" .env && yarn install --ignore-engines --network-timeout 1000000 && yarn build
FROM nginx:alpine AS cmdb-ui
RUN mkdir /etc/nginx/html && rm -f /etc/nginx/conf.d/default.conf
COPY --from=builder /data/apps/cmdb-ui/dist /etc/nginx/html/
COPY --from=builder /data/apps/cmdb-ui/dist /etc/nginx/html/