mirror of
https://github.com/veops/cmdb.git
synced 2025-09-13 15:06:53 +08:00
Compare commits
34 Commits
dev_common
...
v2.4.8
Author | SHA1 | Date | |
---|---|---|---|
|
3a00bfd236 | ||
|
2e97ebd895 | ||
|
eb6a813cbc | ||
|
ff78face48 | ||
|
d55433c438 | ||
|
daf0254616 | ||
|
6b32009955 | ||
|
d53288c1fb | ||
|
586d820a08 | ||
|
6776be4599 | ||
|
ff2b8ea198 | ||
|
ed46a1e1c1 | ||
|
0dc614fb46 | ||
|
bc66d33ce0 | ||
|
d5db68d7d0 | ||
|
b22b8b286b | ||
|
dd4f3b0e9c | ||
|
688f4e0ea4 | ||
|
c1813f525d | ||
|
b405e28498 | ||
|
fa32758462 | ||
|
29995b660a | ||
|
b96fc06a62 | ||
|
c7f30b63ff | ||
|
5bff69a8a8 | ||
|
d5e60fab88 | ||
|
190f452118 | ||
|
98a4824364 | ||
|
c0f9baea79 | ||
|
d4b661c77f | ||
|
75cd7bde77 | ||
|
ec912d3a65 | ||
|
42f02b4986 | ||
|
a13b999820 |
32
.github/workflows/docker-build-and-release.yaml
vendored
32
.github/workflows/docker-build-and-release.yaml
vendored
@@ -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:
|
||||
|
24
README.md
24
README.md
@@ -73,7 +73,8 @@
|
||||
## 安装
|
||||
|
||||
### Docker 一键快速构建
|
||||
> 方法一
|
||||
|
||||
[//]: # (> 方法一)
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
@@ -83,13 +84,20 @@ git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
> 方法二, 该方法适用于linux系统
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
||||
```shell
|
||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
||||
sh install.sh install
|
||||
```
|
||||
|
||||
[//]: # (> 方法二, 该方法适用于linux系统)
|
||||
|
||||
[//]: # (- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2))
|
||||
|
||||
[//]: # (- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`)
|
||||
|
||||
[//]: # (```shell)
|
||||
|
||||
[//]: # (curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh)
|
||||
|
||||
[//]: # (sh install.sh install)
|
||||
|
||||
[//]: # (```)
|
||||
|
||||
### [本地开发环境搭建](docs/local.md)
|
||||
|
||||
|
@@ -67,6 +67,7 @@ colorama = ">=0.4.6"
|
||||
pycryptodomex = ">=3.19.0"
|
||||
lz4 = ">=4.3.2"
|
||||
python-magic = "==0.4.27"
|
||||
jsonpath = "==0.82.2"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
@@ -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))
|
||||
|
@@ -2,17 +2,19 @@
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import jsonpath
|
||||
import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
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 CLOUD_MAP
|
||||
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 AutoDiscoveryMappingCache
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
@@ -21,7 +23,9 @@ from api.lib.cmdb.ci_type import CITypeGroupManager
|
||||
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 RelationSourceEnum
|
||||
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 +113,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 +136,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 +148,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 +185,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 +206,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 +227,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 []
|
||||
@@ -218,12 +247,21 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
rules = cls.cls.get_by(to_dict=True)
|
||||
|
||||
for rule in rules:
|
||||
if not rule['enabled']:
|
||||
continue
|
||||
|
||||
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']:
|
||||
@@ -239,6 +277,12 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
result.append(rule)
|
||||
break
|
||||
elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']:
|
||||
try:
|
||||
if not int(oneagent_id, 16): # excludes master
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(rule['adr_id'])
|
||||
if not adr:
|
||||
continue
|
||||
@@ -247,17 +291,18 @@ 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:
|
||||
@@ -320,17 +365,18 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
|
||||
if adr.type == "http":
|
||||
kwargs.setdefault('extra_option', dict)
|
||||
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
|
||||
if en_name and kwargs['extra_option'].get('category'):
|
||||
for item in ClOUD_MAP[en_name]:
|
||||
for item in CLOUD_MAP[en_name]:
|
||||
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
||||
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
||||
kwargs['extra_option']['category']]
|
||||
kwargs["extra_option"]["provider"] = en_name
|
||||
break
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
@@ -338,10 +384,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
|
||||
@@ -355,17 +404,18 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
adr = AutoDiscoveryRule.get_by_id(existed.adr_id) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(existed.adr_id)))
|
||||
if adr.type == "http":
|
||||
kwargs.setdefault('extra_option', dict)
|
||||
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
|
||||
if en_name and kwargs['extra_option'].get('category'):
|
||||
for item in ClOUD_MAP[en_name]:
|
||||
for item in CLOUD_MAP[en_name]:
|
||||
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
||||
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
||||
kwargs['extra_option']['category']]
|
||||
kwargs["extra_option"]["provider"] = en_name
|
||||
break
|
||||
|
||||
if 'attributes' in kwargs:
|
||||
@@ -374,11 +424,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,13 +443,20 @@ 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'):
|
||||
if len(kwargs) == 1 and 'enabled' in kwargs: # enable or disable
|
||||
pass
|
||||
elif inst.agent_id != kwargs.get('agent_id') or inst.query_expr != kwargs.get('query_expr'):
|
||||
for item in AutoDiscoveryRuleSyncHistory.get_by(adt_id=inst.id, to_dict=False):
|
||||
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 +490,11 @@ 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)
|
||||
@@ -493,7 +559,6 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
for adt in adts:
|
||||
attr_names |= set((adt.attributes or {}).values())
|
||||
|
||||
return [attr for attr in attributes if attr['name'] in attr_names]
|
||||
|
||||
@classmethod
|
||||
@@ -624,7 +689,16 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
ci_id = None
|
||||
if adt.attributes:
|
||||
ci_dict = {adt.attributes[k]: v for k, v in adc.instance.items() if k in adt.attributes}
|
||||
ci_dict = {adt.attributes[k]: None if not v and isinstance(v, (list, dict)) else v
|
||||
for k, v in adc.instance.items() if k in adt.attributes}
|
||||
extra_option = adt.extra_option or {}
|
||||
mapping, path_mapping = AutoDiscoveryHTTPManager.get_predefined_value_mapping(
|
||||
extra_option.get('provider'), extra_option.get('category'))
|
||||
if mapping:
|
||||
ci_dict = {k: (mapping.get(k) or {}).get(str(v), v) for k, v in ci_dict.items()}
|
||||
if path_mapping:
|
||||
ci_dict = {k: jsonpath.jsonpath(v, path_mapping[k]) if k in path_mapping else v
|
||||
for k, v in ci_dict.items()}
|
||||
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, _is_admin=True, **ci_dict)
|
||||
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
|
||||
stdout="accept resource: {}".format(adc.unique_value))
|
||||
@@ -649,10 +723,15 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
for relation_ci in response:
|
||||
relation_ci_id = relation_ci['_id']
|
||||
try:
|
||||
CIRelationManager.add(ci_id, relation_ci_id, valid=False)
|
||||
CIRelationManager.add(ci_id, relation_ci_id,
|
||||
valid=False,
|
||||
source=RelationSourceEnum.AUTO_DISCOVERY)
|
||||
|
||||
except:
|
||||
try:
|
||||
CIRelationManager.add(relation_ci_id, ci_id, valid=False)
|
||||
CIRelationManager.add(relation_ci_id, ci_id,
|
||||
valid=False,
|
||||
source=RelationSourceEnum.AUTO_DISCOVERY)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -665,15 +744,16 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
class AutoDiscoveryHTTPManager(object):
|
||||
@staticmethod
|
||||
def get_categories(name):
|
||||
categories = (ClOUD_MAP.get(name) or {}) or []
|
||||
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
|
||||
@@ -687,16 +767,52 @@ class AutoDiscoveryHTTPManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_attributes(provider, resource):
|
||||
for item in (ClOUD_MAP.get(provider) or {}):
|
||||
for item in (CLOUD_MAP.get(provider) or {}):
|
||||
for _resource in (item.get('map') or {}):
|
||||
if _resource == resource:
|
||||
tpt = item['map'][_resource]
|
||||
if isinstance(tpt, dict):
|
||||
tpt = tpt.get('template')
|
||||
if tpt and os.path.exists(os.path.join(PWD, tpt)):
|
||||
with open(os.path.join(PWD, tpt)) as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_mapping(provider, resource):
|
||||
for item in (CLOUD_MAP.get(provider) or {}):
|
||||
for _resource in (item.get('map') or {}):
|
||||
if _resource == resource:
|
||||
mapping = item['map'][_resource]
|
||||
if not isinstance(mapping, dict):
|
||||
return {}
|
||||
name = mapping.get('mapping')
|
||||
mapping = AutoDiscoveryMappingCache.get(name)
|
||||
if isinstance(mapping, dict):
|
||||
return {mapping[key][provider]['key'].split('.')[0]: key for key in mapping if
|
||||
(mapping[key].get(provider) or {}).get('key')}
|
||||
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def get_predefined_value_mapping(provider, resource):
|
||||
for item in (CLOUD_MAP.get(provider) or {}):
|
||||
for _resource in (item.get('map') or {}):
|
||||
if _resource == resource:
|
||||
mapping = item['map'][_resource]
|
||||
if not isinstance(mapping, dict):
|
||||
return {}, {}
|
||||
name = mapping.get('mapping')
|
||||
mapping = AutoDiscoveryMappingCache.get(name)
|
||||
if isinstance(mapping, dict):
|
||||
return ({key: mapping[key][provider].get('map') for key in mapping if
|
||||
mapping[key].get(provider, {}).get('map')},
|
||||
{key: mapping[key][provider]['key'].split('.', 1)[1] for key in mapping if
|
||||
((mapping[key].get(provider) or {}).get('key') or '').split('.')[1:]})
|
||||
|
||||
return {}, {}
|
||||
|
||||
|
||||
class AutoDiscoverySNMPManager(object):
|
||||
|
||||
@@ -709,6 +825,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
|
||||
|
||||
|
@@ -2,15 +2,27 @@
|
||||
|
||||
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'}}),
|
||||
option={'icon': {'name': 'caise-aws'}, "en": "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'}}),
|
||||
@@ -22,48 +34,307 @@ DEFAULT_HTTP = [
|
||||
option={'icon': {'name': 'caise-dayinji'}}),
|
||||
]
|
||||
|
||||
ClOUD_MAP = {
|
||||
"aliyun": [{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": "templates/aliyun_ecs.json",
|
||||
CLOUD_MAP = {
|
||||
"aliyun": [
|
||||
{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 ECS", "云服务器 Disk"],
|
||||
"map": {
|
||||
"云服务器 ECS": {"template": "templates/aliyun_ecs.json", "mapping": "ecs"},
|
||||
"云服务器 Disk": {"template": "templates/aliyun_ecs_disk.json", "mapping": "evs"},
|
||||
},
|
||||
"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": {"template": "templates/aliyun_cdn.json", "mapping": "CDN"},
|
||||
"负载均衡SLB": {"template": "templates/aliyun_slb.json", "mapping": "loadbalancer"},
|
||||
"专有网络VPC": {"template": "templates/aliyun_vpc.json", "mapping": "vpc"},
|
||||
"交换机Switch": {"template": "templates/aliyun_switch.json", "mapping": "vswitch"},
|
||||
},
|
||||
"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": {"template": "templates/aliyun_ebs.json", "mapping": "evs"},
|
||||
"对象存储OSS": {"template": "templates/aliyun_oss.json", "mapping": "objectStorage"},
|
||||
},
|
||||
"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": {"template": "templates/aliyun_rds_mysql.json", "mapping": "mysql"},
|
||||
"云数据库RDS PostgreSQL": {"template": "templates/aliyun_rds_postgre.json", "mapping": "postgresql"},
|
||||
"云数据库 Redis": {"template": "templates/aliyun_redis.json", "mapping": "redis"},
|
||||
},
|
||||
"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": {"template": "templates/tencent_cvm.json", "mapping": "ecs"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 CVM": "tencent.cvm",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "CDN与边缘",
|
||||
"items": ["内容分发CDN"],
|
||||
"map": {
|
||||
"内容分发CDN": {"template": "templates/tencent_cdn.json", "mapping": "CDN"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"内容分发CDN": "tencent.cdn",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "网络",
|
||||
"items": ["负载均衡CLB", "私有网络VPC", "子网"],
|
||||
"map": {
|
||||
"负载均衡CLB": {"template": "templates/tencent_clb.json", "mapping": "loadbalancer"},
|
||||
"私有网络VPC": {"template": "templates/tencent_vpc.json", "mapping": "vpc"},
|
||||
"子网": {"template": "templates/tencent_subnet.json", "mapping": "vswitch"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"负载均衡CLB": "tencent.clb",
|
||||
"私有网络VPC": "tencent.vpc",
|
||||
"子网": "tencent.subnet",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "存储",
|
||||
"items": ["云硬盘CBS", "对象存储COS"],
|
||||
"map": {
|
||||
"云硬盘CBS": {"template": "templates/tencent_cbs.json", "mapping": "evs"},
|
||||
"对象存储COS": {"template": "templates/tencent_cos.json", "mapping": "objectStorage"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云硬盘CBS": "tencent.cbs",
|
||||
"对象存储COS": "tencent.cos",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "数据库",
|
||||
"items": ["云数据库 MySQL", "云数据库 PostgreSQL", "云数据库 Redis"],
|
||||
"map": {
|
||||
"云数据库 MySQL": {"template": "templates/tencent_rdb.json", "mapping": "mysql"},
|
||||
"云数据库 PostgreSQL": {"template": "templates/tencent_postgres.json", "mapping": "postgresql"},
|
||||
"云数据库 Redis": {"template": "templates/tencent_redis.json", "mapping": "redis"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云数据库 MySQL": "tencent.rdb",
|
||||
"云数据库 PostgreSQL": "tencent.rds_postgres",
|
||||
"云数据库 Redis": "tencent.redis",
|
||||
},
|
||||
},
|
||||
],
|
||||
"huaweicloud": [
|
||||
{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": {"template": "templates/huaweicloud_ecs.json", "mapping": "ecs"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 ECS": "huawei.ecs",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "CDN与智能边缘",
|
||||
"items": ["内容分发网络CDN"],
|
||||
"map": {
|
||||
"内容分发网络CDN": {"template": "templates/huawei_cdn.json", "mapping": "CDN"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"内容分发网络CDN": "huawei.cdn",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "网络",
|
||||
"items": ["弹性负载均衡ELB", "虚拟私有云VPC", "子网"],
|
||||
"map": {
|
||||
"弹性负载均衡ELB": {"template": "templates/huawei_elb.json", "mapping": "loadbalancer"},
|
||||
"虚拟私有云VPC": {"template": "templates/huawei_vpc.json", "mapping": "vpc"},
|
||||
"子网": {"template": "templates/huawei_subnet.json", "mapping": "vswitch"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"弹性负载均衡ELB": "huawei.elb",
|
||||
"虚拟私有云VPC": "huawei.vpc",
|
||||
"子网": "huawei.subnet",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "存储",
|
||||
"items": ["云硬盘EVS", "对象存储OBS"],
|
||||
"map": {
|
||||
"云硬盘EVS": {"template": "templates/huawei_evs.json", "mapping": "evs"},
|
||||
"对象存储OBS": {"template": "templates/huawei_obs.json", "mapping": "objectStorage"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云硬盘EVS": "huawei.evs",
|
||||
"对象存储OBS": "huawei.obs",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "数据库",
|
||||
"items": ["云数据库RDS MySQL", "云数据库RDS PostgreSQL"],
|
||||
"map": {
|
||||
"云数据库RDS MySQL": {"template": "templates/huawei_rds_mysql.json", "mapping": "mysql"},
|
||||
"云数据库RDS PostgreSQL": {"template": "templates/huawei_rds_postgre.json", "mapping": "postgresql"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云数据库RDS MySQL": "huawei.rds_mysql",
|
||||
"云数据库RDS PostgreSQL": "huawei.rds_postgre",
|
||||
},
|
||||
},
|
||||
{
|
||||
"category": "应用中间件",
|
||||
"items": ["分布式缓存Redis"],
|
||||
"map": {
|
||||
"分布式缓存Redis": {"template": "templates/huawei_dcs.json", "mapping": "redis"},
|
||||
},
|
||||
"collect_key_map": {
|
||||
"分布式缓存Redis": "huawei.dcs",
|
||||
},
|
||||
},
|
||||
],
|
||||
"aws": [
|
||||
{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 EC2"],
|
||||
"map": {
|
||||
"云服务器 EC2": {"template": "templates/aws_ec2.json", "mapping": "ecs"},
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from flask import current_app
|
||||
|
||||
@@ -546,3 +548,20 @@ class CMDBCounterCache(object):
|
||||
@classmethod
|
||||
def get_sub_counter(cls):
|
||||
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
||||
|
||||
|
||||
class AutoDiscoveryMappingCache(object):
|
||||
PREFIX = 'CMDB::AutoDiscovery::Mapping::{}'
|
||||
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
res = cache.get(cls.PREFIX.format(name)) or {}
|
||||
if not res:
|
||||
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "auto_discovery/mapping/{}.yaml".format(name))
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
mapping = yaml.safe_load(f)
|
||||
res = mapping.get('mapping') or {}
|
||||
res and cache.set(cls.PREFIX.format(name), res, timeout=0)
|
||||
|
||||
return res
|
@@ -4,12 +4,12 @@
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import threading
|
||||
|
||||
import redis_lock
|
||||
import threading
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.orm import aliased
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
@@ -28,6 +28,7 @@ from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import RelationSourceEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
@@ -217,7 +218,7 @@ class CIManager(object):
|
||||
|
||||
@classmethod
|
||||
def get_ad_statistics(cls):
|
||||
return CMDBCounterCache.get_adc_counter()
|
||||
return CMDBCounterCache.get_adc_counter() or {}
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
@@ -381,6 +382,7 @@ class CIManager(object):
|
||||
for _, attr in attrs:
|
||||
if attr.is_computed:
|
||||
computed_attrs.append(attr.to_dict())
|
||||
ci_dict[attr.name] = None
|
||||
elif attr.is_password:
|
||||
if attr.name in ci_dict:
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
|
||||
@@ -388,10 +390,7 @@ class CIManager(object):
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id][0])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
|
||||
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
@@ -418,6 +417,9 @@ class CIManager(object):
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
|
||||
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
|
||||
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||
try:
|
||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||
@@ -463,6 +465,7 @@ class CIManager(object):
|
||||
for _, attr in attrs:
|
||||
if attr.is_computed:
|
||||
computed_attrs.append(attr.to_dict())
|
||||
ci_dict[attr.name] = None
|
||||
elif attr.is_password:
|
||||
if attr.name in ci_dict:
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
|
||||
@@ -470,10 +473,7 @@ class CIManager(object):
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id][0])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
|
||||
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
@@ -486,6 +486,10 @@ class CIManager(object):
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||
ci_attr2type_attr=ci_attr2type_attr)
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
|
||||
if limit_attrs:
|
||||
for k in copy.deepcopy(ci_dict):
|
||||
if k not in limit_attrs:
|
||||
@@ -1133,7 +1137,14 @@ class CIRelationManager(object):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
|
||||
def add(cls, first_ci_id, second_ci_id,
|
||||
more=None,
|
||||
relation_type_id=None,
|
||||
ancestor_ids=None,
|
||||
valid=True,
|
||||
apply_async=True,
|
||||
source=None,
|
||||
uid=None):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
@@ -1145,9 +1156,10 @@ class CIRelationManager(object):
|
||||
first=True)
|
||||
if existed is not None:
|
||||
if existed.relation_type_id != relation_type_id and relation_type_id is not None:
|
||||
existed.update(relation_type_id=relation_type_id)
|
||||
source = existed.source or source
|
||||
existed.update(relation_type_id=relation_type_id, source=source)
|
||||
|
||||
CIRelationHistoryManager().add(existed, OperateType.UPDATE)
|
||||
CIRelationHistoryManager().add(existed, OperateType.UPDATE, uid=uid)
|
||||
else:
|
||||
if relation_type_id is None:
|
||||
type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id,
|
||||
@@ -1177,11 +1189,13 @@ class CIRelationManager(object):
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id,
|
||||
ancestor_ids=ancestor_ids)
|
||||
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
ancestor_ids=ancestor_ids,
|
||||
source=source)
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD, uid=uid)
|
||||
if apply_async:
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_cache(first_ci_id, second_ci_id, ancestor_ids)
|
||||
|
||||
if more is not None:
|
||||
existed.upadte(more=more)
|
||||
@@ -1189,7 +1203,7 @@ class CIRelationManager(object):
|
||||
return existed.id
|
||||
|
||||
@staticmethod
|
||||
def delete(cr_id):
|
||||
def delete(cr_id, apply_async=True):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||
@@ -1205,8 +1219,12 @@ class CIRelationManager(object):
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||
if apply_async:
|
||||
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_delete(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids)
|
||||
delete_id_filter(cr.second_ci_id)
|
||||
|
||||
return cr_id
|
||||
|
||||
@@ -1221,23 +1239,23 @@ class CIRelationManager(object):
|
||||
if cr is not None:
|
||||
cls.delete(cr.id)
|
||||
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
|
||||
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def delete_3(cls, first_ci_id, second_ci_id):
|
||||
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
if cr is not None:
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
cls.delete(cr.id)
|
||||
cls.delete(cr.id, apply_async=apply_async)
|
||||
|
||||
return cr
|
||||
|
||||
@@ -1276,6 +1294,27 @@ class CIRelationManager(object):
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
|
||||
|
||||
@classmethod
|
||||
def delete_relations_by_source(cls, source,
|
||||
first_ci_id=None, second_ci_type_id=None,
|
||||
second_ci_id=None, first_ci_type_id=None,
|
||||
added=None):
|
||||
existed = []
|
||||
if first_ci_id is not None and second_ci_type_id is not None:
|
||||
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
|
||||
source=source, first_ci_id=first_ci_id, only_query=True).join(
|
||||
CI, CIRelation.second_ci_id == CI.id).filter(CI.type_id == second_ci_type_id)]
|
||||
|
||||
if second_ci_id is not None and first_ci_type_id is not None:
|
||||
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
|
||||
source=source, second_ci_id=second_ci_id, only_query=True).join(
|
||||
CI, CIRelation.first_ci_id == CI.id).filter(CI.type_id == first_ci_type_id)]
|
||||
|
||||
deleted = set(existed) - set(added or [])
|
||||
|
||||
for first, second in deleted:
|
||||
cls.delete_3(first, second, apply_async=False)
|
||||
|
||||
@classmethod
|
||||
def build_by_attribute(cls, ci_dict):
|
||||
type_id = ci_dict['_type']
|
||||
@@ -1296,8 +1335,15 @@ class CIRelationManager(object):
|
||||
relations = _relations
|
||||
else:
|
||||
relations &= _relations
|
||||
|
||||
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
|
||||
first_ci_id=ci_dict['_id'],
|
||||
second_ci_type_id=item.child_id,
|
||||
added=relations)
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
cls.add(parent_ci_id, child_ci_id,
|
||||
valid=False,
|
||||
source=RelationSourceEnum.ATTRIBUTE_VALUES)
|
||||
|
||||
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.child_attr_ids.isnot(None))
|
||||
@@ -1316,11 +1362,18 @@ class CIRelationManager(object):
|
||||
relations = _relations
|
||||
else:
|
||||
relations &= _relations
|
||||
|
||||
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
|
||||
second_ci_id=ci_dict['_id'],
|
||||
first_ci_type_id=item.parent_id,
|
||||
added=relations)
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
cls.add(parent_ci_id, child_ci_id,
|
||||
valid=False,
|
||||
source=RelationSourceEnum.ATTRIBUTE_VALUES)
|
||||
|
||||
@classmethod
|
||||
def rebuild_all_by_attribute(cls, ci_type_relation):
|
||||
def rebuild_all_by_attribute(cls, ci_type_relation, uid):
|
||||
relations = None
|
||||
for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
|
||||
ci_type_relation['child_attr_ids'] or []):
|
||||
@@ -1352,11 +1405,29 @@ class CIRelationManager(object):
|
||||
else:
|
||||
relations &= _relations
|
||||
|
||||
t1 = aliased(CI)
|
||||
t2 = aliased(CI)
|
||||
query = db.session.query(CIRelation).join(t1, t1.id == CIRelation.first_ci_id).join(
|
||||
t2, t2.id == CIRelation.second_ci_id).filter(t1.type_id == ci_type_relation['parent_id']).filter(
|
||||
t2.type_id == ci_type_relation['child_id'])
|
||||
for i in query:
|
||||
db.session.delete(i)
|
||||
ci_relation_delete(i.first_ci_id, i.second_ci_id, i.ancestor_ids)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
db.session.rollback()
|
||||
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
try:
|
||||
cls.add(parent_ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
cls.add(parent_ci_id, child_ci_id,
|
||||
valid=False,
|
||||
apply_async=False,
|
||||
source=RelationSourceEnum.ATTRIBUTE_VALUES,
|
||||
uid=uid)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
|
@@ -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):
|
||||
@@ -977,7 +993,7 @@ class CITypeRelationManager(object):
|
||||
if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or
|
||||
(child_attr_ids and child_attr_ids != old_child_attr_ids)):
|
||||
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(),))
|
||||
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(), current_user.uid))
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
@@ -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
|
||||
|
@@ -41,23 +41,23 @@ class OperateType(BaseEnum):
|
||||
|
||||
|
||||
class CITypeOperateType(BaseEnum):
|
||||
ADD = "0" # 新增模型
|
||||
UPDATE = "1" # 修改模型
|
||||
DELETE = "2" # 删除模型
|
||||
ADD_ATTRIBUTE = "3" # 新增属性
|
||||
UPDATE_ATTRIBUTE = "4" # 修改属性
|
||||
DELETE_ATTRIBUTE = "5" # 删除属性
|
||||
ADD_TRIGGER = "6" # 新增触发器
|
||||
UPDATE_TRIGGER = "7" # 修改触发器
|
||||
DELETE_TRIGGER = "8" # 删除触发器
|
||||
ADD_UNIQUE_CONSTRAINT = "9" # 新增联合唯一
|
||||
UPDATE_UNIQUE_CONSTRAINT = "10" # 修改联合唯一
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 新增数据合规
|
||||
UPDATE_RECONCILIATION = "15" # 修改数据合规
|
||||
DELETE_RECONCILIATION = "16" # 删除数据合规
|
||||
ADD = "0" # add CIType
|
||||
UPDATE = "1" # update CIType
|
||||
DELETE = "2" # delete CIType
|
||||
ADD_ATTRIBUTE = "3"
|
||||
UPDATE_ATTRIBUTE = "4"
|
||||
DELETE_ATTRIBUTE = "5"
|
||||
ADD_TRIGGER = "6"
|
||||
UPDATE_TRIGGER = "7"
|
||||
DELETE_TRIGGER = "8"
|
||||
ADD_UNIQUE_CONSTRAINT = "9"
|
||||
UPDATE_UNIQUE_CONSTRAINT = "10"
|
||||
DELETE_UNIQUE_CONSTRAINT = "11"
|
||||
ADD_RELATION = "12"
|
||||
DELETE_RELATION = "13"
|
||||
ADD_RECONCILIATION = "14"
|
||||
UPDATE_RECONCILIATION = "15"
|
||||
DELETE_RECONCILIATION = "16"
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -93,7 +93,8 @@ class RoleEnum(BaseEnum):
|
||||
class AutoDiscoveryType(BaseEnum):
|
||||
AGENT = "agent"
|
||||
SNMP = "snmp"
|
||||
HTTP = "http"
|
||||
HTTP = "http" # cloud
|
||||
COMPONENTS = "components"
|
||||
|
||||
|
||||
class AttributeDefaultValueEnum(BaseEnum):
|
||||
@@ -107,6 +108,10 @@ class ExecuteStatusEnum(BaseEnum):
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
class RelationSourceEnum(BaseEnum):
|
||||
ATTRIBUTE_VALUES = "0"
|
||||
AUTO_DISCOVERY = "1"
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
|
@@ -232,8 +232,8 @@ class AttributeHistoryManger(object):
|
||||
|
||||
class CIRelationHistoryManager(object):
|
||||
@staticmethod
|
||||
def add(rel_obj, operate_type=OperateType.ADD):
|
||||
record = OperationRecord.create(uid=current_user.uid)
|
||||
def add(rel_obj, operate_type=OperateType.ADD, uid=None):
|
||||
record = OperationRecord.create(uid=uid or current_user.uid)
|
||||
|
||||
CIRelationHistory.create(relation_id=rel_obj.id,
|
||||
record_id=record.id,
|
||||
|
@@ -28,13 +28,31 @@ def string2int(x):
|
||||
return v
|
||||
|
||||
|
||||
def str2datetime(x):
|
||||
def str2date(x):
|
||||
|
||||
try:
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
|
||||
try:
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S").date()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def str2datetime(x):
|
||||
|
||||
x = x.replace('T', ' ')
|
||||
x = x.replace('Z', '')
|
||||
|
||||
try:
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
|
||||
class ValueTypeMap(object):
|
||||
@@ -44,7 +62,7 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.TEXT: lambda x: x,
|
||||
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
|
||||
ValueTypeEnum.DATETIME: str2datetime,
|
||||
ValueTypeEnum.DATE: str2datetime,
|
||||
ValueTypeEnum.DATE: str2date,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
}
|
||||
|
||||
|
@@ -94,7 +94,7 @@ class AttributeValueManager(object):
|
||||
except ValueDeserializeError as e:
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||
except ValueError:
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
|
||||
|
||||
@staticmethod
|
||||
def _check_is_choice(attr, value_type, value):
|
||||
@@ -123,9 +123,9 @@ class AttributeValueManager(object):
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
@staticmethod
|
||||
def check_re(expr, value):
|
||||
def check_re(expr, alias, value):
|
||||
if not re.compile(expr).match(str(value)):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
@@ -141,7 +141,7 @@ class AttributeValueManager(object):
|
||||
v = None
|
||||
|
||||
if attr.re_check and value:
|
||||
self.check_re(attr.re_check, value)
|
||||
self.check_re(attr.re_check, attr.alias, value)
|
||||
|
||||
return v
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy.dialects.mysql import DOUBLE
|
||||
|
||||
from api.extensions import db
|
||||
@@ -11,6 +10,7 @@ from api.lib.cmdb.const import CIStatusEnum
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import RelationSourceEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.database import Model
|
||||
from api.lib.database import Model2
|
||||
@@ -260,6 +260,7 @@ class CIRelation(Model):
|
||||
second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False)
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
||||
source = db.Column(db.Enum(*RelationSourceEnum.all()), name="source")
|
||||
|
||||
ancestor_ids = db.Column(db.String(128), index=True)
|
||||
|
||||
@@ -578,6 +579,7 @@ class AutoDiscoveryCIType(Model):
|
||||
|
||||
extra_option = db.Column(db.JSON)
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
enabled = db.Column(db.Boolean, default=True)
|
||||
|
||||
|
||||
class AutoDiscoveryCITypeRelation(Model):
|
||||
|
@@ -58,10 +58,10 @@ def ci_cache(ci_id, operate_type, record_id):
|
||||
|
||||
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def rebuild_relation_for_attribute_changed(ci_type_relation):
|
||||
def rebuild_relation_for_attribute_changed(ci_type_relation, uid):
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
|
||||
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
|
||||
CIRelationManager.rebuild_all_by_attribute(ci_type_relation, uid)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||
|
@@ -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,16 +111,25 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
|
||||
class AutoDiscoveryRuleHTTPView(APIView):
|
||||
url_prefix = ("/adr/http/<string:name>/categories",
|
||||
"/adr/http/<string:name>/attributes",
|
||||
"/adr/snmp/<string:name>/attributes")
|
||||
"/adr/http/<string:name>/mapping",
|
||||
"/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))
|
||||
|
||||
if "mapping" in request.url:
|
||||
resource = request.values.get('resource')
|
||||
return self.jsonify(AutoDiscoveryHTTPManager.get_mapping(name, resource))
|
||||
|
||||
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
|
||||
|
||||
|
||||
@@ -139,6 +149,11 @@ class AutoDiscoveryCITypeView(APIView):
|
||||
i['extra_option'].pop('secret', None)
|
||||
else:
|
||||
i['extra_option']['secret'] = AESCrypto.decrypt(i['extra_option']['secret'])
|
||||
if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('password'):
|
||||
if not (current_user.username == "cmdb_agent" or current_user.uid == i['uid']):
|
||||
i['extra_option'].pop('password', None)
|
||||
else:
|
||||
i['extra_option']['password'] = AESCrypto.decrypt(i['extra_option']['password'])
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@@ -250,7 +265,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')
|
||||
|
@@ -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')
|
||||
|
@@ -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)
|
||||
|
@@ -55,3 +55,4 @@ pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
||||
jsonpath==0.82.2
|
||||
|
@@ -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",
|
||||
|
@@ -54,6 +54,78 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-markdown</div>
|
||||
<div class="code-name">&#xe96a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-bar_horizontal</div>
|
||||
<div class="code-name">&#xe860;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-gauge</div>
|
||||
<div class="code-name">&#xe965;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-heatmap</div>
|
||||
<div class="code-name">&#xe966;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-treemap</div>
|
||||
<div class="code-name">&#xe967;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-radar</div>
|
||||
<div class="code-name">&#xe968;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-data</div>
|
||||
<div class="code-name">&#xe969;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-import</div>
|
||||
<div class="code-name">&#xe963;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-batch_operation</div>
|
||||
<div class="code-name">&#xe964;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-enterprise_edition</div>
|
||||
<div class="code-name">&#xe962;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ops-KVM</div>
|
||||
<div class="code-name">&#xe961;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-vcenter</div>
|
||||
<div class="code-name">&#xe960;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-manual_warehousing</div>
|
||||
@@ -534,12 +606,6 @@
|
||||
<div class="code-name">&#xe862;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-import</div>
|
||||
<div class="code-name">&#xe860;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></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>
|
||||
|
@@ -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
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -57,7 +57,9 @@ export default {
|
||||
computed: {
|
||||
...mapState(['user', 'locale']),
|
||||
hasBackendPermission() {
|
||||
return this.user?.detailPermissions?.backend?.length
|
||||
const isAdmin = this?.user?.roles?.permissions?.includes('acl_admin')
|
||||
|
||||
return isAdmin || this.user?.detailPermissions?.backend?.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@@ -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,
|
||||
})
|
||||
}
|
||||
|
@@ -45,13 +45,23 @@ 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',
|
||||
})
|
||||
}
|
||||
|
||||
export function getHttpAttrMapping(name, resource) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/http/${name}/mapping`,
|
||||
method: 'GET',
|
||||
params: {
|
||||
resource
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeDiscovery(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/adt/ci_types/${type_id}`,
|
||||
|
@@ -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,9 @@ export default {
|
||||
font-weight: 400;
|
||||
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: @layout-sidebar-selected-color;
|
||||
@@ -214,9 +267,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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: '200px' }" v-model="currentCate">
|
||||
<a-select-option v-for="cate in categoriesSelect" :key="cate" :value="cate">{{ cate }}</a-select-option>
|
||||
</a-select>
|
||||
<AttrMapTable
|
||||
@@ -28,7 +29,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery'
|
||||
import _ from 'lodash'
|
||||
import { getHttpCategories, getHttpAttributes, getSnmpAttributes, getHttpAttrMapping } from '../../api/discovery'
|
||||
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
|
||||
import ADPreviewTable from './adPreviewTable.vue'
|
||||
import HttpADCategory from './httpADCategory.vue'
|
||||
@@ -68,6 +70,10 @@ export default {
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentAdt: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -76,6 +82,7 @@ export default {
|
||||
categoriesSelect: [],
|
||||
currentCate: '',
|
||||
tableData: [],
|
||||
httpAttrMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -89,21 +96,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) => {
|
||||
if (this.isEdit) {
|
||||
this.formatTableData(res)
|
||||
} else {
|
||||
this.tableData = res
|
||||
}
|
||||
})
|
||||
this.getHttpAttr(newVal)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -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) => {
|
||||
@@ -133,7 +140,7 @@ export default {
|
||||
})
|
||||
this.categoriesSelect = categoriesSelect
|
||||
if (this.isEdit && categoriesSelect?.length) {
|
||||
this.currentCate = categoriesSelect[0]
|
||||
this.currentCate = this?.currentAdt?.extra_option?.category || categoriesSelect[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -151,28 +158,54 @@ export default {
|
||||
},
|
||||
formatTableData(list) {
|
||||
const _findADT = this.adCITypeList.find((item) => Number(item.adr_id) === Number(this.currentTab))
|
||||
this.tableData = (list || []).map((item) => {
|
||||
if (_findADT.attributes) {
|
||||
return {
|
||||
...item,
|
||||
attr: _findADT.attributes[`${item.name}`],
|
||||
}
|
||||
} else {
|
||||
this.tableData = (list || []).map((val) => {
|
||||
const item = _.cloneDeep(val)
|
||||
|
||||
if (_findADT?.attributes?.[item.name]) {
|
||||
item.attr = _findADT.attributes[item.name]
|
||||
}
|
||||
|
||||
const attrMapName = this.httpAttrMap?.[item?.name]
|
||||
|
||||
if (
|
||||
this.isEdit &&
|
||||
!item.attr &&
|
||||
attrMapName &&
|
||||
this.ciTypeAttributes.some((ele) => ele.name === attrMapName)
|
||||
) {
|
||||
item.attr = attrMapName
|
||||
}
|
||||
|
||||
if (!item.attr) {
|
||||
const _find = this.ciTypeAttributes.find((ele) => ele.name === item.name)
|
||||
if (_find) {
|
||||
return {
|
||||
...item,
|
||||
attr: _find.name,
|
||||
}
|
||||
item.attr = _find.name
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
},
|
||||
getTableData() {
|
||||
const $table = this.$refs.attrMapTable
|
||||
const { fullData } = $table.getTableData()
|
||||
return fullData || []
|
||||
},
|
||||
|
||||
async getHttpAttr(val) {
|
||||
await this.getHttpAttrMapping(this.ruleName, val)
|
||||
getHttpAttributes(this.ruleName, { resource: val }).then((res) => {
|
||||
if (this.isEdit) {
|
||||
this.formatTableData(res)
|
||||
} else {
|
||||
this.tableData = res
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async getHttpAttrMapping(name, resource) {
|
||||
const res = await getHttpAttrMapping(name, resource)
|
||||
this.httpAttrMap = res || {}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -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,17 @@ 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',
|
||||
resourceSearchTip1: 'Please use conditional filtering for CI filtering and copy and paste the filter expression into the fill-in box in the previous step.',
|
||||
resourceSearchTip2: 'Note 1: Please use the green button to the right of the expression to copy it',
|
||||
resourceSearchTip3: 'Note 2: If you do not need to filter, please click the grey button to copy and paste directly to configure for all nodes',
|
||||
enable: 'Enable',
|
||||
enableTip: 'Confirm switching on?',
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
@@ -479,6 +500,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 +671,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
|
||||
|
@@ -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,17 @@ const cmdb_zh = {
|
||||
relationADTip2: '当自动发现属性与关联模型属性一致时,两实例模型则自动关联',
|
||||
relationADTip3: '如果自动发现的属性值是列表,则会和关联模型建立多个关系',
|
||||
deleteRelationAdTip: '不可再删除',
|
||||
cronTips: '格式同crontab, 例如:0 15 * * 1-5',
|
||||
privateCloud: 'vSphere API配置',
|
||||
host: '地址',
|
||||
account: '账号',
|
||||
insecure: '是否证书验证',
|
||||
vcenterName: '虚拟平台名',
|
||||
resourceSearchTip1: '请使用条件过滤进行CI筛选,并将过滤表达式复制粘贴到上一步填写框中。',
|
||||
resourceSearchTip2: '注1:请使用表达式右侧的绿色按钮进行复制',
|
||||
resourceSearchTip3: '注2:如不需要筛选,请直接点击灰色按钮进行复制粘贴,即可配置为所有节点',
|
||||
enable: '开启',
|
||||
enableTip: '确定切换开启状态吗',
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
@@ -478,7 +499,8 @@ const cmdb_zh = {
|
||||
agent: '服务器',
|
||||
snmp: '网络设备',
|
||||
http: '公有云',
|
||||
plugin: '插件',
|
||||
component: '数据库 & 中间件',
|
||||
privateCloud: '私有云',
|
||||
rule: '自动发现规则',
|
||||
timeout: '超时错误',
|
||||
mode: '模式',
|
||||
@@ -648,7 +670,8 @@ if __name__ == "__main__":
|
||||
confirmDeleteView: '您确定要删除该视图吗?',
|
||||
noInstancePerm: '您没有该实例的查看权限',
|
||||
noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性',
|
||||
topoViewSearchPlaceholder: '请输入节点名字'
|
||||
topoViewSearchPlaceholder: '请输入节点名字',
|
||||
moreBtn: '展示更多({count})'
|
||||
},
|
||||
}
|
||||
export default cmdb_zh
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,22 @@
|
||||
<span>{{ $t('edit') }}</span>
|
||||
</a-space>
|
||||
</a>
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.attributeMap') }}</div>
|
||||
<div class="attr-ad-header attr-ad-header_between">
|
||||
{{ $t('cmdb.ciType.attributeMap') }}
|
||||
<div class="attr-ad-open">
|
||||
<span class="attr-ad-open-label">{{ $t('cmdb.ciType.enable') }}</span>
|
||||
<a-switch v-model="form.enabled" v-if="isClient" />
|
||||
<a-popconfirm
|
||||
v-else
|
||||
:title="$t('cmdb.ciType.enableTip')"
|
||||
:ok-text="$t('confirm')"
|
||||
:cancel-text="$t('cancel')"
|
||||
@confirm="changeEnabled"
|
||||
>
|
||||
<a-switch :checked="form.enabled" />
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attr-ad-attributemap-main">
|
||||
<AttrMapTable
|
||||
v-if="adrType === 'agent'"
|
||||
@@ -34,6 +49,7 @@
|
||||
:adCITypeList="adCITypeList"
|
||||
:currentTab="adr_id"
|
||||
:uniqueKey="uniqueKey"
|
||||
:currentAdt="currentAdt"
|
||||
:style="{ marginBottom: '20px' }"
|
||||
/>
|
||||
</div>
|
||||
@@ -51,7 +67,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 +85,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 +105,7 @@
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 6 }"
|
||||
:label="$t('cmdb.ciType.adInterval')"
|
||||
:required="true"
|
||||
>
|
||||
<el-popover v-model="cronVisible" trigger="click">
|
||||
<template slot>
|
||||
@@ -98,28 +122,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
|
||||
@@ -134,10 +189,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
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'
|
||||
@@ -194,11 +251,19 @@ export default {
|
||||
agent_id: '',
|
||||
auto_accept: false,
|
||||
query_expr: '',
|
||||
enabled: true,
|
||||
},
|
||||
form2: {
|
||||
key: '',
|
||||
secret: '',
|
||||
},
|
||||
privateCloudForm: {
|
||||
host: '',
|
||||
account: '',
|
||||
password: '',
|
||||
// insecure: false,
|
||||
vcenterName: '',
|
||||
},
|
||||
interval: 'cron', // interval cron
|
||||
cron: '',
|
||||
intervalValue: 3,
|
||||
@@ -214,6 +279,10 @@ export default {
|
||||
form3: this.$form.createForm(this, { name: 'snmp_form' }),
|
||||
cronVisible: false,
|
||||
uniqueKey: '',
|
||||
isPrivateCloud: false,
|
||||
privateCloudName: '',
|
||||
PRIVATE_CLOUD_NAME,
|
||||
isClient: false, // 是否前端新增临时数据
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -225,7 +294,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 +310,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 +323,7 @@ export default {
|
||||
]
|
||||
},
|
||||
labelCol() {
|
||||
const span = this.$i18n.locale === 'en' ? 4 : 2
|
||||
const span = this.$i18n.locale === 'en' ? 5 : 3
|
||||
return {
|
||||
span
|
||||
}
|
||||
@@ -262,13 +335,41 @@ export default {
|
||||
const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id))
|
||||
const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id))
|
||||
this.uniqueKey = _find?.unique_key ?? ''
|
||||
this.isClient = _findADT?.isClient ?? false
|
||||
|
||||
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 +409,14 @@ 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 || '',
|
||||
enabled: _findADT?.enabled ?? true,
|
||||
}
|
||||
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 +431,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 +509,27 @@ 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 (params.extra_option) {
|
||||
params.extra_option = _.omit(params.extra_option, 'insecure')
|
||||
}
|
||||
|
||||
if (currentAdt?.isClient) {
|
||||
postCITypeDiscovery(this.CITypeId, params).then((res) => {
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.$emit('handleSave', res.id)
|
||||
@@ -415,6 +541,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()
|
||||
},
|
||||
@@ -427,6 +587,17 @@ export default {
|
||||
hideCron() {
|
||||
this.cronVisible = false
|
||||
},
|
||||
changeEnabled() {
|
||||
if (!this.isClient) {
|
||||
putCITypeDiscovery(this.currentAdt.id, {
|
||||
enabled: !this.form.enabled
|
||||
}).then((res) => {
|
||||
this.form.enabled = !this.form.enabled
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.$emit('handleSave', res.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -437,6 +608,26 @@ export default {
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
|
||||
.attr-ad-header_between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.attr-ad-open {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0px 20px;
|
||||
|
||||
&-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.attr-ad-attributemap-main {
|
||||
margin-left: 17px;
|
||||
}
|
||||
@@ -458,6 +649,11 @@ export default {
|
||||
margin-left: 17px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.radio-master-tip {
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
.attr-ad-snmp-form {
|
||||
.ant-form-item {
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
@@ -645,7 +645,7 @@ export default {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
min-height: 20px;
|
||||
> i {
|
||||
width: 182px;
|
||||
|
@@ -14,17 +14,18 @@
|
||||
<TriggerTable ref="triggerTable" :CITypeId="CITypeId"></TriggerTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
|
||||
<template v-if="activeKey === '6'">
|
||||
<div class="grant-config-wrap" :style="{ maxHeight: `${windowHeight - 150}px` }" v-if="activeKey === '6'">
|
||||
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
||||
<div class="citype-detail-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
</template>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import AttributesTable from './attributesTable'
|
||||
import RelationTable from './relationTable'
|
||||
import TriggerTable from './triggerTable.vue'
|
||||
@@ -57,6 +58,11 @@ export default {
|
||||
},
|
||||
beforeCreate() {},
|
||||
mounted() {},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
changeTab(activeKey) {
|
||||
this.activeKey = activeKey
|
||||
@@ -81,4 +87,7 @@ export default {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.grant-config-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
File diff suppressed because it is too large
Load Diff
239
cmdb-ui/src/modules/cmdb/views/ci_types/modelExport.vue
Normal file
239
cmdb-ui/src/modules/cmdb/views/ci_types/modelExport.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<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, itemSelectAll } }"
|
||||
>
|
||||
<a-tree
|
||||
v-if="direction === 'left'"
|
||||
blockNode
|
||||
checkable
|
||||
:checkedKeys="[...selectedKeys, ...targetKeys]"
|
||||
:treeData="treeData"
|
||||
:checkStrictly="false"
|
||||
@check="
|
||||
(_, props) => {
|
||||
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect, itemSelectAll);
|
||||
}
|
||||
"
|
||||
@select="
|
||||
(_, props) => {
|
||||
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect, itemSelectAll);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</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, group) => {
|
||||
const types = _.cloneDeep(group?.ci_types || [])
|
||||
types.forEach((item) => {
|
||||
item.key = `${group.id}-${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 = `${item.id}-${child.id}`
|
||||
const disabled = this.targetKeys.includes(key)
|
||||
childrenKeys.push(key)
|
||||
|
||||
return {
|
||||
key,
|
||||
title: child?.alias || child?.name || this.$t('other'),
|
||||
disabled,
|
||||
children: []
|
||||
}
|
||||
})
|
||||
return {
|
||||
key: String(item?.id),
|
||||
title: item?.name || this.$t('other'),
|
||||
children,
|
||||
childrenKeys,
|
||||
disabled: children.every((item) => item.disabled),
|
||||
}
|
||||
})
|
||||
newTreeData = newTreeData.filter((item) => item.children.length > 0)
|
||||
return newTreeData
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(targetKeys, direction, moveKeys) {
|
||||
const childKeys = []
|
||||
const newTargetKeys = [...targetKeys]
|
||||
|
||||
if (direction === 'right') {
|
||||
// 如果是选中父节点,添加时去除父节点,添加其子节点
|
||||
this.treeData.forEach((item) => {
|
||||
const parentIndex = newTargetKeys.findIndex((key) => item.key === key)
|
||||
if (parentIndex !== -1) {
|
||||
newTargetKeys.splice(parentIndex, 1)
|
||||
childKeys.push(...item.childrenKeys)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const uniqTargetKeys = _.uniq([...newTargetKeys, ...childKeys])
|
||||
this.targetKeys = uniqTargetKeys
|
||||
},
|
||||
onChecked(_, e, checkedKeys, itemSelect, itemSelectAll) {
|
||||
const { eventKey } = e.node
|
||||
const selected = checkedKeys.indexOf(eventKey) === -1
|
||||
const childrenKeys = this.treeData.find((item) => item.key === eventKey)?.childrenKeys || []
|
||||
// 如果当前点击是子节点,处理其联动父节点
|
||||
this.treeData.forEach((item) => {
|
||||
if (item.childrenKeys.includes(eventKey)) {
|
||||
if (selected && item.childrenKeys.every((childKey) => [eventKey, ...checkedKeys].includes(childKey))) {
|
||||
itemSelect(item.key, true)
|
||||
} else if (!selected) {
|
||||
itemSelect(item.key, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
itemSelectAll([eventKey, ...childrenKeys], 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.getTypeIds(this.targetKeys)
|
||||
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
|
||||
})
|
||||
},
|
||||
getTypeIds(targetKeys) {
|
||||
let typeIds = targetKeys?.map((key) => {
|
||||
return this?.transferDataSource?.find((node) => node?.key === key)?.id || ''
|
||||
})
|
||||
typeIds = typeIds.filter((id) => id)
|
||||
return typeIds?.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-export-transfer {
|
||||
/deep/ .ant-transfer-list {
|
||||
.ant-transfer-list-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
.ant-transfer-list-header {
|
||||
.ant-transfer-list-header-selected {
|
||||
span:first-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-transfer-list-header-title {
|
||||
color: @primary-color;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -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'
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
@@ -18,6 +18,11 @@
|
||||
>{{ $t('download') }}</a-button
|
||||
>
|
||||
</div>
|
||||
<div v-if="fromCronJob" class="resource-search-tip">
|
||||
<div class="resource-search-tip-item">{{ $t('cmdb.ciType.resourceSearchTip1') }}</div>
|
||||
<div class="resource-search-tip-item">{{ $t('cmdb.ciType.resourceSearchTip2') }}</div>
|
||||
<div class="resource-search-tip-item">{{ $t('cmdb.ciType.resourceSearchTip3') }}</div>
|
||||
</div>
|
||||
<SearchForm
|
||||
ref="search"
|
||||
:type="type"
|
||||
@@ -44,7 +49,7 @@
|
||||
size="small"
|
||||
row-id="_id"
|
||||
:loading="loading"
|
||||
:height="fromCronJob ? windowHeight - 180 : windowHeight - 240"
|
||||
:height="fromCronJob ? windowHeight - 280 : windowHeight - 240"
|
||||
show-header-overflow
|
||||
highlight-hover-row
|
||||
:data="instanceList"
|
||||
@@ -98,16 +103,20 @@
|
||||
>
|
||||
<template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<template v-else-if="col.is_link && row[col.field]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
@@ -568,5 +577,14 @@ export default {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
|
||||
&-tip {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&-item {
|
||||
font-size: 12px;
|
||||
color: @text-color_4
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,7 +1225,7 @@ export default {
|
||||
topoViewJsonData.nodes.keys().forEach((key) => {
|
||||
const node = topoViewJsonData?.nodes?.get(key)
|
||||
if (node?.data?.btnType !== 'more') {
|
||||
node.opacity = node?.text?.indexOf(v) !== -1 ? 1 : 0.1
|
||||
node.opacity = `${node?.text ?? ''}`?.indexOf?.(v) !== -1 ? 1 : 0.1
|
||||
}
|
||||
})
|
||||
const instance = this.$refs.showTopoView.getInstance()
|
||||
|
@@ -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}`"
|
||||
@@ -238,16 +242,20 @@
|
||||
#default="{row}"
|
||||
>
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<template v-else-if="col.is_link && row[col.field]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
@@ -1229,6 +1237,9 @@ export default {
|
||||
}
|
||||
)
|
||||
},
|
||||
getRowSeq(row) {
|
||||
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1337,6 +1348,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>
|
||||
|
@@ -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.8
|
||||
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.8
|
||||
container_name: cmdb-ui
|
||||
depends_on:
|
||||
cmdb-api:
|
||||
|
@@ -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/
|
||||
|
Reference in New Issue
Block a user