Compare commits

..

1 Commits

Author SHA1 Message Date
pycook
07eb902583 feat(api): my preference support grouping 2024-05-18 22:54:07 +08:00
139 changed files with 7384 additions and 21189 deletions

6
.env
View File

@@ -1,6 +0,0 @@
MYSQL_ROOT_PASSWORD='123456'
MYSQL_HOST='mysql'
MYSQL_PORT=3306
MYSQL_USER='cmdb'
MYSQL_DATABASE='cmdb'
MYSQL_PASSWORD='123456'

0
.github/config.yml vendored Normal file
View File

View File

@@ -1,79 +0,0 @@
name: docker-images-build-and-release
on:
push:
branches:
- master
tags: ["v*"]
pull_request:
branches:
- master
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
steps:
- name: Checkout Repo
uses: actions/checkout@v4
release-api-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-API Docker image
uses: docker/build-push-action@v6
with:
file: docker/Dockerfile-API
context: .
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:
file: docker/Dockerfile-UI
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-ui:${{ env.TAG }}

View File

@@ -1,4 +1,6 @@
include ./Makefile.variable
MYSQL_ROOT_PASSWORD ?= root
MYSQL_PORT ?= 3306
REDIS_PORT ?= 6379
default: help
help: ## display this help
@@ -48,25 +50,3 @@ clean: ## remove unwanted files like .pyc's
lint: ## check style with flake8
flake8 --exclude=env .
.PHONY: lint
api-docker-build:
export DOCKER_CLI_EXPERIMENTAL=enabled ;\
! ( docker buildx ls | grep multi-platform-builder ) && docker buildx create --use --platform=$(BUILD_ARCH) --name multi-platform-builder ;\
docker buildx build \
--builder multi-platform-builder \
--platform=$(BUILD_ARCH) \
--tag $(REGISTRY)/cmdb-api:$(CMDB_DOCKER_VERSION) \
--tag $(REGISTRY)/cmdb-api:latest \
-f docker/Dockerfile-API \
.
ui-docker-build:
export DOCKER_CLI_EXPERIMENTAL=enabled ;\
! ( docker buildx ls | grep multi-platform-builder ) && docker buildx create --use --platform=$(BUILD_ARCH) --name multi-platform-builder ;\
docker buildx build \
--builder multi-platform-builder \
--platform=$(BUILD_ARCH) \
--tag $(REGISTRY)/cmdb-ui:$(CMDB_DOCKER_VERSION) \
--tag $(REGISTRY)/cmdb-ui:latest \
-f docker/Dockerfile-UI \
.

View File

@@ -1,21 +0,0 @@
SHELL := /bin/bash -o pipefail
MYSQL_ROOT_PASSWORD ?= root
MYSQL_PORT ?= 3306
REDIS_PORT ?= 6379
LATEST_TAG_DIFF:=$(shell git describe --tags --abbrev=8)
LATEST_COMMIT:=$(VERSION)-dev-$(shell git rev-parse --short=8 HEAD)
BUILD_ARCH ?= linux/amd64,linux/arm64
# Set your version by env or using latest tags from git
CMDB_VERSION?=$(LATEST_TAG_DIFF)
ifeq ($(CMDB_VERSION),)
#fall back to last commit
CMDB_VERSION=$(LATEST_COMMIT)
endif
COMMIT_VERSION:=$(LATEST_COMMIT)
CMDB_DOCKER_VERSION:=${CMDB_VERSION}
CMDB_CHART_VERSION:=$(shell echo ${CMDB_VERSION} | sed 's/^v//g' )
REGISTRY ?= local

View File

@@ -73,8 +73,7 @@
## 安装
### Docker 一键快速构建
[//]: # (> 方法一)
> 方法一
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
- 第二步: 拷贝项目
```shell
@@ -84,20 +83,13 @@ 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)
@@ -113,7 +105,4 @@ docker compose up -d
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
<p align="center">
<img src="docs/images/wechat.png" alt="公众号: 维易科技OneOps" />
</p>
![公众号: 维易科技OneOps](docs/images/wechat.png)

View File

@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
environs = "==4.2.0"
marshmallow = "==2.20.2"
# async tasks
celery = "==5.3.1"
celery = ">=5.3.1"
celery_once = "==3.0.1"
more-itertools = "==5.0.0"
kombu = ">=5.3.1"
@@ -67,7 +67,6 @@ colorama = ">=0.4.6"
pycryptodomex = ">=3.19.0"
lz4 = ">=4.3.2"
python-magic = "==0.4.27"
jsonpath = "==0.82.2"
[dev-packages]
# Testing

View File

@@ -128,7 +128,7 @@ def cmdb_init_acl():
perms = [PermEnum.READ]
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
@@ -190,7 +190,6 @@ def cmdb_counter():
login_user(UserCache.get('worker'))
i = 0
today = datetime.date.today()
while True:
try:
db.session.remove()
@@ -201,10 +200,6 @@ def cmdb_counter():
CMDBCounterCache.flush_adc_counter()
i = 0
if datetime.date.today() != today:
CMDBCounterCache.clear_ad_exec_history()
today = datetime.date.today()
CMDBCounterCache.flush_sub_counter()
i += 1
@@ -331,7 +326,7 @@ def cmdb_inner_secrets_init(address):
"""
init inner secrets for password feature
"""
res, ok = KeyManage(backend=InnerKVManger()).init()
res, ok = KeyManage(backend=InnerKVManger).init()
if not ok:
if res.get("status") == "failed":
KeyManage.print_response(res)
@@ -498,64 +493,3 @@ def cmdb_agent_init():
click.echo("Key : {}".format(click.style(user.key, bg='red')))
click.echo("Secret: {}".format(click.style(user.secret, bg='red')))
@click.command()
@click.option(
'-v',
'--version',
help='input cmdb version, e.g. 2.4.6',
required=True,
)
@with_appcontext
def cmdb_patch(version):
"""
CMDB upgrade patch
"""
version = version[1:] if version.lower().startswith("v") else version
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 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()
if version >= "2.4.7":
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.models.cmdb import AutoDiscoveryRule
for i in DEFAULT_INNER:
existed = AutoDiscoveryRule.get_by(name=i['name'], first=True, to_dict=False)
if existed is not None:
if "en" in i['option'] and 'en' not in (existed.option or {}):
option = copy.deepcopy(existed.option)
option['en'] = i['option']['en']
existed.update(option=option, commit=False)
db.session.commit()
except Exception as e:
print("cmdb patch failed: {}".format(e))

View File

@@ -229,7 +229,7 @@ class AttributeManager(object):
is_choice = True if choice_value or kwargs.get('choice_web_hook') or kwargs.get('choice_other') else False
name = kwargs.pop("name")
if name in BUILTIN_KEYWORDS or kwargs.get('alias') in BUILTIN_KEYWORDS:
if name in BUILTIN_KEYWORDS:
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
while kwargs.get('choice_other'):

View File

@@ -1,51 +1,34 @@
# -*- coding:utf-8 -*-
import copy
import datetime
import json
import jsonpath
import os
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
from api.lib.cmdb.cache import CITypeAttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.const import AutoDiscoveryType
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search
from api.lib.mixin import DBMixin
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import AESCrypto
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryRule
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_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
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 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
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.mixin import DBMixin
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import AESCrypto
from api.models.cmdb import AutoDiscoveryAccount
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import AutoDiscoveryCounter
from api.models.cmdb import AutoDiscoveryExecHistory
from api.models.cmdb import AutoDiscoveryRule
from api.models.cmdb import AutoDiscoveryRuleSyncHistory
from api.tasks.cmdb import build_relations_for_ad_accept
from api.tasks.cmdb import write_ad_rule_sync_history
PWD = os.path.abspath(os.path.dirname(__file__))
app_cli = CMDBApp()
def parse_plugin_script(script):
@@ -113,30 +96,14 @@ class AutoDiscoveryRuleCRUD(DBMixin):
else:
self.cls.create(**rule)
def _can_add(self, valid=True, **kwargs):
def _can_add(self, **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') and valid:
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
acl = ACLManager(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))
kwargs['owner'] = current_user.uid
return kwargs
def _can_update(self, valid=True, **kwargs):
def _can_update(self, **kwargs):
existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
@@ -148,22 +115,6 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if other and other.id != existed.id:
return abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
if existed.is_plugin and valid:
acl = ACLManager(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))
return existed
def update(self, _id, **kwargs):
@@ -171,44 +122,21 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
for item in AutoDiscoveryCIType.get_by(adr_id=_id, to_dict=False):
item.update(updated_at=datetime.datetime.now())
return super(AutoDiscoveryRuleCRUD, self).update(_id, filter_none=False, **kwargs)
def _can_delete(self, **kwargs):
if AutoDiscoveryCIType.get_by(adr_id=kwargs['_id'], first=True):
return abort(400, ErrFormat.adr_referenced)
existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
if existed.is_plugin:
acl = ACLManager(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))
return existed
return self._can_update(**kwargs)
class AutoDiscoveryCITypeCRUD(DBMixin):
cls = AutoDiscoveryCIType
@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.type_id in type_ids]
def get_all(cls):
return cls.cls.get_by(to_dict=False)
@classmethod
def get_by_id(cls, _id):
@@ -219,59 +147,25 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
return cls.cls.get_by(type_id=type_id, to_dict=False)
@classmethod
def get_ad_attributes(cls, type_id):
result = []
adts = cls.get_by_type_id(type_id)
for adt in adts:
adr = AutoDiscoveryRuleCRUD.get_by_id(adt.adr_id)
if not adr:
continue
if adr.type == AutoDiscoveryType.HTTP:
for i in DEFAULT_INNER:
if adr.name == i['name']:
attrs = AutoDiscoveryHTTPManager.get_attributes(
i['en'], (adt.extra_option or {}).get('category')) or []
result.extend([i.get('name') for i in attrs])
break
elif adr.type == AutoDiscoveryType.SNMP:
attributes = AutoDiscoverySNMPManager.get_attributes()
result.extend([i.get('name') for i in (attributes or [])])
else:
result.extend([i.get('name') for i in (adr.attributes or [])])
return sorted(list(set(result)))
@classmethod
def get(cls, ci_id, oneagent_id, oneagent_name, last_update_at=None):
"""
OneAgent sync rules
:param ci_id:
:param oneagent_id:
:param oneagent_name:
:param last_update_at:
:return:
"""
def get(cls, ci_id, oneagent_id, last_update_at=None):
result = []
rules = cls.cls.get_by(to_dict=True)
for rule in rules:
if not rule['enabled']:
if rule.get('relation'):
continue
if isinstance(rule.get("extra_option"), dict):
decrypt_account(rule['extra_option'], rule['uid'])
if rule['extra_option'].get('_reference'):
rule['extra_option'].pop('password', None)
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']):
rule['extra_option'].pop('secret', None)
rule['extra_option'].update(
AutoDiscoveryAccountCRUD().get_config_by_id(rule['extra_option']['_reference']))
else:
rule['extra_option']['secret'] = AESCrypto.decrypt(rule['extra_option']['secret'])
if oneagent_id and rule['agent_id'] == oneagent_id:
result.append(rule)
elif rule['query_expr']:
query = rule['query_expr'].lstrip('q').lstrip('=')
s = ci_search(query, fl=['_id'], count=1000000)
s = search(query, fl=['_id'], count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
@@ -282,32 +176,25 @@ 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
if adr.type in (AutoDiscoveryType.SNMP, AutoDiscoveryType.HTTP):
continue
if not rule['updated_at']:
continue
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 "", ad_rules_updated_at])
i['adr']['created_at'] or "", i['adr']['updated_at'] or ""])
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:
@@ -326,7 +213,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
agent_id = agent_id.strip()
q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}"
s = ci_search(q.format(current_user.username, agent_id.strip()))
s = search(q.format(current_user.username, agent_id.strip()))
try:
response, _, _, _, _, _ = s.search()
if response:
@@ -335,7 +222,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
current_app.logger.warning(e)
return abort(400, str(e))
s = ci_search(q.format(current_user.nickname, agent_id.strip()))
s = search(q.format(current_user.nickname, agent_id.strip()))
try:
response, _, _, _, _, _ = s.search()
if response:
@@ -349,7 +236,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if query_expr.startswith('q='):
query_expr = query_expr[2:]
s = ci_search(query_expr, count=1000000)
s = search(query_expr, count=1000000)
try:
response, _, _, _, _, _ = s.search()
for i in response:
@@ -367,39 +254,19 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
def _can_add(**kwargs):
if kwargs.get('adr_id'):
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
if adr.type == AutoDiscoveryType.HTTP:
kwargs.setdefault('extra_option', dict())
en_name = None
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]:
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 adr.type == AutoDiscoveryType.COMPONENTS and kwargs.get('extra_option'):
for i in DEFAULT_INNER:
if i['name'] == adr.name:
kwargs['extra_option']['collect_key'] = i['option'].get('collect_key')
break
# if not adr.is_plugin:
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
# if other:
# ci_type = CITypeCache.get(other.type_id)
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
encrypt_account(kwargs.get('extra_option'))
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))
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
kwargs['uid'] = current_user.uid
@@ -409,44 +276,11 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id'])))
adr = AutoDiscoveryRule.get_by_id(existed.adr_id) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(existed.adr_id)))
if adr.type == AutoDiscoveryType.HTTP:
kwargs.setdefault('extra_option', dict())
en_name = None
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]:
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 adr.type == AutoDiscoveryType.COMPONENTS and kwargs.get('extra_option'):
for i in DEFAULT_INNER:
if i['name'] == adr.name:
kwargs['extra_option']['collect_key'] = i['option'].get('collect_key')
break
if 'attributes' in kwargs:
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
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))
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
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
@@ -455,22 +289,10 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
encrypt_account(kwargs.get('extra_option'))
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
inst = self._can_update(_id=_id, **kwargs)
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
return super(AutoDiscoveryCITypeCRUD, self).update(_id, filter_none=False, **kwargs)
def _can_delete(self, **kwargs):
if AutoDiscoveryCICRUD.get_by_adt_id(kwargs['_id']):
@@ -481,61 +303,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
return existed
def delete(self, _id):
inst = self._can_delete(_id=_id)
inst.soft_delete()
for item in AutoDiscoveryRuleSyncHistory.get_by(adt_id=inst.id, to_dict=False):
item.delete(commit=False)
db.session.commit()
attributes = self.get_ad_attributes(inst.type_id)
for item in AutoDiscoveryCITypeRelationCRUD.get_by_type_id(inst.type_id):
if item.ad_key not in attributes:
item.soft_delete()
return inst
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)
def upsert(self, ad_type_id, relations):
existed = self.cls.get_by(ad_type_id=ad_type_id, to_dict=False)
existed = {(i.ad_key, i.peer_type_id, i.peer_attr_id): i for i in existed}
new = []
for r in relations:
k = (r.get('ad_key'), r.get('peer_type_id'), r.get('peer_attr_id'))
if len(list(filter(lambda x: x, k))) == 3 and k not in existed:
self.cls.create(ad_type_id=ad_type_id, **r)
new.append(k)
for deleted in set(existed.keys()) - set(new):
existed[deleted].soft_delete()
return self.get_by_type_id(ad_type_id, to_dict=True)
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
class AutoDiscoveryCICRUD(DBMixin):
cls = AutoDiscoveryCI
@@ -570,6 +337,7 @@ 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
@@ -623,24 +391,16 @@ class AutoDiscoveryCICRUD(DBMixin):
changed = False
if existed is not None:
if existed.instance != kwargs['instance']:
instance = copy.deepcopy(existed.instance) or {}
instance.update(kwargs['instance'])
kwargs['instance'] = instance
existed.update(filter_none=False, **kwargs)
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
stdout="update resource: {}".format(kwargs.get('unique_value')))
changed = True
else:
existed = self.cls.create(**kwargs)
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
stdout="add resource: {}".format(kwargs.get('unique_value')))
changed = True
if adt.auto_accept and changed:
try:
self.accept(existed)
except Exception as e:
current_app.logger.error(e)
return abort(400, str(e))
elif changed:
existed.update(is_accept=False, accept_time=None, accept_by=None, filter_none=False)
@@ -660,13 +420,6 @@ class AutoDiscoveryCICRUD(DBMixin):
inst.delete()
adt = AutoDiscoveryCIType.get_by_id(inst.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
AutoDiscoveryExecHistoryCRUD().add(type_id=inst.type_id,
stdout="delete resource: {}".format(inst.unique_value))
self._after_delete(inst)
return inst
@@ -682,13 +435,6 @@ class AutoDiscoveryCICRUD(DBMixin):
not is_app_admin("cmdb") and validate_permission(ci_type.name, ResourceTypeEnum.CI, PermEnum.DELETE, "cmdb")
existed.delete()
adt = AutoDiscoveryCIType.get_by_id(existed.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
AutoDiscoveryExecHistoryCRUD().add(type_id=type_id,
stdout="delete resource: {}".format(unique_value))
# TODO: delete ci
@classmethod
@@ -699,24 +445,36 @@ class AutoDiscoveryCICRUD(DBMixin):
adt = AutoDiscoveryCITypeCRUD.get_by_id(adc.adt_id) or abort(404, ErrFormat.adt_not_found)
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_id = CIManager.add(adc.type_id, is_auto_discovery=True, **ci_dict)
ad_key2attr = adt.attributes or {}
if ad_key2attr:
ci_dict = {ad_key2attr[k]: None if not v and isinstance(v, (list, dict)) else v
for k, v in adc.instance.items() if k in ad_key2attr}
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))
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
for r_adt in relation_adts:
if not r_adt.relation or ci_id is None:
continue
for ad_key in r_adt.relation:
if not adc.instance.get(ad_key):
continue
cmdb_key = r_adt.relation[ad_key]
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'),
adc.instance.get(ad_key))
s = search(query)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
current_app.logger.warning(e)
return abort(400, str(e))
build_relations_for_ad_accept.apply_async(args=(adc.to_dict(), ci_id, ad_key2attr), queue=CMDB_QUEUE)
relation_ci_id = response and response[0]['_id']
if relation_ci_id:
try:
CIRelationManager.add(ci_id, relation_ci_id)
except:
try:
CIRelationManager.add(relation_ci_id, ci_id)
except:
pass
adc.update(is_accept=True,
accept_by=nickname or current_user.nickname,
@@ -727,75 +485,17 @@ class AutoDiscoveryCICRUD(DBMixin):
class AutoDiscoveryHTTPManager(object):
@staticmethod
def get_categories(name):
categories = (CLOUD_MAP.get(name) or {}) or []
for item in copy.deepcopy(categories):
item.pop('map', None)
item.pop('collect_key_map', None)
return (ClOUD_MAP.get(name) or {}).get('categories') or []
return categories
def get_resources(self, name):
en_name = None
for i in DEFAULT_INNER:
if i['name'] == name:
en_name = i['en']
break
if en_name:
categories = self.get_categories(en_name)
return [j for i in categories for j in i['items']]
@staticmethod
def get_attributes(name, category):
tpt = ((ClOUD_MAP.get(name) or {}).get('map') or {}).get(category)
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_attributes(provider, resource):
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):
@@ -806,191 +506,3 @@ class AutoDiscoverySNMPManager(object):
return json.loads(f.read())
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
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
def upsert(self, **kwargs):
existed = self.cls.get_by(adt_id=kwargs.get('adt_id'),
oneagent_id=kwargs.get('oneagent_id'),
oneagent_name=kwargs.get('oneagent_name'),
first=True,
to_dict=False)
if existed is not None:
existed.update(**kwargs)
else:
self.cls.create(**kwargs)
class AutoDiscoveryExecHistoryCRUD(DBMixin):
cls = AutoDiscoveryExecHistory
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
class AutoDiscoveryCounterCRUD(DBMixin):
cls = AutoDiscoveryCounter
def get(self, type_id):
res = self.cls.get_by(type_id=type_id, first=True, to_dict=True)
if res is None:
return dict(rule_count=0, exec_target_count=0, instance_count=0, accept_count=0,
this_month_count=0, this_week_count=0, last_month_count=0, last_week_count=0)
return res
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
def encrypt_account(config):
if isinstance(config, dict):
if config.get('secret'):
config['secret'] = AESCrypto.encrypt(config['secret'])
if config.get('password'):
config['password'] = AESCrypto.encrypt(config['password'])
def decrypt_account(config, uid):
if isinstance(config, dict):
if config.get('password'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == uid):
config.pop('password', None)
else:
try:
config['password'] = AESCrypto.decrypt(config['password'])
except Exception as e:
current_app.logger.error('decrypt account failed: {}'.format(e))
if config.get('secret'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == uid):
config.pop('secret', None)
else:
try:
config['secret'] = AESCrypto.decrypt(config['secret'])
except Exception as e:
current_app.logger.error('decrypt account failed: {}'.format(e))
class AutoDiscoveryAccountCRUD(DBMixin):
cls = AutoDiscoveryAccount
def get(self, adr_id):
res = self.cls.get_by(adr_id=adr_id, to_dict=True)
for i in res:
decrypt_account(i.get('config'), i['uid'])
return res
def get_config_by_id(self, _id):
res = self.cls.get_by_id(_id)
if not res:
return {}
config = res.to_dict().get('config') or {}
decrypt_account(config, res.uid)
return config
def _can_add(self, **kwargs):
encrypt_account(kwargs.get('config'))
kwargs['uid'] = current_user.uid
return kwargs
def upsert(self, adr_id, accounts):
existed_all = self.cls.get_by(adr_id=adr_id, to_dict=False)
account_names = {i['name'] for i in accounts}
name_changed = dict()
for account in accounts:
existed = None
if account.get('id'):
existed = self.cls.get_by_id(account.get('id'))
if existed is None:
continue
account.pop('id')
name_changed[existed.name] = account.get('name')
else:
account = self._can_add(**account)
if existed is not None:
if current_user.uid == existed.uid:
config = copy.deepcopy(existed.config) or {}
config.update(account.get('config') or {})
account['config'] = config
existed.update(**account)
else:
self.cls.create(adr_id=adr_id, **account)
for item in existed_all:
if name_changed.get(item.name, item.name) not in account_names:
if current_user.uid == item.uid:
item.soft_delete()
def _can_update(self, **kwargs):
existed = self.cls.get_by_id(kwargs['_id']) or abort(404, ErrFormat.not_found)
if isinstance(kwargs.get('config'), dict) and kwargs['config'].get('secret'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
if isinstance(kwargs.get('config'), dict) and kwargs['config'].get('password'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
return existed
def update(self, _id, **kwargs):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
encrypt_account(kwargs.get('config'))
inst = self._can_update(_id=_id, **kwargs)
obj = inst.update(_id=_id, filter_none=False, **kwargs)
return obj
def _can_delete(self, **kwargs):
pass

View File

@@ -2,38 +2,15 @@
from api.lib.cmdb.const import AutoDiscoveryType
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'}, "en": "aliyun"}),
dict(name="腾讯云", en="tencentcloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
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'}, "en": "huaweicloud"}),
dict(name="AWS", en="aws", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
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", "collect_key": "nginx"}),
dict(name="Apache", en="apache", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-apache'}, "en": "apache", "collect_key": "apache"}),
dict(name="Tomcat", en="tomcat", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-tomcat'}, "en": "tomcat", "collect_key": "tomcat"}),
dict(name="MySQL", en="mysql", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-mySQL'}, "en": "mysql", "collect_key": "mysql"}),
dict(name="MSSQL", en="mssql", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-SQLServer'}, "en": "mssql", "collect_key": "sqlserver"}),
dict(name="Oracle", en="oracle", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-oracle'}, "en": "oracle", "collect_key": "oracle"}),
dict(name="Redis", en="redis", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-redis'}, "en": "redis", "collect_key": "redis"}),
DEFAULT_HTTP = [
dict(name="阿里云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aliyun'}}),
dict(name="腾讯云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-tengxunyun'}}),
dict(name="华为云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-huaweiyun'}}),
dict(name="AWS", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aws'}}),
dict(name="交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-jiaohuanji'}}),
@@ -45,307 +22,32 @@ DEFAULT_INNER = [
option={'icon': {'name': 'caise-dayinji'}}),
]
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",
},
},
{
"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",
},
},
{
"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",
},
},
{
"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",
},
},
],
"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_pool.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",
},
},
],
ClOUD_MAP = {
"aliyun": {
"categories": ["云服务器 ECS"],
"map": {
"云服务器 ECS": "templates/aliyun_ecs.json",
}
},
"tencentcloud": {
"categories": ["云服务器 CVM"],
"map": {
"云服务器 CVM": "templates/tencent_cvm.json",
}
},
"huaweicloud": {
"categories": ["云服务器 ECS"],
"map": {
"云服务器 ECS": "templates/huaweicloud_ecs.json",
}
},
"aws": {
"categories": ["云服务器 EC2"],
"map": {
"云服务器 EC2": "templates/aws_ec2.json",
}
},
}

View File

@@ -2,21 +2,12 @@
from __future__ import unicode_literals
import datetime
import os
import yaml
from flask import current_app
from api.extensions import cache
from api.extensions import db
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.models.cmdb import Attribute, AutoDiscoveryExecHistory
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import AutoDiscoveryCounter
from api.models.cmdb import AutoDiscoveryRuleSyncHistory
from api.models.cmdb import Attribute
from api.models.cmdb import CI
from api.models.cmdb import CIType
from api.models.cmdb import CITypeAttribute
@@ -457,67 +448,7 @@ class CMDBCounterCache(object):
cache.set(cls.KEY2, result, timeout=0)
res = db.session.query(AutoDiscoveryCI.created_at,
AutoDiscoveryCI.updated_at,
AutoDiscoveryCI.adt_id,
AutoDiscoveryCI.type_id,
AutoDiscoveryCI.is_accept).filter(AutoDiscoveryCI.deleted.is_(False))
today = datetime.datetime.today()
this_month = datetime.datetime(today.year, today.month, 1)
last_month = this_month - datetime.timedelta(days=1)
last_month = datetime.datetime(last_month.year, last_month.month, 1)
this_week = today - datetime.timedelta(days=datetime.date.weekday(today))
this_week = datetime.datetime(this_week.year, this_week.month, this_week.day)
last_week = this_week - datetime.timedelta(days=7)
last_week = datetime.datetime(last_week.year, last_week.month, last_week.day)
result = dict()
for i in res:
if i.type_id not in result:
result[i.type_id] = dict(instance_count=0, accept_count=0,
this_month_count=0, this_week_count=0, last_month_count=0, last_week_count=0)
adts = AutoDiscoveryCIType.get_by(type_id=i.type_id, to_dict=False)
result[i.type_id]['rule_count'] = len(adts) + AutoDiscoveryCITypeRelation.get_by(
ad_type_id=i.type_id, only_query=True).count()
result[i.type_id]['exec_target_count'] = len(
set([i.oneagent_id for adt in adts for i in db.session.query(
AutoDiscoveryRuleSyncHistory.oneagent_id).filter(
AutoDiscoveryRuleSyncHistory.adt_id == adt.id)]))
result[i.type_id]['instance_count'] += 1
if i.is_accept:
result[i.type_id]['accept_count'] += 1
if last_month <= i.created_at < this_month:
result[i.type_id]['last_month_count'] += 1
elif i.created_at >= this_month:
result[i.type_id]['this_month_count'] += 1
if last_week <= i.created_at < this_week:
result[i.type_id]['last_week_count'] += 1
elif i.created_at >= this_week:
result[i.type_id]['this_week_count'] += 1
for type_id in result:
existed = AutoDiscoveryCounter.get_by(type_id=type_id, first=True, to_dict=False)
if existed is None:
AutoDiscoveryCounter.create(type_id=type_id, **result[type_id])
else:
existed.update(**result[type_id])
for i in AutoDiscoveryCounter.get_by(to_dict=False):
if i.type_id not in result:
i.delete()
@classmethod
def clear_ad_exec_history(cls):
ci_types = CIType.get_by(to_dict=False)
for ci_type in ci_types:
for i in AutoDiscoveryExecHistory.get_by(type_id=ci_type.id, only_query=True).order_by(
AutoDiscoveryExecHistory.id.desc()).offset(50000):
i.delete(commit=False)
db.session.commit()
return result
@classmethod
def get_adc_counter(cls):
@@ -548,20 +479,3 @@ 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

View File

@@ -4,12 +4,12 @@
import copy
import datetime
import json
import redis_lock
import threading
import redis_lock
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,7 +28,6 @@ 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
@@ -218,13 +217,13 @@ class CIManager(object):
@classmethod
def get_ad_statistics(cls):
return CMDBCounterCache.get_adc_counter() or {}
return CMDBCounterCache.get_adc_counter()
@staticmethod
def ci_is_exist(unique_key, unique_value, type_id):
"""
:param unique_key: is an attribute
:param unique_key: is a attribute
:param unique_value:
:param type_id:
:return:
@@ -319,8 +318,8 @@ class CIManager(object):
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
unique_value = None
# primary key is not auto inc id
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID):
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
not ci_dict.get(unique_key.name)): # primary key is not auto inc id
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
@@ -382,15 +381,17 @@ 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)
password_dict[attr.id] = ci_dict.pop(attr.name)
elif attr.alias in ci_dict:
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
password_dict[attr.id] = ci_dict.pop(attr.alias)
if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
@@ -417,14 +418,10 @@ 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)
record_id, has_dynamic = value_manager.create_or_update_attr_value(
ci, ci_dict, key2attr, ticket_id=ticket_id)
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
except BadRequest as e:
if existed is None:
cls.delete(ci.id)
@@ -434,7 +431,7 @@ class CIManager(object):
for attr_id in password_dict:
record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id)
if record_id or has_dynamic: # has changed
if record_id: # has change
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
if ref_ci_dict: # add relations
@@ -443,6 +440,7 @@ class CIManager(object):
return ci.id
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
current_app.logger.info((ci_id, ci_dict, __sync))
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id)
@@ -465,15 +463,17 @@ 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)
password_dict[attr.id] = ci_dict.pop(attr.name)
elif attr.alias in ci_dict:
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
password_dict[attr.id] = ci_dict.pop(attr.alias)
if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
value_manager.check_re(attr.re_check, password_dict[attr.id])
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
@@ -486,10 +486,6 @@ 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:
@@ -499,8 +495,7 @@ class CIManager(object):
ci_dict.pop(k)
try:
record_id, has_dynamic = value_manager.create_or_update_attr_value(
ci, ci_dict, key2attr, ticket_id=ticket_id)
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
except BadRequest as e:
raise e
@@ -508,25 +503,25 @@ class CIManager(object):
for attr_id in password_dict:
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
if record_id or has_dynamic: # has changed
if record_id: # has change
if not __sync:
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
else:
ci_cache(ci_id, OperateType.UPDATE, record_id)
ci_cache((ci_id, OperateType.UPDATE, record_id))
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
if ref_ci_dict:
if not __sync:
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
else:
ci_relation_add(ref_ci_dict, ci.id)
ci_relation_add((ref_ci_dict, ci.id))
@staticmethod
def update_unique_value(ci_id, unique_name, unique_value):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
key2attr = {unique_name: AttributeCache.get(unique_name)}
record_id, _ = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr)
record_id = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr)
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
@@ -741,7 +736,7 @@ class CIManager(object):
fields=None, value_tables=None, unique_required=False, excludes=None):
"""
:param ci_ids: list of CI instance ID, e.g. ['1', '2']
:param ci_ids: list of CI instance ID, eg. ['1', '2']
:param ret_key: name, id or alias
:param fields:
:param value_tables:
@@ -766,7 +761,6 @@ class CIManager(object):
@classmethod
def save_password(cls, ci_id, attr_id, value, record_id, type_id):
value, is_dynamic = value
changed = None
encrypt_value = None
value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD]
@@ -783,18 +777,14 @@ class CIManager(object):
if existed is None:
if value:
value_table.create(ci_id=ci_id, attr_id=attr_id, value=encrypt_value)
if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)]
changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)]
elif existed.value != encrypt_value:
if value:
existed.update(ci_id=ci_id, attr_id=attr_id, value=encrypt_value)
if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW,
PASSWORD_DEFAULT_SHOW, type_id)]
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW, PASSWORD_DEFAULT_SHOW, type_id)]
else:
existed.delete()
if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)]
changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)]
if current_app.config.get('SECRETS_ENGINE') == 'vault':
vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN'))
@@ -1100,18 +1090,6 @@ class CIRelationManager(object):
return ci_ids, level2ids
@classmethod
def get_parent_ids(cls, ci_ids):
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
CI, CI.id == CIRelation.first_ci_id).filter(
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
result = {}
for ci in cis:
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
return result
@staticmethod
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
db.session.commit()
@@ -1137,14 +1115,7 @@ 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,
apply_async=True,
source=None,
uid=None):
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True):
first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id)
@@ -1156,10 +1127,9 @@ 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:
source = existed.source or source
existed.update(relation_type_id=relation_type_id, source=source)
existed.update(relation_type_id=relation_type_id)
CIRelationHistoryManager().add(existed, OperateType.UPDATE, uid=uid)
CIRelationHistoryManager().add(existed, OperateType.UPDATE)
else:
if relation_type_id is None:
type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id,
@@ -1189,13 +1159,11 @@ 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,
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)
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)
if more is not None:
existed.upadte(more=more)
@@ -1203,7 +1171,7 @@ class CIRelationManager(object):
return existed.id
@staticmethod
def delete(cr_id, apply_async=True):
def delete(cr_id):
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
@@ -1219,12 +1187,8 @@ class CIRelationManager(object):
his_manager = CIRelationHistoryManager()
his_manager.add(cr, operate_type=OperateType.DELETE)
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)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
return cr_id
@@ -1239,23 +1203,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, apply_async=True):
def delete_3(cls, first_ci_id, second_ci_id):
cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
to_dict=False,
first=True)
if cr is not None:
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
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, apply_async=apply_async)
cls.delete(cr.id)
return cr
@@ -1294,140 +1258,56 @@ 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']
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
CITypeRelation.parent_attr_ids.isnot(None))
CITypeRelation.parent_attr_id.isnot(None))
for item in child_items:
relations = None
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
_relations.add((ci_dict['_id'], child.ci_id))
if relations is None:
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 []):
cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
CITypeRelation.child_attr_ids.isnot(None))
CITypeRelation.child_attr_id.isnot(None))
for item in parent_items:
relations = None
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(child_attr.name)
value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
_relations.add((parent.ci_id, ci_dict['_id']))
if relations is None:
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 []):
cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(child_attr.name)
value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
@classmethod
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 []):
def rebuild_all_by_attribute(cls, ci_type_relation):
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
if not parent_attr or not child_attr:
return
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
if not parent_attr or not child_attr:
continue
parent_value_table = TableMap(attr=parent_attr).table
child_value_table = TableMap(attr=child_attr).table
parent_value_table = TableMap(attr=parent_attr).table
child_value_table = TableMap(attr=child_attr).table
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
child_value2ci_ids = {}
for child in child_values:
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
child_value2ci_ids = {}
for child in child_values:
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
for parent in parent_values:
for child_ci_id in child_value2ci_ids.get(parent.value, []):
_relations.add((parent.ci_id, child_ci_id))
if relations is None:
relations = _relations
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,
apply_async=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES,
uid=uid)
except Exception as e:
current_app.logger.error(e)
for parent in parent_values:
for child_ci_id in child_value2ci_ids.get(parent.value, []):
try:
cls.add(parent.ci_id, child_ci_id, valid=False)
except:
pass
class CITriggerManager(object):

View File

@@ -1,6 +1,7 @@
# -*- coding:utf-8 -*-
import copy
import toposort
from flask import abort
from flask import current_app
@@ -34,7 +35,6 @@ from api.lib.perm.acl.acl import validate_permission
from api.models.cmdb import Attribute
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import CI
from api.models.cmdb import CIFilterPerms
from api.models.cmdb import CIType
@@ -54,7 +54,6 @@ from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import RelationType
from api.models.cmdb import TopologyView
class CITypeManager(object):
@@ -83,7 +82,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, type_ids=None):
def get_ci_types(type_name=None, like=True):
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)])
@@ -92,9 +91,6 @@ 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'):
@@ -231,9 +227,6 @@ class CITypeManager(object):
if CI.get_by(type_id=type_id, first=True, to_dict=False) is not None:
return abort(400, ErrFormat.ci_exists_and_cannot_delete_type)
if CITypeInheritance.get_by(parent_id=type_id, first=True):
return abort(400, ErrFormat.ci_type_inheritance_cannot_delete)
relation_views = PreferenceRelationView.get_by(to_dict=False)
for rv in relation_views:
for item in (rv.cr_ids or []):
@@ -257,28 +250,19 @@ class CITypeManager(object):
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(ad_type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(peer_type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
item.soft_delete(commit=False)
item.delete(commit=False)
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.soft_delete(commit=False)
item.delete(commit=False)
try:
from api.models.cmdb import CITypeReconciliation
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
item.delete(commit=False)
except Exception:
pass
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
item.delete(commit=False)
db.session.commit()
ci_type.soft_delete()
@@ -294,12 +278,6 @@ 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)]
@@ -395,7 +373,7 @@ class CITypeGroupManager(object):
cls = CITypeGroup
@staticmethod
def get(need_other=None, config_required=True, type_ids=None, ci_types=None):
def get(need_other=None, config_required=True):
resources = None
if current_app.config.get('USE_ACL'):
resources = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI)
@@ -409,8 +387,6 @@ 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
@@ -418,7 +394,7 @@ class CITypeGroupManager(object):
group_types.add(t["type_id"])
if need_other:
ci_types = CITypeManager.get_ci_types(type_ids=type_ids) if ci_types is None else ci_types
ci_types = CITypeManager.get_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):
@@ -539,8 +515,6 @@ 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
@@ -549,6 +523,7 @@ 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)
@@ -613,6 +588,7 @@ 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)
@@ -706,19 +682,9 @@ class CITypeAttributeManager(object):
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
item and item.soft_delete(commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.parent_attr_ids.index(attr_id)
elif item.child_id == type_id and attr_id in (item.child_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.child_attr_ids.index(attr_id)
else:
continue
item.update(parent_attr_ids=item_dict['parent_attr_ids'].pop(pop_idx),
child_attr_ids=item_dict['child_attr_ids'].pop(pop_idx),
commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
item.soft_delete(commit=False)
db.session.commit()
@@ -779,29 +745,21 @@ class CITypeRelationManager(object):
"""
@staticmethod
def get(type_ids=None):
def get():
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:
type2attributes[item.parent_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.parent_id)]
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
res[idx]['child'] = item.child.to_dict()
if item.child_id not in type2attributes:
type2attributes[item.child_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.child_id)]
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
res[idx]['relation_type'] = item.relation_type.to_dict()
result.append(res[idx])
return result, type2attributes
return res, type2attributes
@staticmethod
def get_child_type_ids(type_id, level):
@@ -833,8 +791,8 @@ class CITypeRelationManager(object):
ci_type_dict["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint
ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids
ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
return ci_type_dict
@@ -867,72 +825,6 @@ class CITypeRelationManager(object):
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
@staticmethod
def get_relations_by_type_id(type_id):
nodes, edges = [], []
node_ids, edge_tuples = set(), set()
ci_type = CITypeCache.get(type_id)
if ci_type is None:
return nodes, edges
ci_type_dict = ci_type.to_dict()
ci_type_dict.setdefault('level', [0])
nodes.append(ci_type_dict)
node_ids.add(ci_type.id)
def _find(_id, lv):
lv += 1
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
if i.child_id not in node_ids:
node_ids.add(i.child_id)
node = i.child.to_dict()
node.setdefault('level', []).append(lv)
nodes.append(node)
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
edge_tuples.add((i.parent_id, i.child_id))
_find(i.child_id, lv)
continue
elif (i.parent_id, i.child_id) not in edge_tuples:
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
edge_tuples.add((i.parent_id, i.child_id))
_find(i.child_id, lv)
for _node in nodes:
if _node['id'] == i.child_id and lv not in _node['level']:
_node['level'].append(lv)
def _reverse_find(_id, lv):
lv -= 1
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
if i.parent_id not in node_ids:
node_ids.add(i.parent_id)
node = i.parent.to_dict()
node.setdefault('level', []).append(lv)
nodes.append(node)
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
edge_tuples.add((i.parent_id, i.child_id))
_reverse_find(i.parent_id, lv)
continue
elif (i.parent_id, i.child_id) not in edge_tuples:
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
edge_tuples.add((i.parent_id, i.child_id))
_reverse_find(i.parent_id, lv)
for _node in nodes:
if _node['id'] == i.child_id and lv not in _node['level']:
_node['level'].append(lv)
level = 0
_reverse_find(ci_type.id, level)
level = 0
_find(ci_type.id, level)
return nodes, edges
@staticmethod
def _get(parent_id, child_id):
return CITypeRelation.get_by(parent_id=parent_id,
@@ -946,7 +838,7 @@ class CITypeRelationManager(object):
@classmethod
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
parent_attr_ids=None, child_attr_ids=None):
parent_attr_id=None, child_attr_id=None):
p = CITypeManager.check_is_existed(parent)
c = CITypeManager.check_is_existed(child)
@@ -961,22 +853,21 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
old_parent_attr_ids, old_child_attr_ids = None, None
old_parent_attr_id = None
existed = cls._get(p.id, c.id)
if existed is not None:
old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids)
old_child_attr_ids = copy.deepcopy(existed.child_attr_ids)
old_parent_attr_id = existed.parent_attr_id
existed = existed.update(relation_type_id=relation_type_id,
constraint=constraint,
parent_attr_ids=parent_attr_ids,
child_attr_ids=child_attr_ids,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
filter_none=False)
else:
existed = CITypeRelation.create(parent_id=p.id,
child_id=c.id,
relation_type_id=relation_type_id,
parent_attr_ids=parent_attr_ids,
child_attr_ids=child_attr_ids,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
constraint=constraint)
if current_app.config.get("USE_ACL"):
@@ -990,10 +881,10 @@ class CITypeRelationManager(object):
current_user.username,
ResourceTypeEnum.CI_TYPE_RELATION)
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(), current_user.uid))
if parent_attr_id and parent_attr_id != old_parent_attr_id:
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
@@ -1050,8 +941,7 @@ class CITypeAttributeGroupManager(object):
parent_ids = CITypeInheritanceManager.base(type_id)
groups = []
id2type = {i: CITypeCache.get(i) for i in parent_ids}
id2type = {k: v.alias for k, v in id2type.items() if v}
id2type = {i: CITypeCache.get(i).alias for i in parent_ids}
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)
@@ -1244,16 +1134,17 @@ class CITypeAttributeGroupManager(object):
if isinstance(_from, int):
from_group = CITypeAttributeGroup.get_by_id(_from)
else:
from_group = CITypeAttributeGroup.get_by(name=_from, type_id=type_id, first=True, to_dict=False)
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False)
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
if isinstance(_to, int):
to_group = CITypeAttributeGroup.get_by_id(_to)
else:
to_group = CITypeAttributeGroup.get_by(name=_to, type_id=type_id, first=True, to_dict=False)
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False)
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
from_order, to_order = from_group.order, to_group.order
from_group.update(order=to_order)
to_group.update(order=from_order)
@@ -1287,8 +1178,8 @@ class CITypeTemplateManager(object):
id2obj_dicts[added_id].get('child_id'),
id2obj_dicts[added_id].get('relation_type_id'),
id2obj_dicts[added_id].get('constraint'),
id2obj_dicts[added_id].get('parent_attr_ids'),
id2obj_dicts[added_id].get('child_attr_ids'),
id2obj_dicts[added_id].get('parent_attr_id'),
id2obj_dicts[added_id].get('child_attr_id'),
)
else:
obj = cls.create(flush=True, **id2obj_dicts[added_id])
@@ -1324,14 +1215,13 @@ 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:
@@ -1351,7 +1241,6 @@ 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'])
@@ -1389,7 +1278,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, attr_id_map):
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map):
for i in ci_type_relations:
i.pop('parent', None)
i.pop('child', None)
@@ -1399,32 +1288,15 @@ 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 Exception:
except BadRequest:
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:
@@ -1436,9 +1308,6 @@ 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'],
@@ -1502,9 +1371,6 @@ 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:
@@ -1517,10 +1383,10 @@ class CITypeTemplateManager(object):
if ad_rule:
rule['adr_id'] = ad_rule.id
ad_rule.update(valid=False, **adr)
ad_rule.update(**adr)
elif adr:
ad_rule = AutoDiscoveryRuleCRUD().add(valid=False, **adr)
ad_rule = AutoDiscoveryRuleCRUD().add(**adr)
rule['adr_id'] = ad_rule.id
else:
continue
@@ -1540,9 +1406,6 @@ class CITypeTemplateManager(object):
if ((i.extra_option or {}).get('alias') or None) == (
(rule.get('extra_option') or {}).get('alias') or None):
existed = True
rule.pop('extra_option', None)
rule.pop('enabled', None)
rule.pop('cron', None)
AutoDiscoveryCITypeCRUD().update(i.id, **rule)
break
@@ -1552,23 +1415,6 @@ 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
@@ -1580,8 +1426,6 @@ 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 {})
@@ -1600,14 +1444,9 @@ 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, attr_id_map)
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_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))
@@ -1620,73 +1459,26 @@ 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(type_ids=None):
def export_template():
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=ci_types,
ci_types=CITypeManager.get_ci_types(),
ci_type_groups=CITypeGroupManager.get(),
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
ci_type_relations=ci_type_relations,
ci_type_inheritance=CITypeInheritanceManager.get_all(type_ids=type_ids),
ci_type_relations=CITypeRelationManager.get()[0],
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:
@@ -1694,16 +1486,12 @@ class CITypeTemplateManager(object):
except:
return ""
type_id2name = {i['id']: i['name'] for i in tpt['ci_types']}
ad_rules = AutoDiscoveryCITypeCRUD.get_all(type_ids=type_ids)
ad_rules = AutoDiscoveryCITypeCRUD.get_all()
rules = []
for r in ad_rules:
r = r.to_dict()
if r.get('extra_option') and '_reference' in r['extra_option']:
r['extra_option'].pop('_reference')
r['type_name'] = type_id2name.get(r.pop('type_id'))
ci_type = CITypeCache.get(r.pop('type_id'))
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
@@ -1737,6 +1525,65 @@ class CITypeTemplateManager(object):
return tpt
@staticmethod
def export_template_by_type(type_id):
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found2.format("id={}".format(type_id)))
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.common_setting.upload_file import CommonFileCRUD
tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
ci_type_auto_discovery_rules=list(),
type2attributes=dict(),
type2attribute_group=dict(),
icons=dict()
)
def get_icon_value(icon):
try:
return CommonFileCRUD().get_file_binary_str(icon)
except:
return ""
ad_rules = AutoDiscoveryCITypeCRUD.get_by_type_id(ci_type.id)
rules = []
for r in ad_rules:
r = r.to_dict()
r['type_name'] = ci_type and ci_type.name
if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name
r['adr'] = adr and adr.to_dict() or {}
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
if icon_url and icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
rules.append(r)
tpt['ci_type_auto_discovery_rules'] = rules
for ci_type in tpt['ci_types']:
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
icon_url = ci_type['icon'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
for attr in tpt['type2attributes'][ci_type['id']]:
for i in (attr.get('choice_value') or []):
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
icon_url = i[1]['icon']['url'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
return tpt
class CITypeUniqueConstraintManager(object):
@staticmethod

View File

@@ -41,23 +41,23 @@ class OperateType(BaseEnum):
class CITypeOperateType(BaseEnum):
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"
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" # 删除关系
class RetKey(BaseEnum):
@@ -73,7 +73,6 @@ class ResourceTypeEnum(BaseEnum):
RELATION_VIEW = "RelationView" # read/update/delete/grant
CI_FILTER = "CIFilter" # read
PAGE = "page" # read
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
class PermEnum(BaseEnum):
@@ -93,8 +92,7 @@ class RoleEnum(BaseEnum):
class AutoDiscoveryType(BaseEnum):
AGENT = "agent"
SNMP = "snmp"
HTTP = "http" # cloud
COMPONENTS = "components"
HTTP = "http"
class AttributeDefaultValueEnum(BaseEnum):
@@ -108,17 +106,13 @@ 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"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id'}
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
L_TYPE = None
L_CI = None

View File

@@ -13,7 +13,6 @@ from api.lib.cmdb.const import OperateType
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.perm.acl.cache import UserCache
from api.models.cmdb import CI
from api.models.cmdb import Attribute
from api.models.cmdb import AttributeHistory
from api.models.cmdb import CIRelationHistory
@@ -232,8 +231,8 @@ class AttributeHistoryManger(object):
class CIRelationHistoryManager(object):
@staticmethod
def add(rel_obj, operate_type=OperateType.ADD, uid=None):
record = OperationRecord.create(uid=uid or current_user.uid)
def add(rel_obj, operate_type=OperateType.ADD):
record = OperationRecord.create(uid=current_user.uid)
CIRelationHistory.create(relation_id=rel_obj.id,
record_id=record.id,
@@ -307,7 +306,7 @@ class CITriggerHistoryManager(object):
def get(page, page_size, type_id=None, trigger_id=None, operate_type=None):
query = CITriggerHistory.get_by(only_query=True)
if type_id:
query = query.join(CI, CI.id == CITriggerHistory.ci_id).filter(CI.type_id == type_id)
query = query.filter(CITriggerHistory.type_id == type_id)
if trigger_id:
query = query.filter(CITriggerHistory.trigger_id == trigger_id)

View File

@@ -25,15 +25,14 @@ from api.lib.cmdb.resp_format import ErrFormat
from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem
class PreferenceManager(object):
pref_attr_cls = PreferenceShowAttributes
@@ -86,6 +85,7 @@ class PreferenceManager(object):
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
@staticmethod
def get_types2(instance=False, tree=False):
"""

View File

@@ -35,7 +35,7 @@ class ErrFormat(CommonErrFormat):
"Only creators and administrators are allowed to delete attributes!") # 目前只允许 属性创建人、管理员 删除属性!
# 属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type
attribute_name_cannot_be_builtin = _l(
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, _type, ci_type, ticket_id")
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, _type, ci_type")
attribute_choice_other_invalid = _l(
"Predefined value: Other model request parameters are illegal!") # 预定义值: 其他模型请求参数不合法!
@@ -62,7 +62,6 @@ class ErrFormat(CommonErrFormat):
"The model cannot be deleted because the CI already exists") # 因为CI已经存在不能删除模型
ci_exists_and_cannot_delete_inheritance = _l(
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在不能删除继承关系
ci_type_inheritance_cannot_delete = _l("The model is inherited and cannot be deleted") # 该模型被继承, 不能删除
# 因为关系视图 {} 引用了该模型,不能删除模型
ci_relation_view_exists_and_cannot_delete_type = _l(
@@ -145,8 +144,3 @@ class ErrFormat(CommonErrFormat):
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
# 因为该分组下定义了拓扑视图,不能删除
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")

View File

@@ -17,12 +17,10 @@ def search(query=None,
count=1,
sort=None,
excludes=None,
use_id_filter=False,
use_ci_filter=True):
use_id_filter=False):
if current_app.config.get("USE_ES"):
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
else:
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
return s

View File

@@ -70,7 +70,6 @@ class Search(object):
self.valid_type_names = []
self.type2filter_perms = dict()
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
@staticmethod
def _operator_proc(key):

View File

@@ -1,251 +0,0 @@
# -*- coding:utf-8 -*-
import json
from flask import abort
from flask import current_app
from flask_login import current_user
from werkzeug.exceptions import BadRequest
from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import TopologyView
from api.models.cmdb import TopologyViewGroup
class TopologyViewManager(object):
group_cls = TopologyViewGroup
cls = TopologyView
@classmethod
def get_name_by_id(cls, _id):
res = cls.cls.get_by_id(_id)
return res and res.name
def get_view_by_id(self, _id):
res = self.cls.get_by_id(_id)
return res and res.to_dict() or {}
@classmethod
def add_group(cls, name, order):
if order is None:
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
cur_max_order = cur_max_order and cur_max_order.order or 0
order = cur_max_order + 1
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
400, ErrFormat.topology_group_exists.format(name))
return cls.group_cls.create(name=name, order=order)
def update_group(self, group_id, name, view_ids):
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
if name is not None and name != existed.name:
existed.update(name=name)
for idx, view_id in enumerate(view_ids):
view = self.cls.get_by_id(view_id)
if view is not None:
view.update(group_id=group_id, order=idx)
return existed.to_dict()
@classmethod
def delete_group(cls, _id):
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
if cls.cls.get_by(group_id=_id, first=True):
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
existed.soft_delete()
@classmethod
def group_order(cls, group_ids):
for idx, group_id in enumerate(group_ids):
group = cls.group_cls.get_by_id(group_id)
group.update(order=idx + 1)
@classmethod
def add(cls, name, group_id, option, order=None, **kwargs):
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
if order is None:
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
cls.cls.order.desc()).first()
cur_max_order = cur_max_order and cur_max_order.order or 0
order = cur_max_order + 1
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
if current_app.config.get('USE_ACL'):
try:
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
except BadRequest:
pass
ACLManager().grant_resource_to_role(name,
current_user.username,
ResourceTypeEnum.TOPOLOGY_VIEW)
return inst
@classmethod
def update(cls, _id, **kwargs):
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
existed_name = existed.name
inst = existed.update(filter_none=False, **kwargs).to_dict()
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
try:
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
except BadRequest:
pass
return inst
@classmethod
def delete(cls, _id):
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
existed.soft_delete()
if current_app.config.get("USE_ACL"):
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
@classmethod
def group_inner_order(cls, _ids):
for idx, _id in enumerate(_ids):
topology = cls.cls.get_by_id(_id)
topology.update(order=idx + 1)
@classmethod
def get_all(cls):
resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)])
groups = cls.group_cls.get_by(to_dict=True)
groups = sorted(groups, key=lambda x: x['order'])
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
other_group = dict(views=[])
for view in topo_views:
if resources is not None and view['name'] not in resources:
continue
if view['group_id']:
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
else:
other_group['views'].append(view)
if other_group['views']:
groups.append(other_group)
return groups
@staticmethod
def relation_from_ci_type(type_id):
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
return dict(nodes=nodes, edges=edges)
def topology_view(self, view_id=None, preview=None):
if view_id is not None:
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
central_node_type, central_node_instances, path = (view.central_node_type,
view.central_node_instances, view.path)
else:
central_node_type = preview.get('central_node_type')
central_node_instances = preview.get('central_node_instances')
path = preview.get('path')
nodes, links = [], []
_type = CITypeCache.get(central_node_type)
if not _type:
return dict(nodes=nodes, links=links)
type2meta = {_type.id: _type.icon}
root_ids = []
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
central_node_instances)
s = ci_search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
current_app.logger.info(e)
return dict(nodes=nodes, links=links)
for i in response:
root_ids.append(i['_id'])
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
if not root_ids:
return dict(nodes=nodes, links=links)
prefix = REDIS_PREFIX_CI_RELATION
key = list(map(str, root_ids))
id2node = {}
for level in sorted([i for i in path.keys() if int(i) > 0]):
type_ids = {int(i) for i in path[level]}
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
new_key = []
for idx, from_id in enumerate(key):
for to_id, type_id in res[idx]:
if type_id in type_ids:
links.append({'from': from_id, 'to': to_id})
id2node[to_id] = {'id': to_id, 'type_id': type_id}
new_key.append(to_id)
if type_id not in type2meta:
type2meta[type_id] = CITypeCache.get(type_id).icon
key = new_key
ci_ids = list(map(int, root_ids))
for level in sorted([i for i in path.keys() if int(i) < 0]):
type_ids = {int(i) for i in path[level]}
res = CIRelationManager.get_parent_ids(ci_ids)
_ci_ids = []
for to_id in res:
for from_id, type_id in res[to_id]:
if type_id in type_ids:
from_id, to_id = str(from_id), str(to_id)
links.append({'from': from_id, 'to': to_id})
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
_ci_ids.append(from_id)
if type_id not in type2meta:
type2meta[type_id] = CITypeCache.get(type_id).icon
ci_ids = _ci_ids
fl = set()
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
type2show = {}
for type_id in type_ids:
ci_type = CITypeCache.get(type_id)
if ci_type:
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
if attr:
fl.add(attr.name)
type2show[type_id] = attr.name
if id2node:
s = ci_search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
use_id_filter=False, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError:
return dict(nodes=nodes, links=links)
for i in response:
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
nodes.extend(id2node.values())
return dict(nodes=nodes, links=links, type2meta=type2meta)

View File

@@ -28,31 +28,13 @@ def string2int(x):
return v
def str2date(x):
def str2datetime(x):
try:
return datetime.datetime.strptime(x, "%Y-%m-%d").date()
except ValueError:
pass
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")
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
class ValueTypeMap(object):
@@ -62,7 +44,7 @@ class ValueTypeMap(object):
ValueTypeEnum.TEXT: lambda x: x,
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
ValueTypeEnum.DATETIME: str2datetime,
ValueTypeEnum.DATE: str2date,
ValueTypeEnum.DATE: str2datetime,
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
}

View File

@@ -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_invalid2.format(alias, value))
return abort(400, ErrFormat.attribute_value_invalid.format(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, alias, value):
def check_re(expr, value):
if not re.compile(expr).match(str(value)):
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
return abort(400, ErrFormat.attribute_value_invalid.format(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, attr.alias, value)
self.check_re(attr.re_check, value)
return v
@@ -266,7 +266,6 @@ class AttributeValueManager(object):
:return:
"""
changed = []
has_dynamic = False
for key, value in ci_dict.items():
attr = key2attr.get(key)
if not attr:
@@ -290,16 +289,10 @@ class AttributeValueManager(object):
for idx in range(index, len(existed_attrs)):
existed_attr = existed_attrs[idx]
existed_attr.delete(flush=False, commit=False)
if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
else:
has_dynamic = True
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
for idx in range(index, len(value)):
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
else:
has_dynamic = True
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
else:
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
existed_value = existed_attr and existed_attr.value
@@ -308,10 +301,7 @@ class AttributeValueManager(object):
if existed_value is None and value is not None:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
else:
has_dynamic = True
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
else:
if existed_value != value and existed_attr:
if value is None:
@@ -319,22 +309,16 @@ class AttributeValueManager(object):
else:
existed_attr.update(value=value, flush=False, commit=False)
if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
else:
has_dynamic = True
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
if changed or has_dynamic:
try:
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.warning(str(e))
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
try:
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.warning(str(e))
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
return self.write_change2(changed, ticket_id=ticket_id), has_dynamic
else:
return None, has_dynamic
return self.write_change2(changed, ticket_id=ticket_id)
@staticmethod
def delete_attr_value(attr_id, ci_id, commit=True):

View File

@@ -3,7 +3,6 @@ import functools
from flask import abort, session
from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.perm.acl.acl import is_app_admin
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
@@ -17,7 +16,7 @@ def perms_role_required(app_name, resource_type_name, resource_name, perm, role_
except Exception as e:
# resource_type not exist, continue check role
if role_name:
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
if role_name not in session.get("acl", {}).get("parentRoles", []):
abort(403, ErrFormat.role_required.format(role_name))
return func(*args, **kwargs)
@@ -26,7 +25,7 @@ def perms_role_required(app_name, resource_type_name, resource_name, perm, role_
if not has_perms:
if role_name:
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
if role_name not in session.get("acl", {}).get("parentRoles", []):
abort(403, ErrFormat.role_required.format(role_name))
else:
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))

View File

@@ -48,12 +48,7 @@ class CMDBApp(BaseApp):
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]},
{"page": "TopologyView", "page_cn": "拓扑视图",
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
"create_topology_view"],
},
]
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read"]}]
def __init__(self):
super().__init__()

View File

@@ -1,6 +1,7 @@
# -*- coding:utf-8 -*-
from flask import abort
from sqlalchemy import func
from api.extensions import db
@@ -12,23 +13,17 @@ class DBMixin(object):
cls = None
@classmethod
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False,
last_size=None, **kwargs):
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False, **kwargs):
page = get_page(page)
page_size = get_page_size(page_size)
if fl is None:
query = db.session.query(cls.cls)
query = db.session.query(cls.cls).filter(cls.cls.deleted.is_(False))
else:
query = db.session.query(*[getattr(cls.cls, i) for i in fl])
query = db.session.query(*[getattr(cls.cls, i) for i in fl]).filter(cls.cls.deleted.is_(False))
_query = None
if count_query:
_query = db.session.query(func.count(cls.cls.id))
if hasattr(cls.cls, 'deleted'):
query = query.filter(cls.cls.deleted.is_(False))
if _query:
_query = _query.filter(cls.cls.deleted.is_(False))
_query = db.session.query(func.count(cls.cls.id)).filter(cls.cls.deleted.is_(False))
for k in kwargs:
if hasattr(cls.cls, k):
@@ -45,15 +40,14 @@ class DBMixin(object):
return _query, query
numfound = query.count()
if not last_size:
return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')()
for i in query.offset((page - 1) * page_size).limit(page_size)]
else:
offset = numfound - last_size
if offset < 0:
offset = 0
return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')()
for i in query.offset(offset).limit(last_size)]
return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')()
for i in query.offset((page - 1) * page_size).limit(page_size)]
def _must_be_required(self, _id):
existed = self.cls.get_by_id(_id)
existed or abort(404, "Factor [{}] does not exist".format(_id))
return existed
def _can_add(self, **kwargs):
raise NotImplementedError

View File

@@ -3,9 +3,9 @@
import msgpack
import redis_lock
from flask import current_app
from api.extensions import cache
from api.extensions import db
from api.extensions import rd
from api.lib.decorator import flush_db
from api.models.acl import App
@@ -161,7 +161,6 @@ class RoleRelationCache(object):
def get_parent_ids(cls, rid, app_id, force=False):
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
if not parent_ids or force:
db.session.commit()
from api.lib.perm.acl.role import RoleRelationCRUD
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
@@ -172,7 +171,6 @@ class RoleRelationCache(object):
def get_child_ids(cls, rid, app_id, force=False):
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
if not child_ids or force:
db.session.commit()
from api.lib.perm.acl.role import RoleRelationCRUD
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
@@ -189,7 +187,6 @@ class RoleRelationCache(object):
"""
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
if not resources or force:
db.session.commit()
from api.lib.perm.acl.role import RoleCRUD
resources = RoleCRUD.get_resources(rid, app_id)
if resources['id2perms'] or resources['group2perms']:
@@ -201,7 +198,6 @@ class RoleRelationCache(object):
def get_resources2(cls, rid, app_id, force=False):
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
if not r_g or force:
db.session.commit()
res = cls.get_resources(rid, app_id)
id2perms = res['id2perms']
group2perms = res['group2perms']

View File

@@ -79,8 +79,7 @@ class PermissionCRUD(object):
return r and cls.get_all(r.id)
@staticmethod
def grant(rid, perms, resource_id=None, group_id=None, rebuild=True,
source=AuditOperateSource.acl, force_update=False):
def grant(rid, perms, resource_id=None, group_id=None, rebuild=True, source=AuditOperateSource.acl):
app_id = None
rt_id = None
@@ -107,23 +106,8 @@ class PermissionCRUD(object):
if not perms:
perms = [i.get('name') for i in ResourceTypeCRUD.get_perms(group.resource_type_id)]
if force_update:
revoke_role_permissions = []
existed_perms = RolePermission.get_by(rid=rid,
app_id=app_id,
group_id=group_id,
resource_id=resource_id,
to_dict=False)
for role_perm in existed_perms:
perm = PermissionCache.get(role_perm.perm_id, rt_id)
if perm and perm.name not in perms:
role_perm.soft_delete()
revoke_role_permissions.append(role_perm)
AuditCRUD.add_permission_log(app_id, AuditOperateType.revoke, rid, rt_id,
revoke_role_permissions, source=source)
_role_permissions = []
for _perm in set(perms):
perm = PermissionCache.get(_perm, rt_id)
if not perm:

View File

@@ -315,12 +315,9 @@ class ResourceCRUD(object):
return resource
@staticmethod
def delete(_id, rebuild=True, app_id=None):
def delete(_id, rebuild=True):
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
if app_id is not None and resource.app_id != app_id:
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
origin = resource.to_dict()
resource.soft_delete()

View File

@@ -154,19 +154,19 @@ class RoleRelationCRUD(object):
if existed:
continue
if parent_id in cls.recursive_child_ids(child_id, app_id):
return abort(400, ErrFormat.inheritance_dead_loop)
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
RoleRelationCache.clean(parent_id, app_id)
RoleRelationCache.clean(child_id, app_id)
if parent_id in cls.recursive_child_ids(child_id, app_id):
return abort(400, ErrFormat.inheritance_dead_loop)
if app_id is None:
for app in AppCRUD.get_all():
if app.name != "acl":
RoleRelationCache.clean(child_id, app.id)
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
AuditScope.role_relation, role.id, {}, {},
{'child_ids': list(child_ids), 'parent_ids': [parent_id], }

View File

@@ -51,12 +51,12 @@ def _auth_with_key():
user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path)
if user and authenticated:
login_user(user)
# reset_session(user)
reset_session(user)
return True
role, authenticated = Role.query.authenticate_with_key(key, secret, req_args, path)
if role and authenticated:
# reset_session(None, role=role.name)
reset_session(None, role=role.name)
return True
return False

View File

@@ -29,6 +29,6 @@ class CommonErrFormat(object):
role_required = _l("Role {} can only operate!") # 角色 {} 才能操作!
user_not_found = _l("User {} does not exist") # 用户 {} 不存在
no_permission = _l("For resource: {}, you do not have {} permission!") # 您没有资源: {} 的{}权限!
no_permission = _l("You do not have {} permission for resource: {}!") # 您没有资源: {} 的{}权限!
no_permission2 = _l("You do not have permission to operate!") # 您没有操作权限!
no_permission_only_owner = _l("Only the creator or administrator has permission!") # 只有创建人或者管理员才有权限!

View File

@@ -3,8 +3,8 @@ import os
import secrets
import sys
import threading
from base64 import b64decode, b64encode
from base64 import b64decode, b64encode
from Cryptodome.Protocol.SecretSharing import Shamir
from colorama import Back, Fore, Style, init as colorama_init
from cryptography.hazmat.backends import default_backend
@@ -30,7 +30,6 @@ seal_status = True
secrets_encrypt_key = ""
secrets_root_key = ""
def string_to_bytes(value):
if not value:
return ""
@@ -79,7 +78,7 @@ class KeyManage:
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
self.backend = backend
threading.Thread(target=self.watch_root_key, args=(app,), daemon=True).start()
threading.Thread(target=self.watch_root_key, args=(app,)).start()
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
if not self.trigger:
@@ -413,7 +412,7 @@ class KeyManage:
class InnerCrypt:
def __init__(self):
self.encrypt_key = b64decode(secrets_encrypt_key)
# self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
#self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
def encrypt(self, plaintext):
"""
@@ -491,4 +490,4 @@ if __name__ == "__main__":
t_ciphertext, status1 = c.encrypt(t_plaintext)
print("Ciphertext:", t_ciphertext)
decrypted_plaintext, status2 = c.decrypt(t_ciphertext)
print("Decrypted plaintext:", decrypted_plaintext)
print("Decrypted plaintext:", decrypted_plaintext)

View File

@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
params = webhook.get('parameters') or None
if isinstance(params, dict):
params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
params = json.loads(Template(json.dumps(params)).render(payload))
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
auth = _wrap_auth(**webhook.get('authorization', {}))
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':

View File

@@ -2,6 +2,7 @@
import datetime
from sqlalchemy.dialects.mysql import DOUBLE
from api.extensions import db
@@ -10,7 +11,6 @@ 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
@@ -79,11 +79,8 @@ class CITypeRelation(Model):
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
parent_attr_ids = db.Column(db.JSON) # [parent_attr_id, ]
child_attr_ids = db.Column(db.JSON) # [child_attr_id, ]
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
@@ -104,7 +101,6 @@ class Attribute(Model):
is_link = db.Column(db.Boolean, default=False)
is_password = db.Column(db.Boolean, default=False)
is_sortable = db.Column(db.Boolean, default=False)
is_dynamic = db.Column(db.Boolean, default=False)
default = db.Column(db.JSON) # {"default": None}
@@ -213,26 +209,6 @@ class CITriggerHistory(Model):
webhook = db.Column(db.Text)
class TopologyViewGroup(Model):
__tablename__ = 'c_topology_view_groups'
name = db.Column(db.String(64), index=True)
order = db.Column(db.Integer, default=0)
class TopologyView(Model):
__tablename__ = 'c_topology_views'
name = db.Column(db.String(64), index=True)
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
category = db.Column(db.String(32))
central_node_type = db.Column(db.Integer)
central_node_instances = db.Column(db.Text)
path = db.Column(db.JSON)
order = db.Column(db.Integer, default=0)
option = db.Column(db.JSON)
class CITypeUniqueConstraint(Model):
__tablename__ = "c_c_t_u_c"
@@ -260,7 +236,6 @@ 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)
@@ -567,28 +542,18 @@ class AutoDiscoveryCIType(Model):
attributes = db.Column(db.JSON) # {ad_key: cmdb_key}
relation = db.Column(db.JSON) # [{ad_key: {type_id: x, attr_id: x}}], CMDB > 2.4.5: deprecated
relation = db.Column(db.JSON) # [{ad_key: {type_id: x, attr_id: x}}]
auto_accept = db.Column(db.Boolean, default=False)
agent_id = db.Column(db.String(8), index=True)
query_expr = db.Column(db.Text)
interval = db.Column(db.Integer) # seconds, > 2.4.5: deprecated
interval = db.Column(db.Integer) # seconds
cron = db.Column(db.String(128))
extra_option = db.Column(db.JSON)
uid = db.Column(db.Integer, index=True)
enabled = db.Column(db.Boolean, default=True)
class AutoDiscoveryCITypeRelation(Model):
__tablename__ = "c_ad_ci_type_relations"
ad_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
ad_key = db.Column(db.String(128))
peer_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
peer_attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
class AutoDiscoveryCI(Model):
@@ -606,45 +571,6 @@ class AutoDiscoveryCI(Model):
accept_time = db.Column(db.DateTime)
class AutoDiscoveryRuleSyncHistory(Model2):
__tablename__ = "c_ad_rule_sync_histories"
adt_id = db.Column(db.Integer, db.ForeignKey('c_ad_ci_types.id'))
oneagent_id = db.Column(db.String(8))
oneagent_name = db.Column(db.String(64))
sync_at = db.Column(db.DateTime, default=datetime.datetime.now())
class AutoDiscoveryExecHistory(Model2):
__tablename__ = "c_ad_exec_histories"
type_id = db.Column(db.Integer, index=True)
stdout = db.Column(db.Text)
class AutoDiscoveryCounter(Model2):
__tablename__ = "c_ad_counter"
type_id = db.Column(db.Integer, index=True)
rule_count = db.Column(db.Integer, default=0)
exec_target_count = db.Column(db.Integer, default=0)
instance_count = db.Column(db.Integer, default=0)
accept_count = db.Column(db.Integer, default=0)
this_month_count = db.Column(db.Integer, default=0)
this_week_count = db.Column(db.Integer, default=0)
last_month_count = db.Column(db.Integer, default=0)
last_week_count = db.Column(db.Integer, default=0)
class AutoDiscoveryAccount(Model):
__tablename__ = "c_ad_accounts"
uid = db.Column(db.Integer, index=True)
name = db.Column(db.String(64))
adr_id = db.Column(db.Integer, db.ForeignKey('c_ad_rules.id'))
config = db.Column(db.JSON)
class CIFilterPerms(Model):
__tablename__ = "c_ci_filter_perms"

View File

@@ -1,8 +1,8 @@
# -*- coding:utf-8 -*-
import datetime
import json
import redis_lock
from flask import current_app
from flask_login import login_user
@@ -12,21 +12,16 @@ from api.extensions import celery
from api.extensions import db
from api.extensions import es
from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache
from api.lib.utils import handle_arg_list
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import CI
from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute
@@ -60,10 +55,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, uid):
def rebuild_relation_for_attribute_changed(ci_type_relation):
from api.lib.cmdb.ci import CIRelationManager
CIRelationManager.rebuild_all_by_attribute(ci_type_relation, uid)
CIRelationManager.rebuild_all_by_attribute(ci_type_relation)
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
@@ -92,13 +87,6 @@ def ci_delete(ci_id):
else:
rd.delete(ci_id, REDIS_PREFIX_CI)
instance = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
if instance is not None:
adt = AutoDiscoveryCIType.get_by_id(instance.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
instance.delete()
current_app.logger.info("{0} delete..........".format(ci_id))
@@ -125,17 +113,18 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@reconnect_db
def ci_relation_cache(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
if ancestor_ids is not None:
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
@@ -202,15 +191,16 @@ def ci_relation_add(parent_dict, child_id, uid):
@reconnect_db
def ci_relation_delete(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if str(child_id) in children:
children.pop(str(child_id))
if str(child_id) in children:
children.pop(str(child_id))
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
if ancestor_ids is not None:
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
@@ -261,93 +251,3 @@ def calc_computed_attribute(attr_id, uid):
cis = CI.get_by(type_id=i.type_id, to_dict=False)
for ci in cis:
cim.update(ci.id, {})
@celery.task(name="cmdb.write_ad_rule_sync_history", queue=CMDB_QUEUE)
@reconnect_db
def write_ad_rule_sync_history(rules, oneagent_id, oneagent_name, sync_at):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleSyncHistoryCRUD
for rule in rules:
AutoDiscoveryRuleSyncHistoryCRUD().upsert(adt_id=rule['id'],
oneagent_id=oneagent_id,
oneagent_name=oneagent_name,
sync_at=sync_at,
commit=False)
try:
db.session.commit()
except Exception as e:
current_app.logger.error("write auto discovery rule sync history failed: {}".format(e))
db.session.rollback()
@celery.task(name="cmdb.build_relations_for_ad_accept", queue=CMDB_QUEUE)
@reconnect_db
def build_relations_for_ad_accept(adc, ci_id, ad_key2attr):
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
current_app.test_request_context().push()
login_user(UserCache.get('worker'))
relation_ads = AutoDiscoveryCITypeRelation.get_by(ad_type_id=adc['type_id'], to_dict=False)
for r_adt in relation_ads:
ad_key = r_adt.ad_key
if not adc['instance'].get(ad_key):
continue
ad_key_values = [adc['instance'].get(ad_key)] if not isinstance(
adc['instance'].get(ad_key), list) else adc['instance'].get(ad_key)
for ad_key_value in ad_key_values:
query = "_type:{},{}:{}".format(r_adt.peer_type_id, r_adt.peer_attr_id, ad_key_value)
s = ci_search(query, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
current_app.logger.error("build_relations_for_ad_accept failed: {}".format(e))
return
for relation_ci in response:
relation_ci_id = relation_ci['_id']
try:
CIRelationManager.add(ci_id, relation_ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
try:
CIRelationManager.add(relation_ci_id, ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
pass
# build relations in reverse
relation_ads = AutoDiscoveryCITypeRelation.get_by(peer_type_id=adc['type_id'], to_dict=False)
attr2ad_key = {v: k for k, v in ad_key2attr.items()}
for r_adt in relation_ads:
attr = AttributeCache.get(r_adt.peer_attr_id)
ad_key = attr2ad_key.get(attr and attr.name)
if not ad_key:
continue
ad_value = adc['instance'].get(ad_key)
peer_ad_key = r_adt.ad_key
peer_instances = AutoDiscoveryCI.get_by(type_id=r_adt.ad_type_id, to_dict=False)
for peer_instance in peer_instances:
peer_ad_values = peer_instance.instance.get(peer_ad_key)
peer_ad_values = [peer_ad_values] if not isinstance(peer_ad_values, list) else peer_ad_values
if ad_value in peer_ad_values and peer_instance.ci_id:
try:
CIRelationManager.add(peer_instance.ci_id, ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
try:
CIRelationManager.add(ci_id, peer_instance.ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
pass

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-06-20 19:12+0800\n"
"POT-Creation-Date: 2024-03-29 10:42+0800\n"
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh\n"
@@ -81,7 +81,7 @@ msgid "User {} does not exist"
msgstr "用户 {} 不存在"
#: api/lib/resp_format.py:32
msgid "For resource: {}, you do not have {} permission!"
msgid "You do not have {} permission for resource: {}!"
msgstr "您没有资源: {} 的{}权限!"
#: api/lib/resp_format.py:33
@@ -238,248 +238,212 @@ msgstr "因为CI已经存在不能删除模型"
msgid "The inheritance cannot be deleted because the CI already exists"
msgstr "因为CI已经存在不能删除继承关系"
#: api/lib/cmdb/resp_format.py:65
msgid "The model is inherited and cannot be deleted"
msgstr "该模型被继承, 不能删除"
#: api/lib/cmdb/resp_format.py:68
#: api/lib/cmdb/resp_format.py:67
msgid ""
"The model cannot be deleted because the model is referenced by the "
"relational view {}"
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
#: api/lib/cmdb/resp_format.py:70
#: api/lib/cmdb/resp_format.py:69
msgid "Model group {} does not exist"
msgstr "模型分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:71
#: api/lib/cmdb/resp_format.py:70
msgid "Model group {} already exists"
msgstr "模型分组 {} 已经存在"
#: api/lib/cmdb/resp_format.py:72
#: api/lib/cmdb/resp_format.py:71
msgid "Model relationship {} does not exist"
msgstr "模型关系 {} 不存在"
#: api/lib/cmdb/resp_format.py:73
#: api/lib/cmdb/resp_format.py:72
msgid "Attribute group {} already exists"
msgstr "属性分组 {} 已存在"
#: api/lib/cmdb/resp_format.py:74
#: api/lib/cmdb/resp_format.py:73
msgid "Attribute group {} does not exist"
msgstr "属性分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:76
#: api/lib/cmdb/resp_format.py:75
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
msgstr "属性组<{0}> - 属性<{1}> 不存在"
#: api/lib/cmdb/resp_format.py:77
#: api/lib/cmdb/resp_format.py:76
msgid "The unique constraint already exists!"
msgstr "唯一约束已经存在!"
#: api/lib/cmdb/resp_format.py:79
#: api/lib/cmdb/resp_format.py:78
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
msgstr "唯一约束的属性不能是 JSON 和 多值"
#: api/lib/cmdb/resp_format.py:80
#: api/lib/cmdb/resp_format.py:79
msgid "Duplicated trigger"
msgstr "重复的触发器"
#: api/lib/cmdb/resp_format.py:81
#: api/lib/cmdb/resp_format.py:80
msgid "Trigger {} does not exist"
msgstr "触发器 {} 不存在"
#: api/lib/cmdb/resp_format.py:82
msgid "Duplicated reconciliation rule"
msgstr ""
#: api/lib/cmdb/resp_format.py:83
msgid "Reconciliation rule {} does not exist"
msgstr "关系类型 {} 不存在"
#: api/lib/cmdb/resp_format.py:85
msgid "Operation record {} does not exist"
msgstr "操作记录 {} 不存在"
#: api/lib/cmdb/resp_format.py:86
#: api/lib/cmdb/resp_format.py:83
msgid "Unique identifier cannot be deleted"
msgstr "不能删除唯一标识"
#: api/lib/cmdb/resp_format.py:87
#: api/lib/cmdb/resp_format.py:84
msgid "Cannot delete default sorted attributes"
msgstr "不能删除默认排序的属性"
#: api/lib/cmdb/resp_format.py:89
#: api/lib/cmdb/resp_format.py:86
msgid "No node selected"
msgstr "没有选择节点"
#: api/lib/cmdb/resp_format.py:90
#: api/lib/cmdb/resp_format.py:87
msgid "This search option does not exist!"
msgstr "该搜索选项不存在!"
#: api/lib/cmdb/resp_format.py:91
#: api/lib/cmdb/resp_format.py:88
msgid "This search option has a duplicate name!"
msgstr "该搜索选项命名重复!"
#: api/lib/cmdb/resp_format.py:93
#: api/lib/cmdb/resp_format.py:90
msgid "Relationship type {} already exists"
msgstr "关系类型 {} 已经存在"
#: api/lib/cmdb/resp_format.py:94
#: api/lib/cmdb/resp_format.py:91
msgid "Relationship type {} does not exist"
msgstr "关系类型 {} 不存在"
#: api/lib/cmdb/resp_format.py:96
#: api/lib/cmdb/resp_format.py:93
msgid "Invalid attribute value: {}"
msgstr "无效的属性值: {}"
#: api/lib/cmdb/resp_format.py:97
#: api/lib/cmdb/resp_format.py:94
msgid "{} Invalid value: {}"
msgstr "{} 无效的值: {}"
#: api/lib/cmdb/resp_format.py:98
#: api/lib/cmdb/resp_format.py:95
msgid "{} is not in the predefined values"
msgstr "{} 不在预定义值里"
#: api/lib/cmdb/resp_format.py:100
#: api/lib/cmdb/resp_format.py:97
msgid "The value of attribute {} must be unique, {} already exists"
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
#: api/lib/cmdb/resp_format.py:101
#: api/lib/cmdb/resp_format.py:98
msgid "Attribute {} value must exist"
msgstr "属性 {} 值必须存在"
#: api/lib/cmdb/resp_format.py:102
#: api/lib/cmdb/resp_format.py:99
msgid "Out of range value, the maximum value is 2147483647"
msgstr "超过最大值限制, 最大值是2147483647"
#: api/lib/cmdb/resp_format.py:104
#: api/lib/cmdb/resp_format.py:101
msgid "Unknown error when adding or modifying attribute value: {}"
msgstr "新增或者修改属性值未知错误: {}"
#: api/lib/cmdb/resp_format.py:106
#: api/lib/cmdb/resp_format.py:103
msgid "Duplicate custom name"
msgstr "订制名重复"
#: api/lib/cmdb/resp_format.py:108
#: api/lib/cmdb/resp_format.py:105
msgid "Number of models exceeds limit: {}"
msgstr "模型数超过限制: {}"
#: api/lib/cmdb/resp_format.py:109
#: api/lib/cmdb/resp_format.py:106
msgid "The number of CIs exceeds the limit: {}"
msgstr "CI数超过限制: {}"
#: api/lib/cmdb/resp_format.py:111
#: api/lib/cmdb/resp_format.py:108
msgid "Auto-discovery rule: {} already exists!"
msgstr "自动发现规则: {} 已经存在!"
#: api/lib/cmdb/resp_format.py:112
#: api/lib/cmdb/resp_format.py:109
msgid "Auto-discovery rule: {} does not exist!"
msgstr "自动发现规则: {} 不存在!"
#: api/lib/cmdb/resp_format.py:114
#: api/lib/cmdb/resp_format.py:111
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
msgstr "该自动发现规则被模型引用, 不能删除!"
#: api/lib/cmdb/resp_format.py:116
#: api/lib/cmdb/resp_format.py:113
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
msgstr "自动发现规则的应用不能重复定义!"
#: api/lib/cmdb/resp_format.py:117
#: api/lib/cmdb/resp_format.py:114
msgid "The auto-discovery you want to modify: {} does not exist!"
msgstr "您要修改的自动发现: {} 不存在!"
#: api/lib/cmdb/resp_format.py:118
#: api/lib/cmdb/resp_format.py:115
msgid "Attribute does not include unique identifier: {}"
msgstr "属性字段没有包括唯一标识: {}"
#: api/lib/cmdb/resp_format.py:119
#: api/lib/cmdb/resp_format.py:116
msgid "The auto-discovery instance does not exist!"
msgstr "自动发现的实例不存在!"
#: api/lib/cmdb/resp_format.py:120
#: api/lib/cmdb/resp_format.py:117
msgid "The model is not associated with this auto-discovery!"
msgstr "模型并未关联该自动发现!"
#: api/lib/cmdb/resp_format.py:121
#: api/lib/cmdb/resp_format.py:118
msgid "Only the creator can modify the Secret!"
msgstr "只有创建人才能修改Secret!"
#: api/lib/cmdb/resp_format.py:123
#: api/lib/cmdb/resp_format.py:120
msgid "This rule already has auto-discovery instances and cannot be deleted!"
msgstr "该规则已经有自动发现的实例, 不能被删除!"
#: api/lib/cmdb/resp_format.py:125
#: api/lib/cmdb/resp_format.py:122
msgid "The default auto-discovery rule is already referenced by model {}!"
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
#: api/lib/cmdb/resp_format.py:127
#: api/lib/cmdb/resp_format.py:124
msgid "The unique_key method must return a non-empty string!"
msgstr "unique_key方法必须返回非空字符串!"
#: api/lib/cmdb/resp_format.py:128
#: api/lib/cmdb/resp_format.py:125
msgid "The attributes method must return a list"
msgstr "attributes方法必须返回的是list"
#: api/lib/cmdb/resp_format.py:130
#: api/lib/cmdb/resp_format.py:127
msgid "The list returned by the attributes method cannot be empty!"
msgstr "attributes方法返回的list不能为空!"
#: api/lib/cmdb/resp_format.py:132
#: api/lib/cmdb/resp_format.py:129
msgid "Only administrators can define execution targets as: all nodes!"
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
#: api/lib/cmdb/resp_format.py:133
#: api/lib/cmdb/resp_format.py:130
msgid "Execute targets permission check failed: {}"
msgstr "执行机器权限检查不通过: {}"
#: api/lib/cmdb/resp_format.py:135
#: api/lib/cmdb/resp_format.py:132
msgid "CI filter authorization must be named!"
msgstr "CI过滤授权 必须命名!"
#: api/lib/cmdb/resp_format.py:136
#: api/lib/cmdb/resp_format.py:133
msgid "CI filter authorization is currently not supported or query"
msgstr "CI过滤授权 暂时不支持 或 查询"
#: api/lib/cmdb/resp_format.py:139
#: api/lib/cmdb/resp_format.py:136
msgid "You do not have permission to operate attribute {}!"
msgstr "您没有属性 {} 的操作权限!"
#: api/lib/cmdb/resp_format.py:140
#: api/lib/cmdb/resp_format.py:137
msgid "You do not have permission to operate this CI!"
msgstr "您没有该CI的操作权限!"
#: api/lib/cmdb/resp_format.py:142
#: api/lib/cmdb/resp_format.py:139
msgid "Failed to save password: {}"
msgstr "保存密码失败: {}"
#: api/lib/cmdb/resp_format.py:143
#: api/lib/cmdb/resp_format.py:140
msgid "Failed to get password: {}"
msgstr "获取密码失败: {}"
#: api/lib/cmdb/resp_format.py:145
msgid "Scheduling time format error"
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
#: api/lib/cmdb/resp_format.py:146
msgid "CMDB data reconciliation results"
msgstr ""
#: api/lib/cmdb/resp_format.py:147
msgid "Number of {} illegal: {}"
msgstr ""
#: api/lib/cmdb/resp_format.py:149
msgid "Topology view {} already exists"
msgstr "拓扑视图 {} 已经存在"
#: api/lib/cmdb/resp_format.py:150
msgid "Topology group {} already exists"
msgstr "拓扑视图分组 {} 已经存在"
#: api/lib/cmdb/resp_format.py:152
msgid "The group cannot be deleted because the topology view already exists"
msgstr "因为该分组下定义了拓扑视图,不能删除"
#: api/lib/common_setting/resp_format.py:8
msgid "Company info already existed"
msgstr "公司信息已存在,无法创建!"
@@ -736,10 +700,6 @@ msgstr "LDAP测试用户名必填"
msgid "Company wide"
msgstr "全公司"
#: api/lib/common_setting/resp_format.py:84
msgid "No permission to access resource {}, perm {} "
msgstr "您没有资源: {} 的 {} 权限"
#: api/lib/perm/acl/resp_format.py:9
msgid "login successful"
msgstr "登录成功"

View File

@@ -1,32 +1,24 @@
# -*- coding:utf-8 -*-
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 AutoDiscoveryAccountCRUD
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_INNER
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
from api.lib.cmdb.search.ci import search
from api.lib.decorator import args_required
from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import has_perm_from_args
@@ -45,19 +37,14 @@ class AutoDiscoveryRuleView(APIView):
rebuild = False
exists = {i['name'] for i in res}
for i in copy.deepcopy(DEFAULT_INNER):
for i in DEFAULT_HTTP:
if i['name'] not in exists:
i.pop('en', None)
AutoDiscoveryRuleCRUD().add(**i)
rebuild = True
if rebuild:
_, res = AutoDiscoveryRuleCRUD.search(page=1, page_size=100000, **request.values)
for i in res:
if i['type'] == 'http':
i['resources'] = AutoDiscoveryHTTPManager().get_resources(i['name'])
return self.jsonify(res)
@args_required("name", value_required=True)
@@ -111,39 +98,24 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
class AutoDiscoveryRuleHTTPView(APIView):
url_prefix = ("/adr/http/<string:name>/categories",
"/adr/http/<string:name>/attributes",
"/adr/http/<string:name>/mapping",
"/adr/snmp/<string:name>/attributes",
"/adr/components/<string:name>/attributes",)
url_prefix = ("/adr/http/<string:name>/categories", "/adr/http/<string:name>/attributes",
"/adr/snmp/<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))
category = request.values.get('category')
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, category))
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
class AutoDiscoveryCITypeView(APIView):
url_prefix = ("/adt/ci_types/<int:type_id>",
"/adt/ci_types/<int:type_id>/attributes",
"/adt/<int:adt_id>")
url_prefix = ("/adt/ci_types/<int:type_id>", "/adt/<int:adt_id>")
def get(self, type_id):
if "attributes" in request.url:
return self.jsonify(AutoDiscoveryCITypeCRUD.get_ad_attributes(type_id))
_, res = AutoDiscoveryCITypeCRUD.search(page=1, page_size=100000, type_id=type_id, **request.values)
for i in res:
if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('secret'):
@@ -151,11 +123,6 @@ 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)
@@ -179,27 +146,6 @@ class AutoDiscoveryCITypeView(APIView):
return self.jsonify(adt_id=adt_id)
class AutoDiscoveryCITypeRelationView(APIView):
url_prefix = ("/adt/ci_types/<int:type_id>/relations", "/adt/relations/<int:_id>")
def get(self, type_id):
_, res = AutoDiscoveryCITypeRelationCRUD.search(page=1, page_size=100000, ad_type_id=type_id, **request.values)
return self.jsonify(res)
@args_required("relations")
def post(self, type_id):
return self.jsonify(AutoDiscoveryCITypeRelationCRUD().upsert(type_id, request.values['relations']))
def put(self):
return self.post()
def delete(self, _id):
AutoDiscoveryCITypeRelationCRUD().delete(_id)
return self.jsonify(id=_id)
class AutoDiscoveryCIView(APIView):
url_prefix = ("/adc", "/adc/<int:adc_id>", "/adc/ci_types/<int:type_id>/attributes", "/adc/ci_types")
@@ -267,127 +213,24 @@ class AutoDiscoveryRuleSyncView(APIView):
url_prefix = ("/adt/sync",)
def get(self):
if current_user.username not in PRIVILEGED_USERS:
if current_user.username not in ("cmdb_agent", "worker", "admin"):
return abort(403)
oneagent_name = request.values.get('oneagent_name')
oneagent_id = request.values.get('oneagent_id')
last_update_at = request.values.get('last_update_at')
response = []
if AttributeCache.get('oneagent_id'):
query = "oneagent_id:{}".format(oneagent_id)
s = ci_search(query)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
import traceback
current_app.logger.error(traceback.format_exc())
return abort(400, str(e))
query = "{},oneagent_id:{}".format(oneagent_name, oneagent_id)
current_app.logger.info(query)
s = search(query)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
import traceback
current_app.logger.error(traceback.format_exc())
return abort(400, str(e))
for res in response:
if res.get('{}_name'.format(res['ci_type'])) == oneagent_name or oneagent_name == res.get('oneagent_name'):
ci_id = res["_id"]
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(ci_id, oneagent_id, oneagent_name, last_update_at)
return self.jsonify(rules=rules, last_update_at=last_update_at)
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
ci_id = response and response[0]["_id"]
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(ci_id, oneagent_id, last_update_at)
return self.jsonify(rules=rules, last_update_at=last_update_at)
class AutoDiscoveryRuleSyncHistoryView(APIView):
url_prefix = ("/adt/<int:adt_id>/sync/histories",)
def get(self, adt_id):
page = get_page(request.values.pop('page', 1))
page_size = get_page_size(request.values.pop('page_size', None))
numfound, res = AutoDiscoveryRuleSyncHistoryCRUD.search(page=page,
page_size=page_size,
adt_id=adt_id,
**request.values)
return self.jsonify(page=page,
page_size=page_size,
numfound=numfound,
total=len(res),
result=res)
class AutoDiscoveryTestView(APIView):
url_prefix = ("/adt/<int:adt_id>/test", "/adt/test/<string:exec_id>/result")
def get(self, exec_id):
return self.jsonify(stdout="1\n2\n3", exec_id=exec_id)
def post(self, adt_id):
return self.jsonify(exec_id=uuid.uuid4().hex)
class AutoDiscoveryExecHistoryView(APIView):
url_prefix = ("/adc/exec/histories",)
@args_required('type_id')
def get(self):
page = get_page(request.values.pop('page', 1))
page_size = get_page_size(request.values.pop('page_size', None))
last_size = request.values.pop('last_size', None)
if last_size and last_size.isdigit():
last_size = int(last_size)
numfound, res = AutoDiscoveryExecHistoryCRUD.search(page=page,
page_size=page_size,
last_size=last_size,
**request.values)
return self.jsonify(page=page,
page_size=page_size,
numfound=numfound,
total=len(res),
result=res)
@args_required('type_id')
@args_required('stdout')
def post(self):
AutoDiscoveryExecHistoryCRUD().add(type_id=request.values.get('type_id'),
stdout=request.values.get('stdout'))
return self.jsonify(code=200)
class AutoDiscoveryCounterView(APIView):
url_prefix = ("/adc/counter",)
@args_required('type_id')
def get(self):
type_id = request.values.get('type_id')
return self.jsonify(AutoDiscoveryCounterCRUD().get(type_id))
class AutoDiscoveryAccountView(APIView):
url_prefix = ("/adr/accounts", "/adr/accounts/<int:account_id>")
@args_required('adr_id')
def get(self):
adr_id = request.values.get('adr_id')
return self.jsonify(AutoDiscoveryAccountCRUD().get(adr_id))
@args_required('adr_id')
@args_required('accounts', value_required=False)
def post(self):
AutoDiscoveryAccountCRUD().upsert(**request.values)
return self.jsonify(code=200)
@args_required('config')
def put(self, account_id):
res = AutoDiscoveryAccountCRUD().update(account_id, **request.values)
return self.jsonify(res.to_dict())
def delete(self, account_id):
AutoDiscoveryAccountCRUD().delete(account_id)
return self.jsonify(account_id=account_id)

View File

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

View File

@@ -51,11 +51,7 @@ class CITypeView(APIView):
q = request.args.get("type_name")
if type_id is not None:
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 = CITypeCache.get(type_id).to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
ci_types = [ci_type]
elif type_name is not None:
@@ -361,13 +357,15 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/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): # 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)))
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()))
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)

View File

@@ -57,10 +57,10 @@ class CITypeRelationView(APIView):
def post(self, parent_id, child_id):
relation_type_id = request.values.get("relation_type_id")
constraint = request.values.get("constraint")
parent_attr_ids = request.values.get("parent_attr_ids")
child_attr_ids = request.values.get("child_attr_ids")
parent_attr_id = request.values.get("parent_attr_id")
child_attr_id = request.values.get("child_attr_id")
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
parent_attr_ids, child_attr_ids)
parent_attr_id, child_attr_id)
return self.jsonify(ctr_id=ctr_id)

View File

@@ -1,178 +0,0 @@
# -*- coding:utf-8 -*-
from flask import abort
from flask import request
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.topology import TopologyViewManager
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required
from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin
from api.resource import APIView
app_cli = CMDBApp()
class TopologyGroupView(APIView):
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
@args_required('name')
@args_validate(TopologyViewManager.group_cls)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_group, app_cli.admin_name)
def post(self):
name = request.values.get('name')
order = request.values.get('order')
group = TopologyViewManager.add_group(name, order)
return self.jsonify(group.to_dict())
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.update_topology_group, app_cli.admin_name)
def put(self, group_id):
name = request.values.get('name')
view_ids = request.values.get('view_ids')
group = TopologyViewManager().update_group(group_id, name, view_ids)
return self.jsonify(**group)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.delete_topology_group, app_cli.admin_name)
def delete(self, group_id):
TopologyViewManager.delete_group(group_id)
return self.jsonify(group_id=group_id)
class TopologyGroupOrderView(APIView):
url_prefix = ('/topology_views/groups/order',)
@args_required('group_ids')
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.update_topology_group, app_cli.admin_name)
def post(self):
group_ids = request.values.get('group_ids')
TopologyViewManager.group_order(group_ids)
return self.jsonify(group_ids=group_ids)
def put(self):
return self.post()
class TopologyView(APIView):
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
def get(self, type_id=None, _id=None):
if type_id is not None:
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
if _id is not None:
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
return self.jsonify(TopologyViewManager.get_all())
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
@args_validate(TopologyViewManager.cls)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_view, app_cli.admin_name)
def post(self):
name = request.values.pop('name')
group_id = request.values.pop('group_id', None)
option = request.values.pop('option', None)
order = request.values.pop('order', None)
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
return self.jsonify(topo_view)
@args_validate(TopologyViewManager.cls)
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
def put(self, _id):
topo_view = TopologyViewManager.update(_id, **request.values)
return self.jsonify(topo_view)
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
def delete(self, _id):
TopologyViewManager.delete(_id)
return self.jsonify(code=200)
class TopologyOrderView(APIView):
url_prefix = ('/topology_views/order',)
@args_required('view_ids')
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_view, app_cli.admin_name)
def post(self):
view_ids = request.values.get('view_ids')
TopologyViewManager.group_inner_order(view_ids)
return self.jsonify(view_ids=view_ids)
def put(self):
return self.post()
class TopologyViewPreview(APIView):
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
def get(self, _id=None):
if _id is not None:
acl = ACLManager('cmdb')
resource_name = TopologyViewManager.get_name_by_id(_id)
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
not is_app_admin('cmdb')):
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
else:
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
def post(self, _id=None):
return self.get(_id)
class TopologyViewGrantView(APIView):
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
def post(self, view_id, rid):
perms = request.values.pop('perms', None)
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
acl = ACLManager('cmdb')
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
return self.jsonify(code=200)
class TopologyViewRevokeView(APIView):
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
@args_required('perms')
def post(self, view_id, rid):
perms = request.values.pop('perms', None)
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
acl = ACLManager('cmdb')
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
return self.jsonify(code=200)

View File

@@ -1,7 +1,7 @@
-i https://mirrors.aliyun.com/pypi/simple
alembic==1.7.7
bs4==0.0.1
celery==5.3.1
celery>=5.3.1
celery-once==3.0.1
click==8.1.3
elasticsearch==7.17.9
@@ -31,7 +31,6 @@ marshmallow==2.20.2
more-itertools==5.0.0
msgpack-python==0.5.6
Pillow>=10.0.1
pycryptodome==3.12.0
cryptography>=41.0.2
PyJWT==2.4.0
PyMySQL==1.1.0
@@ -54,5 +53,4 @@ shamir~=17.12.0
pycryptodomex>=3.19.0
colorama>=0.4.6
lz4>=4.3.2
python-magic==0.4.27
jsonpath==0.82.2
python-magic==0.4.27

View File

@@ -20,16 +20,10 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
# # database
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
SQLALCHEMY_BINDS = {
'user': SQLALCHEMY_DATABASE_URI
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
}
SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False

View File

@@ -39,7 +39,7 @@
"md5": "^2.2.1",
"moment": "^2.24.0",
"nprogress": "^0.2.0",
"relation-graph": "^2.1.42",
"relation-graph": "^1.1.0",
"snabbdom": "^3.5.1",
"sortablejs": "1.9.0",
"style-resources-loader": "^1.5.0",
@@ -59,7 +59,7 @@
"vue-template-compiler": "2.6.11",
"vuedraggable": "^2.23.0",
"vuex": "^3.1.1",
"vxe-table": "3.7.10",
"vxe-table": "3.6.9",
"vxe-table-plugin-export-xlsx": "2.0.0",
"xe-utils": "3",
"xlsx": "0.15.0",

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1721959219377') format('woff2'),
url('iconfont.woff?t=1721959219377') format('woff'),
url('iconfont.ttf?t=1721959219377') format('truetype');
src: url('iconfont.woff2?t=1713840593232') format('woff2'),
url('iconfont.woff?t=1713840593232') format('woff'),
url('iconfont.ttf?t=1713840593232') format('truetype');
}
.iconfont {
@@ -13,474 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.caise-data_center:before {
content: "\e96f";
}
.caise-folder:before {
content: "\e970";
}
.caise-resource_pool:before {
content: "\e971";
}
.caise-network:before {
content: "\e972";
}
.caise-distributed_switch:before {
content: "\e973";
}
.caise-standard_switch:before {
content: "\e974";
}
.caise-host_cluster:before {
content: "\e975";
}
.caise-storage_cluster:before {
content: "\e976";
}
.caise-data_storage:before {
content: "\e977";
}
.veops-account:before {
content: "\e96e";
}
.veops-collect:before {
content: "\e96d";
}
.veops-collected:before {
content: "\e96c";
}
.veops-text:before {
content: "\e96b";
}
.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";
}
.cmdb-not_warehousing:before {
content: "\e95d";
}
.cmdb-warehousing:before {
content: "\e95e";
}
.cmdb-prompt:before {
content: "\e95c";
}
.cmdb-arrow:before {
content: "\e95b";
}
.cmdb-automatic_inventory:before {
content: "\e95a";
}
.cmdb-week_additions:before {
content: "\e959";
}
.cmdb-month_additions:before {
content: "\e958";
}
.cmdb-rule:before {
content: "\e955";
}
.cmdb-executing_machine:before {
content: "\e956";
}
.cmdb-resource:before {
content: "\e957";
}
.cmdb-discovery_resources:before {
content: "\e954";
}
.cmdb-association:before {
content: "\e953";
}
.ops-is_dynamic-disabled:before {
content: "\e952";
}
.itsm-pdf:before {
content: "\e951";
}
.monitor-sqlserver:before {
content: "\e950";
}
.monitor-dig2:before {
content: "\e94d";
}
.monitor-base2:before {
content: "\e94e";
}
.monitor-foreground1:before {
content: "\e94f";
}
.monitor-log2:before {
content: "\e945";
}
.monitor-backgroud1:before {
content: "\e946";
}
.monitor-port1:before {
content: "\e947";
}
.monitor-ipmi2:before {
content: "\e948";
}
.monitor-process2:before {
content: "\e949";
}
.monitor-snmp2:before {
content: "\e94a";
}
.monitor-performance1:before {
content: "\e94b";
}
.monitor-testing1:before {
content: "\e94c";
}
.monitor-ping2:before {
content: "\e941";
}
.monitor-prometheus:before {
content: "\e942";
}
.monitor-websocket2:before {
content: "\e943";
}
.monitor-traceroute2:before {
content: "\e944";
}
.monitor-port:before {
content: "\e93c";
}
.monitor-base1:before {
content: "\e93d";
}
.monitor-backgroud:before {
content: "\e93e";
}
.monitor-dig1:before {
content: "\e93f";
}
.monitor-foreground:before {
content: "\e940";
}
.monitor-log1:before {
content: "\e934";
}
.monitor-process1:before {
content: "\e935";
}
.monitor-testing:before {
content: "\e936";
}
.monitor-snmp1:before {
content: "\e937";
}
.monitor-performance:before {
content: "\e938";
}
.monitor-traceroute1:before {
content: "\e939";
}
.monitor-ping1:before {
content: "\e93a";
}
.monitor-ipmi1:before {
content: "\e93b";
}
.a-monitor-prometheus1:before {
content: "\e932";
}
.monitor-websocket1:before {
content: "\e933";
}
.monitor-group_expansion1:before {
content: "\e930";
}
.monitor-group_collapse1:before {
content: "\e931";
}
.monitor-group_expansion:before {
content: "\e92e";
}
.monitor-group_collapse:before {
content: "\e92f";
}
.monitor-list_view:before {
content: "\e92d";
}
.monitor-group_view:before {
content: "\e92c";
}
.ops-topology_view:before {
content: "\e92b";
}
.monitor-host_analysis:before {
content: "\e92a";
}
.monitor-add2:before {
content: "\e929";
}
.monitor-native:before {
content: "\e928";
}
.veops-filter2:before {
content: "\e927";
}
.ops-cmdb-data_companies-selected:before {
content: "\e601";
}
.ops-cmdb-data_companies:before {
content: "\e926";
}
.monitor-threshold_value:before {
content: "\e921";
}
.monitor-disposition:before {
content: "\e922";
}
.monitor-automatic_discovery:before {
content: "\e923";
}
.monitor-grouping_list:before {
content: "\e924";
}
.monitor-node_list:before {
content: "\e925";
}
.monitor-general_view:before {
content: "\e920";
}
.monitor-network_topology:before {
content: "\e91b";
}
.monitor-node_management:before {
content: "\e91c";
}
.monitor-alarm_policy:before {
content: "\e91d";
}
.monitor-alarm:before {
content: "\e91e";
}
.monitor-healing:before {
content: "\e91f";
}
.monitor-data_acquisition:before {
content: "\e8d4";
}
.monitor-analysis:before {
content: "\e91a";
}
.monitor-index:before {
content: "\e89b";
}
.monitor-user_defined:before {
content: "\e867";
}
.monitor-database:before {
content: "\e861";
}
.monitor-common:before {
content: "\e865";
}
.veops-edit:before {
content: "\e866";
}
.veops-empower:before {
content: "\e863";
}
.veops-share:before {
content: "\e864";
}
.veops-export:before {
content: "\e862";
}
.monitor-ip:before {
content: "\e807";
}
.monitor-director:before {
content: "\e803";
}
.monitor-host:before {
content: "\e804";
}
.a-cmdb-log1:before {
content: "\e802";
}
.monitor-add:before {
content: "\e7ff";
}
.monitor-down:before {
content: "\e7fc";
}
.monitor-up:before {
content: "\e7fd";
}
.itsm-unfold:before {
content: "\e7f9";
}
.itsm-stretch:before {
content: "\e7f8";
}
.monitor-data_comaparison2:before {
content: "\e7a1";
}
.monitor-data_comaparison1:before {
content: "\e7f7";
}
.a-monitor-online1:before {
content: "\e7a0";
}
.ops-setting-application-selected:before {
content: "\e919";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,825 +5,6 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "41143117",
"name": "caise-数据中心",
"font_class": "caise-data_center",
"unicode": "e96f",
"unicode_decimal": 59759
},
{
"icon_id": "41143118",
"name": "caise-文件夹",
"font_class": "caise-folder",
"unicode": "e970",
"unicode_decimal": 59760
},
{
"icon_id": "41143119",
"name": "caise-资源池",
"font_class": "caise-resource_pool",
"unicode": "e971",
"unicode_decimal": 59761
},
{
"icon_id": "41143120",
"name": "caise-网络",
"font_class": "caise-network",
"unicode": "e972",
"unicode_decimal": 59762
},
{
"icon_id": "41143121",
"name": "caise-分布式交换机",
"font_class": "caise-distributed_switch",
"unicode": "e973",
"unicode_decimal": 59763
},
{
"icon_id": "41143122",
"name": "caise-标准式交换机",
"font_class": "caise-standard_switch",
"unicode": "e974",
"unicode_decimal": 59764
},
{
"icon_id": "41143128",
"name": "caise-主机集群",
"font_class": "caise-host_cluster",
"unicode": "e975",
"unicode_decimal": 59765
},
{
"icon_id": "41143129",
"name": "caise-数据存储集群",
"font_class": "caise-storage_cluster",
"unicode": "e976",
"unicode_decimal": 59766
},
{
"icon_id": "41143143",
"name": "caise-数据存储",
"font_class": "caise-data_storage",
"unicode": "e977",
"unicode_decimal": 59767
},
{
"icon_id": "41141857",
"name": "veops-account",
"font_class": "veops-account",
"unicode": "e96e",
"unicode_decimal": 59758
},
{
"icon_id": "41128804",
"name": "veops-collect",
"font_class": "veops-collect",
"unicode": "e96d",
"unicode_decimal": 59757
},
{
"icon_id": "41128781",
"name": "veops-collected",
"font_class": "veops-collected",
"unicode": "e96c",
"unicode_decimal": 59756
},
{
"icon_id": "41106846",
"name": "veops-text",
"font_class": "veops-text",
"unicode": "e96b",
"unicode_decimal": 59755
},
{
"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",
"font_class": "cmdb-manual_warehousing",
"unicode": "e95f",
"unicode_decimal": 59743
},
{
"icon_id": "40791408",
"name": "cmdb-not_warehousing",
"font_class": "cmdb-not_warehousing",
"unicode": "e95d",
"unicode_decimal": 59741
},
{
"icon_id": "40791401",
"name": "cmdb-warehousing",
"font_class": "cmdb-warehousing",
"unicode": "e95e",
"unicode_decimal": 59742
},
{
"icon_id": "40731588",
"name": "cmdb-prompt",
"font_class": "cmdb-prompt",
"unicode": "e95c",
"unicode_decimal": 59740
},
{
"icon_id": "40722326",
"name": "cmdb-arrow",
"font_class": "cmdb-arrow",
"unicode": "e95b",
"unicode_decimal": 59739
},
{
"icon_id": "40711364",
"name": "cmdb-automatic_inventory",
"font_class": "cmdb-automatic_inventory",
"unicode": "e95a",
"unicode_decimal": 59738
},
{
"icon_id": "40711409",
"name": "cmdb-week_additions",
"font_class": "cmdb-week_additions",
"unicode": "e959",
"unicode_decimal": 59737
},
{
"icon_id": "40711428",
"name": "cmdb-month_additions",
"font_class": "cmdb-month_additions",
"unicode": "e958",
"unicode_decimal": 59736
},
{
"icon_id": "40711344",
"name": "cmdb-rule",
"font_class": "cmdb-rule",
"unicode": "e955",
"unicode_decimal": 59733
},
{
"icon_id": "40711349",
"name": "cmdb-executing_machine",
"font_class": "cmdb-executing_machine",
"unicode": "e956",
"unicode_decimal": 59734
},
{
"icon_id": "40711356",
"name": "cmdb-resource",
"font_class": "cmdb-resource",
"unicode": "e957",
"unicode_decimal": 59735
},
{
"icon_id": "40705423",
"name": "cmdb-discovery_resources",
"font_class": "cmdb-discovery_resources",
"unicode": "e954",
"unicode_decimal": 59732
},
{
"icon_id": "40701723",
"name": "cmdb-association",
"font_class": "cmdb-association",
"unicode": "e953",
"unicode_decimal": 59731
},
{
"icon_id": "40645466",
"name": "ops-is_dynamic-disabled",
"font_class": "ops-is_dynamic-disabled",
"unicode": "e952",
"unicode_decimal": 59730
},
{
"icon_id": "40590472",
"name": "itsm-pdf",
"font_class": "itsm-pdf",
"unicode": "e951",
"unicode_decimal": 59729
},
{
"icon_id": "40552176",
"name": "monitor-sqlserver",
"font_class": "monitor-sqlserver",
"unicode": "e950",
"unicode_decimal": 59728
},
{
"icon_id": "40548499",
"name": "monitor-dig",
"font_class": "monitor-dig2",
"unicode": "e94d",
"unicode_decimal": 59725
},
{
"icon_id": "40548507",
"name": "monitor-base",
"font_class": "monitor-base2",
"unicode": "e94e",
"unicode_decimal": 59726
},
{
"icon_id": "40548498",
"name": "monitor-foreground",
"font_class": "monitor-foreground1",
"unicode": "e94f",
"unicode_decimal": 59727
},
{
"icon_id": "40548504",
"name": "monitor-log",
"font_class": "monitor-log2",
"unicode": "e945",
"unicode_decimal": 59717
},
{
"icon_id": "40548508",
"name": "monitor-backgroud",
"font_class": "monitor-backgroud1",
"unicode": "e946",
"unicode_decimal": 59718
},
{
"icon_id": "40548502",
"name": "monitor-port",
"font_class": "monitor-port1",
"unicode": "e947",
"unicode_decimal": 59719
},
{
"icon_id": "40548501",
"name": "monitor-ipmi",
"font_class": "monitor-ipmi2",
"unicode": "e948",
"unicode_decimal": 59720
},
{
"icon_id": "40548511",
"name": "monitor-process",
"font_class": "monitor-process2",
"unicode": "e949",
"unicode_decimal": 59721
},
{
"icon_id": "40548506",
"name": "monitor-snmp",
"font_class": "monitor-snmp2",
"unicode": "e94a",
"unicode_decimal": 59722
},
{
"icon_id": "40548500",
"name": "monitor-performance",
"font_class": "monitor-performance1",
"unicode": "e94b",
"unicode_decimal": 59723
},
{
"icon_id": "40548510",
"name": "monitor-testing",
"font_class": "monitor-testing1",
"unicode": "e94c",
"unicode_decimal": 59724
},
{
"icon_id": "40548503",
"name": "monitor-ping",
"font_class": "monitor-ping2",
"unicode": "e941",
"unicode_decimal": 59713
},
{
"icon_id": "40548509",
"name": "monitor-prometheus",
"font_class": "monitor-prometheus",
"unicode": "e942",
"unicode_decimal": 59714
},
{
"icon_id": "40548505",
"name": "monitor-websocket",
"font_class": "monitor-websocket2",
"unicode": "e943",
"unicode_decimal": 59715
},
{
"icon_id": "40548512",
"name": "monitor-traceroute",
"font_class": "monitor-traceroute2",
"unicode": "e944",
"unicode_decimal": 59716
},
{
"icon_id": "40548205",
"name": "monitor-port",
"font_class": "monitor-port",
"unicode": "e93c",
"unicode_decimal": 59708
},
{
"icon_id": "40548204",
"name": "monitor-base",
"font_class": "monitor-base1",
"unicode": "e93d",
"unicode_decimal": 59709
},
{
"icon_id": "40548203",
"name": "monitor-backgroud",
"font_class": "monitor-backgroud",
"unicode": "e93e",
"unicode_decimal": 59710
},
{
"icon_id": "40548202",
"name": "monitor-dig",
"font_class": "monitor-dig1",
"unicode": "e93f",
"unicode_decimal": 59711
},
{
"icon_id": "40548201",
"name": "monitor-foreground",
"font_class": "monitor-foreground",
"unicode": "e940",
"unicode_decimal": 59712
},
{
"icon_id": "40548213",
"name": "monitor-log",
"font_class": "monitor-log1",
"unicode": "e934",
"unicode_decimal": 59700
},
{
"icon_id": "40548212",
"name": "monitor-process",
"font_class": "monitor-process1",
"unicode": "e935",
"unicode_decimal": 59701
},
{
"icon_id": "40548211",
"name": "monitor-testing",
"font_class": "monitor-testing",
"unicode": "e936",
"unicode_decimal": 59702
},
{
"icon_id": "40548210",
"name": "monitor-snmp",
"font_class": "monitor-snmp1",
"unicode": "e937",
"unicode_decimal": 59703
},
{
"icon_id": "40548209",
"name": "monitor-performance",
"font_class": "monitor-performance",
"unicode": "e938",
"unicode_decimal": 59704
},
{
"icon_id": "40548208",
"name": "monitor-traceroute",
"font_class": "monitor-traceroute1",
"unicode": "e939",
"unicode_decimal": 59705
},
{
"icon_id": "40548207",
"name": "monitor-ping",
"font_class": "monitor-ping1",
"unicode": "e93a",
"unicode_decimal": 59706
},
{
"icon_id": "40548206",
"name": "monitor-ipmi",
"font_class": "monitor-ipmi1",
"unicode": "e93b",
"unicode_decimal": 59707
},
{
"icon_id": "40548217",
"name": "monitor-prometheus (1)",
"font_class": "a-monitor-prometheus1",
"unicode": "e932",
"unicode_decimal": 59698
},
{
"icon_id": "40548214",
"name": "monitor-websocket",
"font_class": "monitor-websocket1",
"unicode": "e933",
"unicode_decimal": 59699
},
{
"icon_id": "40521692",
"name": "monitor-group_expansion",
"font_class": "monitor-group_expansion1",
"unicode": "e930",
"unicode_decimal": 59696
},
{
"icon_id": "40521691",
"name": "monitor-group_collapse",
"font_class": "monitor-group_collapse1",
"unicode": "e931",
"unicode_decimal": 59697
},
{
"icon_id": "40520774",
"name": "monitor-group_expansion",
"font_class": "monitor-group_expansion",
"unicode": "e92e",
"unicode_decimal": 59694
},
{
"icon_id": "40520787",
"name": "monitor-group_collapse",
"font_class": "monitor-group_collapse",
"unicode": "e92f",
"unicode_decimal": 59695
},
{
"icon_id": "40519707",
"name": "monitor-list_view",
"font_class": "monitor-list_view",
"unicode": "e92d",
"unicode_decimal": 59693
},
{
"icon_id": "40519711",
"name": "monitor-group_view",
"font_class": "monitor-group_view",
"unicode": "e92c",
"unicode_decimal": 59692
},
{
"icon_id": "40499246",
"name": "ops-topology_view",
"font_class": "ops-topology_view",
"unicode": "e92b",
"unicode_decimal": 59691
},
{
"icon_id": "40411336",
"name": "monitor-host_analysis",
"font_class": "monitor-host_analysis",
"unicode": "e92a",
"unicode_decimal": 59690
},
{
"icon_id": "40372105",
"name": "monitor-add2",
"font_class": "monitor-add2",
"unicode": "e929",
"unicode_decimal": 59689
},
{
"icon_id": "40368097",
"name": "monitor-native",
"font_class": "monitor-native",
"unicode": "e928",
"unicode_decimal": 59688
},
{
"icon_id": "40357355",
"name": "veops-filter2",
"font_class": "veops-filter2",
"unicode": "e927",
"unicode_decimal": 59687
},
{
"icon_id": "40356229",
"name": "ops-cmdb-data_companies-selected",
"font_class": "ops-cmdb-data_companies-selected",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "40343814",
"name": "ops-cmdb-data_companies",
"font_class": "ops-cmdb-data_companies",
"unicode": "e926",
"unicode_decimal": 59686
},
{
"icon_id": "40326916",
"name": "monitor-threshold_value",
"font_class": "monitor-threshold_value",
"unicode": "e921",
"unicode_decimal": 59681
},
{
"icon_id": "40326913",
"name": "monitor-disposition",
"font_class": "monitor-disposition",
"unicode": "e922",
"unicode_decimal": 59682
},
{
"icon_id": "40326911",
"name": "monitor-automatic_discovery",
"font_class": "monitor-automatic_discovery",
"unicode": "e923",
"unicode_decimal": 59683
},
{
"icon_id": "40326907",
"name": "monitor-grouping_list",
"font_class": "monitor-grouping_list",
"unicode": "e924",
"unicode_decimal": 59684
},
{
"icon_id": "40326906",
"name": "monitor-node_list",
"font_class": "monitor-node_list",
"unicode": "e925",
"unicode_decimal": 59685
},
{
"icon_id": "40326667",
"name": "monitor-general_view",
"font_class": "monitor-general_view",
"unicode": "e920",
"unicode_decimal": 59680
},
{
"icon_id": "40326683",
"name": "monitor-network_topology",
"font_class": "monitor-network_topology",
"unicode": "e91b",
"unicode_decimal": 59675
},
{
"icon_id": "40326682",
"name": "monitor-node_management",
"font_class": "monitor-node_management",
"unicode": "e91c",
"unicode_decimal": 59676
},
{
"icon_id": "40326681",
"name": "monitor-alarm_policy",
"font_class": "monitor-alarm_policy",
"unicode": "e91d",
"unicode_decimal": 59677
},
{
"icon_id": "40326680",
"name": "monitor-alarm",
"font_class": "monitor-alarm",
"unicode": "e91e",
"unicode_decimal": 59678
},
{
"icon_id": "40326679",
"name": "monitor-healing",
"font_class": "monitor-healing",
"unicode": "e91f",
"unicode_decimal": 59679
},
{
"icon_id": "40326685",
"name": "monitor-data_acquisition",
"font_class": "monitor-data_acquisition",
"unicode": "e8d4",
"unicode_decimal": 59604
},
{
"icon_id": "40326684",
"name": "monitor-analysis",
"font_class": "monitor-analysis",
"unicode": "e91a",
"unicode_decimal": 59674
},
{
"icon_id": "40308359",
"name": "monitor-index",
"font_class": "monitor-index",
"unicode": "e89b",
"unicode_decimal": 59547
},
{
"icon_id": "40307829",
"name": "monitor-user_defined",
"font_class": "monitor-user_defined",
"unicode": "e867",
"unicode_decimal": 59495
},
{
"icon_id": "40307835",
"name": "monitor-database",
"font_class": "monitor-database",
"unicode": "e861",
"unicode_decimal": 59489
},
{
"icon_id": "40307833",
"name": "monitor-common",
"font_class": "monitor-common",
"unicode": "e865",
"unicode_decimal": 59493
},
{
"icon_id": "40307035",
"name": "veops-edit",
"font_class": "veops-edit",
"unicode": "e866",
"unicode_decimal": 59494
},
{
"icon_id": "40307027",
"name": "veops-empower",
"font_class": "veops-empower",
"unicode": "e863",
"unicode_decimal": 59491
},
{
"icon_id": "40307026",
"name": "veops-share",
"font_class": "veops-share",
"unicode": "e864",
"unicode_decimal": 59492
},
{
"icon_id": "40306997",
"name": "veops-export",
"font_class": "veops-export",
"unicode": "e862",
"unicode_decimal": 59490
},
{
"icon_id": "40262335",
"name": "monitor-ip (1)",
"font_class": "monitor-ip",
"unicode": "e807",
"unicode_decimal": 59399
},
{
"icon_id": "40262337",
"name": "monitor-director",
"font_class": "monitor-director",
"unicode": "e803",
"unicode_decimal": 59395
},
{
"icon_id": "40262336",
"name": "monitor-host",
"font_class": "monitor-host",
"unicode": "e804",
"unicode_decimal": 59396
},
{
"icon_id": "40262216",
"name": "cmdb-log",
"font_class": "a-cmdb-log1",
"unicode": "e802",
"unicode_decimal": 59394
},
{
"icon_id": "40261712",
"name": "monitor-add",
"font_class": "monitor-add",
"unicode": "e7ff",
"unicode_decimal": 59391
},
{
"icon_id": "40248186",
"name": "monitor-down",
"font_class": "monitor-down",
"unicode": "e7fc",
"unicode_decimal": 59388
},
{
"icon_id": "40248184",
"name": "monitor-up",
"font_class": "monitor-up",
"unicode": "e7fd",
"unicode_decimal": 59389
},
{
"icon_id": "40235502",
"name": "itsm-unfold",
"font_class": "itsm-unfold",
"unicode": "e7f9",
"unicode_decimal": 59385
},
{
"icon_id": "40235453",
"name": "itsm-shrink",
"font_class": "itsm-stretch",
"unicode": "e7f8",
"unicode_decimal": 59384
},
{
"icon_id": "40217123",
"name": "monitor-data_comaparison2",
"font_class": "monitor-data_comaparison2",
"unicode": "e7a1",
"unicode_decimal": 59297
},
{
"icon_id": "40217122",
"name": "monitor-data_comaparison1",
"font_class": "monitor-data_comaparison1",
"unicode": "e7f7",
"unicode_decimal": 59383
},
{
"icon_id": "40176936",
"name": "monitor-online",
"font_class": "a-monitor-online1",
"unicode": "e7a0",
"unicode_decimal": 59296
},
{
"icon_id": "40043662",
"name": "ops-setting-application-selected",

Binary file not shown.

View File

@@ -9,7 +9,7 @@
:bodyStyle="{ padding: '24px 12px' }"
:placement="placement"
>
<ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
</CustomDrawer>
</template>
@@ -23,14 +23,6 @@ export default {
type: String,
default: 'right',
},
type: {
type: String,
default: 'resourceSearch'
},
typeId: {
type: Number,
default: null
}
},
data() {
return {

View File

@@ -905,33 +905,6 @@ export const multicolorIconList = [
value: 'caise-application',
label: '应用',
list: [{
value: 'caise-data_center',
label: '数据中心'
}, {
value: 'caise-folder',
label: '文件夹'
}, {
value: 'caise-resource_pool',
label: '资源池'
}, {
value: 'caise-network',
label: '网络'
}, {
value: 'caise-distributed_switch',
label: '分布式交换机'
}, {
value: 'caise-standard_switch',
label: '标准式交换机'
}, {
value: 'caise-host_cluster',
label: '主机集群'
}, {
value: 'caise-storage_cluster',
label: '数据存储集群'
}, {
value: 'caise-data_storage',
label: '数据存储'
}, {
value: 'caise-yilianjie',
label: '已连接'
}, {

View File

@@ -182,8 +182,8 @@ export default {
<tag {...{ props, attrs }}>
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
<span>
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
{isShowDot && !menu.meta.disabled &&
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
{isShowDot &&
<a-popover
overlayClassName="custom-menu-extra-submenu"
placement="rightTop"

View File

@@ -10,10 +10,10 @@ export const regList = () => {
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\\.|http:\/\/www\\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\\.[a-zA-Z0-9]{2,})(\\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\\.\\d{1,2})?$', message: '请输入货币金额' },
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' },
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
]
}

View File

@@ -81,7 +81,7 @@ export default {
if (route.name === 'cmdb') {
const preference = await getPreference()
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
} else {
this.$router.push('/cmdb/dashboard')

View File

@@ -57,9 +57,7 @@ export default {
computed: {
...mapState(['user', 'locale']),
hasBackendPermission() {
const isAdmin = this?.user?.roles?.permissions?.includes('acl_admin')
return isAdmin || this.user?.detailPermissions?.backend?.length
return this.user?.detailPermissions?.backend?.length
},
},
methods: {

View File

@@ -160,7 +160,7 @@ export default {
landline: 'landline',
zipCode: 'zip code',
IDCard: 'ID card',
ip: 'IPv4',
ip: 'IP',
email: 'email',
link: 'link',
monetaryAmount: 'monetary amount',

View File

@@ -160,7 +160,7 @@ export default {
landline: '座机',
zipCode: '邮政编码',
IDCard: '身份证号',
ip: 'IPv4地址',
ip: 'IP地址',
email: '邮箱',
link: '链接',
monetaryAmount: '货币金额',

View File

@@ -2,12 +2,11 @@ import { axios } from '@/utils/request'
const urlPrefix = '/v0.1'
export function searchCI(params, isShowMessage = true) {
export function searchCI(params) {
return axios({
url: urlPrefix + `/ci/s`,
method: 'GET',
params: params,
isShowMessage
params: params
})
}

View File

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

View File

@@ -45,37 +45,11 @@ export function getHttpAttributes(name, params) {
})
}
export function getSnmpAttributes(type, name) {
return axios({
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 getHTTPAccounts(params) {
return axios({
url: `/v0.1/adr/accounts`,
method: 'GET',
params
})
}
export function postHTTPAccounts(data) {
return axios({
url: `/v0.1/adr/accounts`,
method: 'POST',
data
})
export function getSnmpAttributes(name) {
return axios({
url: `/v0.1/adr/snmp/${name}/attributes`,
method: 'GET',
})
}
export function getCITypeDiscovery(type_id) {
@@ -144,65 +118,3 @@ export function deleteAdc(adc_id) {
method: 'DELETE',
})
}
export function getAdcCounter(params) {
return axios({
url: `v0.1/adc/counter`,
method: 'GET',
params
})
}
export function getAdcExecHistories(params) {
return axios({
url: `v0.1/adc/exec/histories`,
method: 'GET',
params
})
}
export function getAdtSyncHistories(adt_id) {
return axios({
url: `/v0.1/adt/${adt_id}/sync/histories`,
method: 'GET',
params: {
page_size: 9999
}
})
}
export function postAdtTest(adt_id) {
return axios({
url: `/v0.1/adt/${adt_id}/test`,
method: 'POST',
})
}
export function getAdtTestResult(exec_id) {
return axios({
url: `/v0.1/adt/test/${exec_id}/result`,
method: 'GET'
})
}
export function getCITypeAttributes(type_id) {
return axios({
url: `/v0.1/adt/ci_types/${type_id}/attributes`,
method: 'GET',
})
}
export function getCITypeRelations(type_id) {
return axios({
url: `/v0.1/adt/ci_types/${type_id}/relations`,
method: 'GET',
})
}
export function postCITypeRelations(type_id, data) {
return axios({
url: `/v0.1/adt/ci_types/${type_id}/relations`,
method: 'POST',
data
})
}

View File

@@ -1,92 +1,89 @@
import { axios } from '@/utils/request'
export function getCIHistory(ciId) {
return axios({
url: `/v0.1/history/ci/${ciId}`,
method: 'GET'
})
}
export function getCIHistoryTable(params) {
return axios({
url: `/v0.1/history/records/attribute`,
method: 'GET',
params: params,
timeout: 30 * 1000
})
}
export function getRelationTable(params) {
return axios({
url: `/v0.1/history/records/relation`,
method: 'GET',
params: params,
timeout: 30 * 1000
})
}
export function getCITypesTable(params) {
return axios({
url: `/v0.1/history/ci_types`,
method: 'GET',
params: params,
timeout: 30 * 1000
})
}
export function getUsers(params) {
return axios({
url: `/v1/acl/users/employee`,
method: 'GET',
params: params
})
}
export function getCiTriggers(params) {
return axios({
url: `/v0.1/history/ci_triggers`,
method: 'GET',
params: params
})
}
export function getCiTriggersByCiId(ci_id, params) {
return axios({
url: `/v0.1/history/ci_triggers/${ci_id}`,
method: 'GET',
params
})
}
export function getCiRelatedTickets(params) {
return axios({
url: `/itsm/v1/process_ticket/get_tickets_by`,
method: 'POST',
data: params,
isShowMessage: false
})
}
export function judgeItsmInstalled() {
return axios({
url: `/itsm/v1/process_ticket/itsm_existed`,
method: 'GET',
isShowMessage: false
})
}
export function getCIsBaseline(params) {
return axios({
url: `/v0.1/ci/baseline`,
method: 'GET',
params
})
}
export function CIBaselineRollback(ciId, params) {
return axios({
url: `/v0.1/ci/${ciId}/baseline/rollback`,
method: 'POST',
data: params
})
}
import { axios } from '@/utils/request'
export function getCIHistory(ciId) {
return axios({
url: `/v0.1/history/ci/${ciId}`,
method: 'GET'
})
}
export function getCIHistoryTable(params) {
return axios({
url: `/v0.1/history/records/attribute`,
method: 'GET',
params: params
})
}
export function getRelationTable(params) {
return axios({
url: `/v0.1/history/records/relation`,
method: 'GET',
params: params
})
}
export function getCITypesTable(params) {
return axios({
url: `/v0.1/history/ci_types`,
method: 'GET',
params: params
})
}
export function getUsers(params) {
return axios({
url: `/v1/acl/users/employee`,
method: 'GET',
params: params
})
}
export function getCiTriggers(params) {
return axios({
url: `/v0.1/history/ci_triggers`,
method: 'GET',
params: params
})
}
export function getCiTriggersByCiId(ci_id, params) {
return axios({
url: `/v0.1/history/ci_triggers/${ci_id}`,
method: 'GET',
params
})
}
export function getCiRelatedTickets(params) {
return axios({
url: `/itsm/v1/process_ticket/get_tickets_by`,
method: 'POST',
data: params,
isShowMessage: false
})
}
export function judgeItsmInstalled() {
return axios({
url: `/itsm/v1/process_ticket/itsm_existed`,
method: 'GET',
isShowMessage: false
})
}
export function getCIsBaseline(params) {
return axios({
url: `/v0.1/ci/baseline`,
method: 'GET',
params
})
}
export function CIBaselineRollback(ciId, params) {
return axios({
url: `/v0.1/ci/${ciId}/baseline/rollback`,
method: 'POST',
data: params
})
}

View File

@@ -1,110 +0,0 @@
import { axios } from '@/utils/request'
const urlPrefix = '/v0.1'
export function getTopoGroups() {
return axios({
url: `${urlPrefix}/topology_views`,
method: 'GET',
})
}
export function getTopoView(_id) {
return axios({
url: `${urlPrefix}/topology_views/${_id}`,
method: 'GET',
})
}
export function postTopoGroup(data) {
return axios({
url: `${urlPrefix}/topology_views/groups`,
method: 'POST',
data: data
})
}
export function putTopoGroupByGId(gid, data) {
return axios({
url: `${urlPrefix}/topology_views/groups/${gid}`,
method: 'PUT',
data: data
})
}
export function putTopoGroupsOrder(data) {
return axios({
url: `${urlPrefix}/topology_views/groups/order`,
method: 'PUT',
data: data
})
}
export function deleteTopoGroup(gid, data) {
return axios({
url: `${urlPrefix}/topology_views/groups/${gid}`,
method: 'DELETE',
data: data
})
}
export function addTopoView(data) {
return axios({
url: `${urlPrefix}/topology_views`,
method: 'POST',
data: data
})
}
export function updateTopoView(_id, data) {
return axios({
url: `${urlPrefix}/topology_views/${_id}`,
method: 'PUT',
data: data
})
}
export function deleteTopoView(_id) {
return axios({
url: `${urlPrefix}/topology_views/${_id}`,
method: 'DELETE',
})
}
export function getRelationsByTypeId(_id) {
return axios({
url: `${urlPrefix}/topology_views/relations/ci_types/${_id}`,
method: 'GET',
})
}
export function previewTopoView(params) {
return axios({
url: `${urlPrefix}/topology_views/preview`,
method: 'POST',
data: params,
})
}
export function showTopoView(_id) {
return axios({
url: `${urlPrefix}/topology_views/${_id}/view`,
method: 'GET',
})
}
export function grantTopologyView(viewId, rid, data) {
return axios({
url: `/v0.1/topology_views/${viewId}/roles/${rid}/grant`,
method: 'POST',
data: data
})
}
export function revokeTopologyView(viewId, rid, data) {
return axios({
url: `/v0.1/topology_views/${viewId}/roles/${rid}/revoke`,
method: 'POST',
data: data
})
}

View File

@@ -1,212 +0,0 @@
<template>
<div class="attr-map-table">
<div class="attr-map-table-left">
<div class="attr-map-table-title">{{ $t('cmdb.ciType.attributes') }}</div>
<vxe-table
ref="attr-xTable-left"
size="mini"
:data="tableData"
:scroll-y="{ enabled: true }"
:min-height="78"
>
<vxe-column field="attr" :title="$t('name')">
<template #default="{ row }">
<div class="attr-select">
<span
v-if="uniqueKey"
:style="{
opacity: uniqueKey === row.name ? 1 : 0
}"
class="attr-select-unique"
>
*
</span>
<a-select
v-model="row.attr"
:placeholder="$t('cmdb.ciType.attrMapTableAttrPlaceholder')"
showSearch
allowClear
:options="ciTypeAttributes"
style="width: 100%; height: 28px; line-height: 28px;"
class="attr-map-table-left-select"
></a-select>
</div>
</template>
</vxe-column>
</vxe-table>
</div>
<div class="attr-map-table-right">
<div class="attr-map-table-title">{{ $t('cmdb.ciType.autoDiscovery') }}</div>
<vxe-table
ref="attr-xTable-right"
size="mini"
show-overflow
show-header-overflow
:data="tableData"
:scroll-y="{ enabled: true }"
:row-config="{ height: 42 }"
:min-height="78"
>
<vxe-column field="name" :title="$t('name')"></vxe-column>
<vxe-column field="type" :title="$t('type')"></vxe-column>
<vxe-column v-if="ruleType !== DISCOVERY_CATEGORY_TYPE.AGENT" field="example" :title="$t('cmdb.components.example')">
<template #default="{row}">
<span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span>
</template>
</vxe-column>
<vxe-column field="desc" :title="$t('desc')"></vxe-column>
</vxe-table>
</div>
<div class="attr-map-table-link">
<div
v-for="item in tableData"
:key="item._X_ROW_KEY"
class="attr-map-table-link-item"
>
<div class="attr-map-table-link-left"></div>
<div class="attr-map-table-link-right"></div>
</div>
</div>
</div>
</template>
<script>
import { DISCOVERY_CATEGORY_TYPE } from '@/modules/cmdb/views/discovery/constants.js'
export default {
name: 'AttrMapTable',
props: {
tableData: {
type: Array,
default: () => [],
},
ciTypeAttributes: {
type: Array,
default: () => [],
},
ruleType: {
type: String,
default: '',
},
uniqueKey: {
type: String,
default: '',
}
},
data() {
return {
DISCOVERY_CATEGORY_TYPE
}
},
methods: {
getTableData() {
const leftTable = this.$refs?.['attr-xTable-left']
const rightTable = this.$refs?.['attr-xTable-right']
const { fullData: leftFullData } = leftTable.getTableData()
const { fullData: rightFullData } = rightTable.getTableData()
const fullData = leftFullData.map((item, index) => {
return {
...(rightFullData?.[index] || {}),
...(item || {})
}
})
return {
fullData
}
},
}
}
</script>
<style lang="less" scoped>
.attr-map-table {
display: flex;
justify-content: space-between;
position: relative;
&-left {
width: 30%;
&-select {
/deep/ .ant-select-selection {
height: 28px;
line-height: 28px;
.ant-select-selection__rendered {
height: 28px;
line-height: 28px;
}
}
}
}
&-right {
width: calc(70% - 60px);
}
&-title {
font-size: 14px;
font-weight: 700;
line-height: 22px;
margin-bottom: 12px;
}
&-link {
position: absolute;
z-index: 10;
bottom: 0;
left: calc(30% - 6px);
width: 66px;
&-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: calc(42px - 12px);
width: 100%;
&:last-child {
margin-bottom: calc(21px - 6px);
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 1px;
background-color: @border-color-base;
z-index: -1;
}
}
&-left {
width: 12px;
height: 12px;
background-color: @primary-color;
border: solid 3px #E2E7FC;
border-radius: 50%;
}
&-right {
width: 2px;
height: 10px;
border-radius: 1px 0px 0px 1px;
background-color: @primary-color;
}
}
.attr-select {
display: flex;
align-items: center;
gap: 10px;
&-unique {
color: #FD4C6A;
}
}
}
</style>

View File

@@ -57,20 +57,6 @@
:addedRids="addedRids"
/>
</template>
<template v-if="cmdbGrantType.includes('TopologyView')">
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
<TopologyViewGrant
:resourceTypeName="resourceTypeName"
:tableData="tableData"
:viewId="CITypeId"
grantType="TopologyView"
@grantDepart="grantDepart"
@grantRole="grantRole"
@getTableData="getTableData"
ref="grantTopologyView"
:addedRids="addedRids"
/>
</template>
<GrantModal ref="grantModal" @handleOk="handleOk" />
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
@@ -86,12 +72,11 @@ import { getResourcePerms } from '@/modules/acl/api/permission'
import GrantModal from './grantModal.vue'
import ReadGrantModal from './readGrantModal'
import RelationViewGrant from './relationViewGrant.vue'
import TopologyViewGrant from './topologyViewGrant.vue'
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
export default {
name: 'GrantComp',
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, TopologyViewGrant, GrantModal, ReadGrantModal },
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
props: {
CITypeId: {
type: Number,
@@ -306,20 +291,6 @@ export default {
})
)
}
if (grantType === 'TopologyView') {
this.tableData.unshift(
...rids.map(({ rid, name }) => {
return {
rid,
name,
read: false,
update: false,
delete: false,
grant: false,
}
})
)
}
this.addedRids = rids
this.$nextTick(() => {
setTimeout(() => {

View File

@@ -1,102 +0,0 @@
<template>
<div class="topology-view-grant">
<vxe-table
ref="xTable"
size="mini"
stripe
class="ops-stripe-table"
:data="tableData"
:max-height="`${tableHeight}px`"
:scroll-y="{enabled: true}"
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
>
<vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}">
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
</template>
</vxe-column>
</vxe-table>
<a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space>
</div>
</template>
<script>
import { permMap } from './constants.js'
import { grantTopologyView, revokeTopologyView } from '@/modules/cmdb/api/topology.js'
import { getCurrentRowStyle } from './utils'
export default {
name: 'TopologyViewGrant',
inject: ['loading', 'isModal'],
props: {
viewId: {
type: Number,
default: null,
},
resourceTypeName: {
type: String,
default: '',
},
tableData: {
type: Array,
default: () => [],
},
grantType: {
type: String,
default: 'relation_view',
},
addedRids: {
type: Array,
default: () => [],
},
},
data() {
return {
columns: ['read', 'update', 'delete', 'grant'],
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
tableHeight() {
if (this.isModal) {
return (this.windowHeight - 104) / 2
}
return (this.windowHeight - 104) / 2 - 116
},
permMap() {
return permMap()
}
},
methods: {
getCurrentRowStyle,
grantDepart() {
this.$emit('grantDepart', this.grantType)
},
grantRole() {
this.$emit('grantRole', this.grantType)
},
handleChange(e, col, row) {
if (e.target.checked) {
grantTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
this.$emit('getTableData')
})
} else {
revokeTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
this.$emit('getTableData')
})
}
},
},
}
</script>
<style lang="less" scoped>
.topology-view-grant {
padding: 10px 0;
}
</style>

View File

@@ -1,39 +0,0 @@
<template>
<vxe-table
size="mini"
stripe
class="ops-stripe-table"
show-overflow
keep-source
ref="xTable"
resizable
height="100%"
:data="tableData"
:scroll-y="{enabled: true}"
>
<vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}">
<span v-if="row.type === 'object'">{{ row.example ? JSON.stringify(row.example) : '' }}</span>
<span v-else>{{ row.example }}</span>
</template>
</vxe-column>
<vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-table>
</template>
<script>
export default {
name: 'ADPreviewTable',
props: {
tableData: {
type: Array,
default: () => [],
},
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,300 +0,0 @@
<template>
<div class="http-ad-category">
<div class="http-ad-category-preview" v-if="currentCate && isPreviewDetail">
<div class="category-side">
<div
v-for="(category, categoryIndex) in categories"
:key="category.category"
class="category-side-item"
>
<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, 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>
</div>
<div class="category-table">
<ADPreviewTable
:tableData="tableData"
/>
</div>
</div>
<template v-else>
<a-input-search
class="category-search"
:placeholder="$t('cmdb.ad.httpSearchPlaceHolder')"
@search="onSearchHttp"
/>
<div class="category-main">
<div
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, 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>
</div>
<div class="corporate-tip">
{{ $t('cmdb.ad.corporateTip') }} <a href="mailto:bd@veops.cn">bd@veops.cn</a>
</div>
</template>
</div>
</template>
<script>
import _ from 'lodash'
import ADPreviewTable from './adPreviewTable.vue'
export default {
name: 'HttpADCategory',
components: {
ADPreviewTable
},
props: {
categories: {
type: Array,
default: () => {},
},
currentCate: {
type: String,
default: ''
},
tableData: {
type: Array,
default: () => [],
},
ruleType: {
type: String,
default: 'http',
},
},
data() {
return {
searchValue: '',
isPreviewDetail: false,
}
},
computed: {
filterCategories() {
const categories = _.cloneDeep(this.categories)
const filterCategories = categories.filter((category) => {
category.items = category.items.filter((item) => {
return item?.indexOf(this.searchValue) !== -1
})
return category?.items?.length > 0
})
return filterCategories
}
},
methods: {
onSearchHttp(v) {
this.searchValue = v
},
clickCategory(item) {
this.$emit('clickCategory', item)
this.isPreviewDetail = true
},
clickBack() {
this.isPreviewDetail = false
}
}
}
</script>
<style lang="less" scoped>
.http-ad-category {
&-preview {
display: flex;
width: 100%;
height: calc(100vh - 156px);
justify-content: space-between;
}
.category-side {
flex-shrink: 0;
width: 150px;
height: 100%;
border-right: solid 1px @border-color-base;
padding-right: 10px;
&-item {
&:not(:last-child) {
margin-bottom: 24px;
}
.category-side-title {
font-size: 12px;
font-weight: 400;
color: @text-color_4;
}
.category-side-children {
margin-top: 5px;
&-item {
padding: 7px 10px;
background-color: @layout-content-background;
border-radius: @border-radius-base;
color: @text-color_2;
font-size: 12px;
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;
color: @layout-header-font-selected-color;
}
&_active {
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;
}
}
}
}
}
.category-table {
width: calc(100% - 150px - 10px - 16px);
flex-shrink: 0;
height: 100%;
}
.category-search {
width: 254px;
}
.category-main {
.category-item {
margin-top: 24px;
.category-title {
font-size: 14px;
font-weight: 700;
}
.category-children {
margin-top: 8px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 19px;
&-item {
padding: 18px 19px;
background-color: @layout-content-background;
border-radius: @border-radius-base;
color: @text-color_2;
font-size: 14px;
font-weight: 400;
cursor: pointer;
position: relative;
min-width: 100px;
text-align: center;
&:hover {
background-color: @layout-sidebar-selected-color;
color: @layout-header-font-selected-color;
}
&_active {
background-color: @layout-sidebar-selected-color;
color: @layout-header-font-selected-color;
}
}
}
}
}
.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;
}
}
}
</style>

View File

@@ -1,48 +1,68 @@
<template>
<div class="http-snmp-ad">
<HttpADCategory
v-if="!isEdit && isCloud"
:categories="categories"
:currentCate="currentCate"
:tableData="tableData"
:ruleType="ruleType"
@clickCategory="setCurrentCate"
/>
<template v-else>
<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
v-if="isEdit"
ref="attrMapTable"
:ruleType="ruleType"
:tableData="tableData"
:ciTypeAttributes="ciTypeAttributes"
:uniqueKey="uniqueKey"
/>
<ADPreviewTable
v-else
:tableData="tableData"
/>
</template>
<div>
<a-select v-if="ruleType === 'http'" :style="{ marginBottom: '10px' }" v-model="currentCate">
<a-select-option v-for="cate in categories" :key="cate" :value="cate">{{ cate }}</a-select-option>
</a-select>
<vxe-table
size="mini"
stripe
class="ops-stripe-table"
show-overflow
keep-source
ref="xTable"
resizable
:data="tableData"
:edit-config="isEdit ? { trigger: 'click', mode: 'cell' } : {}"
>
<template v-if="isEdit">
<vxe-colgroup :title="$t('cmdb.ciType.autoDiscovery')">
<vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}">
<span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span>
</template>
</vxe-column>
<vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-colgroup>
<vxe-colgroup :title="$t('cmdb.ciType.attributes')">
<vxe-column field="attr" :title="$t('name')" :edit-render="{}">
<template #default="{row}">
{{ row.attr }}
</template>
<template #edit="{ row }">
<vxe-select
filterable
clearable
v-model="row.attr"
type="text"
:options="ciTypeAttributes"
transfer
></vxe-select>
</template>
</vxe-column>
</vxe-colgroup>
</template>
<template v-else>
<vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}">
<span v-if="row.type === 'object'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span>
</template>
</vxe-column>
<vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</template>
</vxe-table>
</div>
</template>
<script>
import _ from 'lodash'
import { getHttpCategories, getHttpAttributes, getSnmpAttributes, getHttpAttrMapping } from '../../api/discovery'
import { DISCOVERY_CATEGORY_TYPE } from '@/modules/cmdb/views/discovery/constants.js'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
import ADPreviewTable from './adPreviewTable.vue'
import HttpADCategory from './httpADCategory.vue'
import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery'
export default {
name: 'HttpSnmpAD',
components: {
AttrMapTable,
ADPreviewTable,
HttpADCategory
},
props: {
ruleName: {
type: String,
@@ -68,22 +88,12 @@ export default {
type: Number,
default: 0,
},
uniqueKey: {
type: String,
default: '',
},
currentAdt: {
type: Object,
default: () => {},
}
},
data() {
return {
categories: [],
categoriesSelect: [],
currentCate: '',
tableData: [],
httpAttrMap: {}
}
},
computed: {
@@ -97,20 +107,21 @@ export default {
腾讯云: { name: 'tencentcloud' },
华为云: { name: 'huaweicloud' },
AWS: { name: 'aws' },
VCenter: { name: 'vcenter' },
KVM: { name: 'kvm' },
}
},
isCloud() {
return [DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD].includes(this.ruleType)
}
},
watch: {
currentCate: {
immediate: true,
handler(newVal) {
if (newVal) {
this.getHttpAttr(newVal)
getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => {
if (this.isEdit) {
this.formatTableData(res)
} else {
this.tableData = res
}
})
}
},
},
@@ -120,8 +131,8 @@ export default {
this.currentCate = ''
this.$nextTick(() => {
const { ruleType, ruleName } = newVal
if ([DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.COMPONENT].includes(ruleType) && ruleName) {
getSnmpAttributes(ruleType, ruleName).then((res) => {
if (ruleType === 'snmp' && ruleName) {
getSnmpAttributes(ruleName).then((res) => {
if (this.isEdit) {
this.formatTableData(res)
} else {
@@ -129,19 +140,11 @@ export default {
}
})
}
if (this.isCloud && ruleName) {
getHttpCategories(this.ruleName).then((res) => {
if (ruleType === 'http' && ruleName) {
getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => {
this.categories = res
const categoriesSelect = []
res.forEach((category) => {
if (category?.items?.length) {
categoriesSelect.push(...category.items)
}
})
this.categoriesSelect = categoriesSelect
if (this.isEdit && categoriesSelect?.length) {
this.currentCate = this?.currentAdt?.extra_option?.category || categoriesSelect[0]
if (res && res.length) {
this.currentCate = res[0]
}
})
}
@@ -159,61 +162,31 @@ export default {
},
formatTableData(list) {
const _findADT = this.adCITypeList.find((item) => Number(item.adr_id) === Number(this.currentTab))
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) {
this.tableData = (list || []).map((item) => {
if (_findADT.attributes) {
return {
...item,
attr: _findADT.attributes[`${item.name}`],
}
} else {
const _find = this.ciTypeAttributes.find((ele) => ele.name === item.name)
if (_find) {
item.attr = _find.name
return {
...item,
attr: _find.name,
}
}
return item
}
return item
})
},
getTableData() {
const $table = this.$refs.attrMapTable
const $table = this.$refs.xTable
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 || {}
}
},
}
</script>
<style>
.http-snmp-ad {
height: 100%;
}
</style>
<style></style>

View File

@@ -1,147 +0,0 @@
<template>
<div class="node-setting-wrap">
<ops-table
:data="nodes"
size="mini"
show-header-overflow
:row-config="{ height: 42 }"
border
:min-height="78"
>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingIp')">
<template #default="{ row }">
<a-input v-model="row.ip"></a-input>
</template>
</vxe-column>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingCommunity')">
<template #default="{ row }">
<a-input v-model="row.community"></a-input>
</template>
</vxe-column>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingVersion')">
<template #default="{ row }">
<a-select
v-model="row.version"
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
allowClear
class="node-setting-select"
>
<a-select-option value="1">
v1
</a-select-option>
<a-select-option value="2c">
v2c
</a-select-option>
</a-select>
</template>
</vxe-column>
<vxe-column wdith="170">
<template #default="{ row }">
<div class="action">
<a @click="() => copyNode(row.id)">
<a-icon type="copy" />
</a>
<a @click="() => removeNode(row.id, 1)">
<a-icon type="minus-circle" />
</a>
<a @click="addNode">
<a-icon type="plus-circle" />
</a>
</div>
</template>
</vxe-column>
</ops-table>
</div>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
export default {
name: 'MonitorNodeSetting',
props: {
initNodes: {
type: Array,
default: () => [],
},
form: {
type: Object,
default: null,
},
},
data() {
return {
nodes: [],
}
},
methods: {
initNodesFunc() {
this.nodes = _.cloneDeep(this.initNodes)
},
addNode() {
const newNode = {
id: uuidv4(),
ip: '',
community: 'public',
version: '',
}
this.nodes.push(newNode)
},
removeNode(removeId, minLength) {
if (this.nodes.length <= minLength) {
this.$message.error('不可再删除!')
return
}
const _idx = this.nodes.findIndex((item) => item.id === removeId)
if (_idx > -1) {
this.nodes.splice(_idx, 1)
}
},
copyNode(id) {
const copyNode = this.nodes.find((item) => item.id === id)
if (copyNode) {
const newNode = {
...copyNode,
id: uuidv4(),
}
this.nodes.push(newNode)
}
},
getNodeValue() {
const nodes = this.nodes.map((node) => {
return _.pick(node, ['ip', 'community', 'version'])
})
return nodes
},
},
}
</script>
<style lang="less" scoped>
.node-setting-wrap {
margin-left: 17px;
width: 600px;
.ant-row {
/deep/ .ant-input-clear-icon {
color: rgba(0,0,0,.25);
&:hover {
color: rgba(0, 0, 0, 0.45);
}
}
}
.node-setting-select {
width: 150px;
}
}
.action {
height: 36px;
display: flex;
align-items: center;
gap: 12px;
}
</style>

View File

@@ -1,319 +1,317 @@
<template>
<div>
<div id="search-form-bar" class="search-form-bar">
<div :style="{ display: 'inline-flex', alignItems: 'center' }">
<a-space>
<treeselect
v-if="type === 'resourceSearch'"
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
width: '200px',
marginRight: '10px',
'--custom-height': '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '16px',
}"
v-model="currenCiType"
:multiple="true"
:clearable="true"
searchable
:options="ciTypeGroup"
:limit="1"
:limitText="(count) => `+ ${count}`"
value-consists-of="LEAF_PRIORITY"
:placeholder="$t('cmdb.ciType.ciType')"
@close="closeCiTypeGroup"
@open="openCiTypeGroup"
@input="inputCiTypeGroup"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || $t('other'),
title: node.alias || node.name || $t('other'),
children: node.ci_types,
}
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
<a-input
v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '200px' }"
:placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh"
>
<a-icon
type="search"
slot="suffix"
:style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
@click="emitRefresh"
/>
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
<template slot="title">
{{ $t('cmdb.components.ciSearchTips') }}
</template>
<a><a-icon type="question-circle"/></a>
</a-tooltip>
</a-input>
<a-tooltip :title="$t('reset')">
<a-button @click="reset">{{ $t('reset') }}</a-button>
</a-tooltip>
<FilterComp
ref="filterComp"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
@setExpFromFilter="setExpFromFilter"
:expression="expression"
placement="bottomLeft"
>
<div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" />
{{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" :style="{ color: '#d9d9d9' }" />
</div>
</FilterComp>
<a-input
v-if="isShowExpression"
v-model="expression"
v-show="!selectedRowKeys.length"
@focus="
() => {
isFocusExpression = true
}
"
@blur="
() => {
isFocusExpression = false
}
"
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
:style="{ width }"
:placeholder="placeholder"
@keyup.enter="emitRefresh"
>
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
</a-input>
<slot></slot>
</a-space>
</div>
<a-space>
<slot name="extraContent"></slot>
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a
@click="
() => {
$refs.metadataDrawer.open(typeId)
}
"
><a-icon
v-if="type === 'relationView'"
type="question-circle"
/></a>
</a-tooltip>
</a-space>
</div>
<MetadataDrawer ref="metadataDrawer" />
</div>
</template>
<script>
import _ from 'lodash'
import Treeselect from '@riophae/vue-treeselect'
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
import FilterComp from '@/components/CMDBFilterComp'
import { getCITypeGroups } from '../../api/ciTypeGroup'
export default {
name: 'SearchForm',
components: { MetadataDrawer, FilterComp, Treeselect },
props: {
preferenceAttrList: {
type: Array,
required: true,
},
isShowExpression: {
type: Boolean,
default: true,
},
typeId: {
type: Number,
default: null,
},
type: {
type: String,
default: '',
},
selectedRowKeys: {
type: Array,
default: () => [],
},
},
data() {
return {
// Advanced Search Expand/Close
advanced: false,
queryParam: {},
isFocusExpression: false,
expression: '',
fuzzySearch: '',
currenCiType: [],
ciTypeGroup: [],
lastCiType: [],
}
},
computed: {
placeholder() {
return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
},
width() {
return '200px'
},
canSearchPreferenceAttrList() {
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
},
},
watch: {
'$route.path': function(newValue, oldValue) {
this.queryParam = {}
this.expression = ''
this.fuzzySearch = ''
},
},
inject: {
setPreferenceSearchCurrent: {
from: 'setPreferenceSearchCurrent',
default: null,
},
},
mounted() {
if (this.type === 'resourceSearch') {
this.getCITypeGroups()
}
if (this.typeId) {
this.currenCiType = [this.typeId]
}
},
methods: {
// toggleAdvanced() {
// this.advanced = !this.advanced
// },
getCITypeGroups() {
getCITypeGroups({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `parent_${item.id || -1}`
return { ..._.cloneDeep(item) }
})
})
},
reset() {
this.queryParam = {}
this.expression = ''
this.fuzzySearch = ''
if (this.type !== 'resourceView') {
this.currenCiType = []
}
this.emitRefresh()
},
setExpFromFilter(filterExp) {
const regSort = /(?<=sort=).+/g
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
let expression = ''
if (filterExp) {
expression = `q=${filterExp}`
}
if (expSort) {
expression += `&sort=${expSort}`
}
this.expression = expression
this.emitRefresh()
},
handleSubmit() {
this.$refs.filterComp.handleSubmit()
},
openCiTypeGroup() {
this.lastCiType = _.cloneDeep(this.currenCiType)
},
closeCiTypeGroup(value) {
if (!_.isEqual(value, this.lastCiType)) {
this.$emit('updateAllAttributesList', value)
}
},
inputCiTypeGroup(value) {
if (!value || !value.length) {
this.$emit('updateAllAttributesList', value)
}
},
emitRefresh() {
if (this.setPreferenceSearchCurrent) {
this.setPreferenceSearchCurrent(null)
}
this.$nextTick(() => {
this.$emit('refresh', true)
})
},
handleCopyExpression() {
this.$emit('copyExpression')
},
},
}
</script>
<style lang="less">
@import '../../views/index.less';
.ci-searchform-expression {
> input {
border-bottom: 2px solid #d9d9d9;
border-top: none;
border-left: none;
border-right: none;
&:hover,
&:focus {
border-bottom: 2px solid @primary-color;
}
&:focus {
box-shadow: 0 2px 2px -2px #1f78d133;
}
}
.ant-input-suffix {
color: #d9d9d9;
cursor: pointer;
}
}
.ci-searchform-expression-has-value .ant-input-suffix {
color: @func-color_3;
}
.cmdb-search-form {
.ant-form-item-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
<style lang="less" scoped>
.search-form-bar {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
.search-form-bar-filter {
.ops_display_wrapper(transparent);
.search-form-bar-filter-icon {
color: @primary-color;
font-size: 12px;
}
}
}
</style>
<template>
<div>
<div id="search-form-bar" class="search-form-bar">
<div :style="{ display: 'inline-flex', alignItems: 'center' }">
<a-space>
<treeselect
v-if="type === 'resourceSearch'"
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
width: '200px',
marginRight: '10px',
'--custom-height': '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '16px',
}"
v-model="currenCiType"
:multiple="true"
:clearable="true"
searchable
:options="ciTypeGroup"
:limit="1"
:limitText="(count) => `+ ${count}`"
value-consists-of="LEAF_PRIORITY"
:placeholder="$t('cmdb.ciType.ciType')"
@close="closeCiTypeGroup"
@open="openCiTypeGroup"
@input="inputCiTypeGroup"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || $t('other'),
title: node.alias || node.name || $t('other'),
children: node.ci_types,
}
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
<a-input
v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '200px' }"
:placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh"
>
<a-icon
type="search"
slot="suffix"
:style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
@click="emitRefresh"
/>
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
<template slot="title">
{{ $t('cmdb.components.ciSearchTips') }}
</template>
<a><a-icon type="question-circle"/></a>
</a-tooltip>
</a-input>
<a-tooltip :title="$t('reset')">
<a-button @click="reset">{{ $t('reset') }}</a-button>
</a-tooltip>
<FilterComp
ref="filterComp"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
@setExpFromFilter="setExpFromFilter"
:expression="expression"
placement="bottomLeft"
>
<div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" />
{{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" :style="{ color: '#d9d9d9' }" />
</div>
</FilterComp>
<a-input
v-if="isShowExpression"
v-model="expression"
v-show="!selectedRowKeys.length"
@focus="
() => {
isFocusExpression = true
}
"
@blur="
() => {
isFocusExpression = false
}
"
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
:style="{ width }"
:placeholder="placeholder"
@keyup.enter="emitRefresh"
>
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
</a-input>
<slot></slot>
</a-space>
</div>
<a-space>
<slot name="extraContent"></slot>
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a
@click="
() => {
$refs.metadataDrawer.open(typeId)
}
"
><a-icon
v-if="type === 'relationView'"
type="question-circle"
/></a>
</a-tooltip>
</a-space>
</div>
<MetadataDrawer ref="metadataDrawer" />
</div>
</template>
<script>
import _ from 'lodash'
import Treeselect from '@riophae/vue-treeselect'
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
import FilterComp from '@/components/CMDBFilterComp'
import { getCITypeGroups } from '../../api/ciTypeGroup'
export default {
name: 'SearchForm',
components: { MetadataDrawer, FilterComp, Treeselect },
props: {
preferenceAttrList: {
type: Array,
required: true,
},
isShowExpression: {
type: Boolean,
default: true,
},
typeId: {
type: Number,
default: null,
},
type: {
type: String,
default: '',
},
selectedRowKeys: {
type: Array,
default: () => [],
},
},
data() {
return {
// Advanced Search Expand/Close
advanced: false,
queryParam: {},
isFocusExpression: false,
expression: '',
fuzzySearch: '',
currenCiType: [],
ciTypeGroup: [],
lastCiType: [],
}
},
computed: {
placeholder() {
return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
},
width() {
return '200px'
},
canSearchPreferenceAttrList() {
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
},
},
watch: {
'$route.path': function(newValue, oldValue) {
this.queryParam = {}
this.expression = ''
this.fuzzySearch = ''
},
},
inject: {
setPreferenceSearchCurrent: {
from: 'setPreferenceSearchCurrent',
default: null,
},
},
mounted() {
if (this.type === 'resourceSearch') {
this.getCITypeGroups()
}
if (this.typeId) {
this.currenCiType = [this.typeId]
}
},
methods: {
// toggleAdvanced() {
// this.advanced = !this.advanced
// },
getCITypeGroups() {
getCITypeGroups({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `parent_${item.id || -1}`
return { ..._.cloneDeep(item) }
})
})
},
reset() {
this.queryParam = {}
this.expression = ''
this.fuzzySearch = ''
this.currenCiType = []
this.emitRefresh()
},
setExpFromFilter(filterExp) {
const regSort = /(?<=sort=).+/g
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
let expression = ''
if (filterExp) {
expression = `q=${filterExp}`
}
if (expSort) {
expression += `&sort=${expSort}`
}
this.expression = expression
this.emitRefresh()
},
handleSubmit() {
this.$refs.filterComp.handleSubmit()
},
openCiTypeGroup() {
this.lastCiType = _.cloneDeep(this.currenCiType)
},
closeCiTypeGroup(value) {
if (!_.isEqual(value, this.lastCiType)) {
this.$emit('updateAllAttributesList', value)
}
},
inputCiTypeGroup(value) {
if (!value || !value.length) {
this.$emit('updateAllAttributesList', value)
}
},
emitRefresh() {
if (this.setPreferenceSearchCurrent) {
this.setPreferenceSearchCurrent(null)
}
this.$nextTick(() => {
this.$emit('refresh', true)
})
},
handleCopyExpression() {
this.$emit('copyExpression')
},
},
}
</script>
<style lang="less">
@import '../../views/index.less';
.ci-searchform-expression {
> input {
border-bottom: 2px solid #d9d9d9;
border-top: none;
border-left: none;
border-right: none;
&:hover,
&:focus {
border-bottom: 2px solid @primary-color;
}
&:focus {
box-shadow: 0 2px 2px -2px #1f78d133;
}
}
.ant-input-suffix {
color: #d9d9d9;
cursor: pointer;
}
}
.ci-searchform-expression-has-value .ant-input-suffix {
color: @func-color_3;
}
.cmdb-search-form {
.ant-form-item-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
<style lang="less" scoped>
.search-form-bar {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
.search-form-bar-filter {
.ops_display_wrapper(transparent);
.search-form-bar-filter-icon {
color: @primary-color;
font-size: 12px;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,12 +16,6 @@ const genCmdbRoutes = async () => {
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard', keepAlive: false },
component: () => import('../views/dashboard/index_v2.vue')
},
{
path: '/cmdb/topoviews',
name: 'cmdb_topology_views',
meta: { title: 'cmdb.menu.topologyView', appName: 'cmdb', icon: 'ops-topology_view', selectedIcon: 'ops-topology_view', keepAlive: false },
component: () => import('../views/topology_view/index.vue')
},
{
path: '/cmdb/disabled1',
name: 'cmdb_disabled1',
@@ -149,30 +143,21 @@ const genCmdbRoutes = async () => {
}
// Dynamically add subscription items and business relationships
const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
const resourceViewsIndex = routes.children.findIndex(item => item.name === 'cmdb_resource_views')
preference.group_types.forEach(group => {
if (preference.group_types.length > 1) {
routes.children[resourceViewsIndex].children.push({
path: `/cmdb/instances/types/group${group.id}`,
name: `cmdb_instances_group_${group.id}`,
meta: { title: group.name || 'other', disabled: true, style: 'margin-left: 12px' },
})
}
group.ci_types.forEach(item => {
routes.children[resourceViewsIndex].children.push({
path: `/cmdb/instances/types/${item.id}`,
component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`,
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
})
preference.forEach(item => {
routes.children[2].children.push({
path: `/cmdb/instances/types/${item.id}`,
component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`,
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
})
})
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.type_ids.some(item => item === Number(lastTypeId))) {
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
} else if (routes.children[resourceViewsIndex]?.children?.length > 0) {
routes.redirect = routes.children[resourceViewsIndex].children.find(item => !item.hidden && !item.meta.disabled)?.path
} else if (routes.children[2]?.children?.length > 0) {
routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
} else {
routes.redirect = '/cmdb/dashboard'
}
@@ -184,7 +169,7 @@ const genCmdbRoutes = async () => {
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
}
})
routes.children.splice(resourceViewsIndex, 0, ...relationViews)
routes.children.splice(2, 0, ...relationViews)
return routes
}

View File

@@ -101,14 +101,10 @@
:cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }"
class="ops-unstripe-table checkbox-hover-table"
class="ops-unstripe-table"
:custom-config="{ storage: true }"
>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''">
<template #default="{row}">
{{ getRowSeq(row) }}
</template>
</vxe-column>
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
<vxe-table-column
v-for="(col, index) in columns"
:key="`${col.field}_${index}`"
@@ -140,7 +136,6 @@
:mode="col.is_list ? 'multiple' : 'default'"
class="ci-table-edit-select"
allowClear
showSearch
>
<a-select-option
:value="choice[0]"
@@ -162,7 +157,7 @@
:type="choice[1].icon.name"
/>
</template>
<span>{{ choice[0] }}</span>
{{ choice[0] }}
</span>
</a-select-option>
</a-select>
@@ -172,20 +167,16 @@
#default="{ row }"
>
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
<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>
<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
>
<PasswordField
v-else-if="col.is_password && row[col.field]"
:ci_id="row._id"
@@ -200,7 +191,6 @@
borderRadius: '4px',
padding: '1px 5px',
margin: '2px',
verticalAlign: 'bottom',
...getChoiceValueStyle(col, value),
display: 'inline-flex',
alignItems: 'center',
@@ -212,7 +202,7 @@
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/>
<ops-icon
v-else-if="getChoiceValueIcon(col, value).name"
v-else
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
:type="getChoiceValueIcon(col, value).name"
/>{{ value }}
@@ -224,7 +214,6 @@
borderRadius: '4px',
padding: '1px 5px',
margin: '2px 0',
verticalAlign: 'bottom',
...getChoiceValueStyle(col, row[col.field]),
display: 'inline-flex',
alignItems: 'center',
@@ -236,7 +225,7 @@
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/>
<ops-icon
v-else-if="getChoiceValueIcon(col, row[col.field]).name"
v-else
:style="{ color: getChoiceValueIcon(col, row[col.field]).color, marginRight: '5px' }"
:type="getChoiceValueIcon(col, row[col.field]).name"
/>
@@ -1055,9 +1044,6 @@ export default {
this.visible = false
}
},
getRowSeq(row) {
return this.$refs.xTable.getVxetableRef().getRowSeq(row)
}
},
}
</script>
@@ -1075,33 +1061,4 @@ export default {
overflow: auto;
margin-bottom: -24px;
}
.checkbox-hover-table {
/deep/ .vxe-table--body-wrapper {
.vxe-checkbox--label {
display: inline;
padding-left: 0px !important;
color: #bfbfbf;
}
.vxe-icon-checkbox-unchecked {
display: none;
}
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
display: none;
}
.vxe-cell--checkbox {
&:hover {
.vxe-icon-checkbox-unchecked {
display: inline;
}
.vxe-checkbox--label {
display: none;
}
}
}
}
}
</style>

View File

@@ -163,12 +163,6 @@ export default {
width: 110,
help: this.$t('cmdb.ci.tips10'),
},
{
field: 'is_dynamic',
title: this.$t('cmdb.ciType.isDynamic'),
width: 110,
help: this.$t('cmdb.ciType.dynamicTips'),
},
]
},
},

View File

@@ -105,7 +105,7 @@ export default {
default: true,
},
attrList: {
type: Function,
type: Array,
default: () => [],
}
},

View File

@@ -132,10 +132,6 @@ export default {
type: Object,
default: () => {},
},
initQueryLoading: {
type: Boolean,
default: false,
}
},
data() {
return {
@@ -149,208 +145,11 @@ export default {
firstCIJsonAttr: {},
secondCIJsonAttr: {},
canEdit: {},
topoData: {
nodes: {},
edges: []
}
}
},
computed: {
exsited_ci() {
const _exsited_ci = [this.ciId]
this.parentCITypes.forEach((parent) => {
if (this.firstCIs[parent.name]) {
this.firstCIs[parent.name].forEach((parentCi) => {
_exsited_ci.push(parentCi._id)
})
}
})
this.childCITypes.forEach((child) => {
if (this.secondCIs[child.name]) {
this.secondCIs[child.name].forEach((childCi) => {
_exsited_ci.push(childCi._id)
})
}
})
return _exsited_ci
},
},
inject: {
attrList: { from: 'attrList' },
attributes: { from: 'attributes' },
ci_types: { from: 'ci_types' },
relationViewRefreshNumber: {
from: 'relationViewRefreshNumber',
default: () => null,
},
},
mounted() {
if (!this.initQueryLoading) {
this.init(true)
}
},
methods: {
async init(isFirst) {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
const ci_types_list = this.ci_types()
this.handleTopoData()
if (
isFirst &&
this.$refs.ciDetailRelationTopo &&
ci_types_list.length
) {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
}
})
},
async getFirstCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=1&count=10000`)
.then((res) => {
const firstCIs = {}
res.result.forEach((item) => {
this.firstCIJsonAttr[item._type].forEach((attr) => {
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
})
if (item.ci_type in firstCIs) {
firstCIs[item.ci_type].push(item)
} else {
firstCIs[item.ci_type] = [item]
}
})
this.firstCIs = firstCIs
})
.catch((e) => {})
},
async getSecondCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=0&count=10000`)
.then((res) => {
const secondCIs = {}
res.result.forEach((item) => {
this.secondCIJsonAttr[item._type].forEach((attr) => {
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
})
if (item.ci_type in secondCIs) {
secondCIs[item.ci_type].push(item)
} else {
secondCIs[item.ci_type] = [item]
}
})
this.secondCIs = secondCIs
})
.catch((e) => {})
},
async getParentCITypes() {
const res = await getCITypeParent(this.typeId)
this.parentCITypes = res.parents
for (let i = 0; i < res.parents.length; i++) {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.parents[i].id]: p_res.result,
}
})
}
const firstCIColumns = {}
const firstCIJsonAttr = {}
res.parents.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
firstCIJsonAttr[item.id] = jsonAttr
firstCIColumns[item.id] = columns
firstCIColumns[item.id].push({
key: 'p_operation',
field: 'operation',
title: this.$t('operation'),
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.firstCIColumns = firstCIColumns
this.firstCIJsonAttr = firstCIJsonAttr
},
async getChildCITypes() {
const res = await getCITypeChildren(this.typeId)
this.childCITypes = res.children
for (let i = 0; i < res.children.length; i++) {
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.children[i].id]: c_res.result,
}
})
}
const secondCIColumns = {}
const secondCIJsonAttr = {}
res.children.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
secondCIJsonAttr[item.id] = jsonAttr
secondCIColumns[item.id] = columns
secondCIColumns[item.id].push({
key: 'c_operation',
field: 'operation',
title: this.$t('operation'),
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.secondCIColumns = secondCIColumns
this.secondCIJsonAttr = secondCIJsonAttr
},
reload() {
this.init()
},
deleteRelation(first_ci_id, second_ci_id) {
deleteCIRelationView(first_ci_id, second_ci_id).then((res) => {
this.init()
if (this.relationViewRefreshNumber) {
this.relationViewRefreshNumber()
}
})
},
handleChangeActiveKey(e) {
if (e.target.value === '1') {
this.$nextTick(() => {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
})
}
},
handleTopoData() {
topoData() {
const ci_types_list = this.ci_types()
if (!ci_types_list?.length) {
this.$set(this, 'topoData', {
nodes: {},
edges: []
})
return
}
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const unique_id = _findCiType.show_id || this.attributes().unique_id
const unique_name = _findCiType.show_name || this.attributes().unique
@@ -467,11 +266,185 @@ export default {
})
}
})
this.$set(this, 'topoData', {
nodes,
edges
return { nodes, edges }
},
exsited_ci() {
const _exsited_ci = [this.typeId]
this.parentCITypes.forEach((parent) => {
if (this.firstCIs[parent.name]) {
this.firstCIs[parent.name].forEach((parentCi) => {
_exsited_ci.push(parentCi._id)
})
}
})
}
this.childCITypes.forEach((child) => {
if (this.secondCIs[child.name]) {
this.secondCIs[child.name].forEach((childCi) => {
_exsited_ci.push(childCi._id)
})
}
})
return _exsited_ci
},
},
inject: {
attrList: { from: 'attrList' },
attributes: { from: 'attributes' },
ci_types: { from: 'ci_types' },
relationViewRefreshNumber: {
from: 'relationViewRefreshNumber',
default: () => null,
},
},
mounted() {
this.init(true)
},
methods: {
async init(isFirst) {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
if (isFirst && this.$refs.ciDetailRelationTopo) {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
}
})
},
async getFirstCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&&level=1&&reverse=1&&count=10000`)
.then((res) => {
const firstCIs = {}
res.result.forEach((item) => {
this.firstCIJsonAttr[item._type].forEach((attr) => {
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
})
if (item.ci_type in firstCIs) {
firstCIs[item.ci_type].push(item)
} else {
firstCIs[item.ci_type] = [item]
}
})
this.firstCIs = firstCIs
})
.catch((e) => {})
},
async getSecondCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&&level=1&&reverse=0&&count=10000`)
.then((res) => {
const secondCIs = {}
res.result.forEach((item) => {
this.secondCIJsonAttr[item._type].forEach((attr) => {
item[`${attr}`] = item[`${attr}`] ? JSON.stringify(item[`${attr}`]) : ''
})
if (item.ci_type in secondCIs) {
secondCIs[item.ci_type].push(item)
} else {
secondCIs[item.ci_type] = [item]
}
})
this.secondCIs = secondCIs
})
.catch((e) => {})
},
async getParentCITypes() {
const res = await getCITypeParent(this.typeId)
this.parentCITypes = res.parents
for (let i = 0; i < res.parents.length; i++) {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.parents[i].id]: p_res.result,
}
})
}
const firstCIColumns = {}
const firstCIJsonAttr = {}
res.parents.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
firstCIJsonAttr[item.id] = jsonAttr
firstCIColumns[item.id] = columns
firstCIColumns[item.id].push({
key: 'p_operation',
field: 'operation',
title: this.$t('operation'),
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.firstCIColumns = firstCIColumns
this.firstCIJsonAttr = firstCIJsonAttr
},
async getChildCITypes() {
const res = await getCITypeChildren(this.typeId)
this.childCITypes = res.children
for (let i = 0; i < res.children.length; i++) {
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.children[i].id]: c_res.result,
}
})
}
const secondCIColumns = {}
const secondCIJsonAttr = {}
res.children.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
secondCIJsonAttr[item.id] = jsonAttr
secondCIColumns[item.id] = columns
secondCIColumns[item.id].push({
key: 'c_operation',
field: 'operation',
title: this.$t('operation'),
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.secondCIColumns = secondCIColumns
this.secondCIJsonAttr = secondCIJsonAttr
},
reload() {
this.init()
},
deleteRelation(first_ci_id, second_ci_id) {
deleteCIRelationView(first_ci_id, second_ci_id).then((res) => {
this.init()
if (this.relationViewRefreshNumber) {
this.relationViewRefreshNumber()
}
})
},
handleChangeActiveKey(e) {
if (e.target.value === '1') {
this.$nextTick(() => {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
})
}
},
},
}
</script>

View File

@@ -17,7 +17,7 @@
border: 1px solid #d9d9d9;
border-radius: 2px;
padding: 4px 8px;
width: auto;
width: 100px;
text-align: center;
.title {
font-size: 16px;
@@ -73,7 +73,7 @@
}
}
.root {
width: auto;
width: 100px;
border-color: @primary-color;
font-weight: 700;
padding: 4px 8px;

View File

@@ -29,10 +29,6 @@ export default {
methods: {
init() {
const root = document.getElementById('ci-detail-relation-topo')
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.font = '16px'
this.canvas = new TreeCanvas({
root: root,
disLinkable: false, // 可删除连线
@@ -58,15 +54,7 @@ export default {
return 10
},
getWidth(d) {
const metrics = context.measureText(d?.title || '')
const width = metrics.width
/**
* width 文字宽度
* 20 icon 宽度
* 4 盒子内边距
* 40 节点间距
*/
return width + 20 + 4 + 40
return 40
},
getHGap(d) {
return 80
@@ -81,27 +69,22 @@ export default {
this.canvas.on('events', ({ type, data }) => {
const sourceNode = data?.id || null
if (type === 'custom:clickLeft') {
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=1&count=10000`).then((res) => {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'left')
})
}
if (type === 'custom:clickRight') {
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=0&count=10000`).then((res) => {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'right')
})
}
})
},
setTopoData(data) {
const root = document.getElementById('ci-detail-relation-topo')
if (root && root?.innerHTML) {
root.innerHTML = ''
}
this.canvas = null
this.init()
this.topoData = _.cloneDeep(data)
this.canvas.redraw(data, {}, () => {
this.canvas.draw(data, {}, () => {
this.canvas.focusCenterWithAnimate()
})
},
@@ -117,37 +100,35 @@ export default {
const r = res.result[i]
if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type)
if (_findCiType) {
const { attributes } = await getCITypeAttributesById(_findCiType.id)
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
newNodes.push({
id: `${r._id}`,
Class: Node,
title: r.ci_type_alias || r.ci_type,
name: r.ci_type,
side: side,
unique_alias,
unique_name,
unique_value: r[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
}
const { attributes } = await getCITypeAttributesById(_findCiType.id)
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
newNodes.push({
id: `${r._id}`,
Class: Node,
title: r.ci_type_alias || r.ci_type,
name: r.ci_type,
side: side,
unique_alias,
unique_name,
unique_value: r[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
}
newEdges.push({
id: `${r._id}`,

View File

@@ -1,468 +1,449 @@
<template>
<div :style="{ height: '100%' }">
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
<a-icon type="share-alt" />
{{ $t('cmdb.ci.share') }}
</a>
<a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
<div class="ci-detail-attr">
<el-descriptions
:title="group.name || $t('other')"
:key="group.name"
v-for="group in attributeGroups"
border
:column="3"
>
<el-descriptions-item
:label="`${attr.alias || attr.name}`"
:key="attr.name"
v-for="attr in group.attributes"
>
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
</el-descriptions-item>
</el-descriptions>
</div>
</a-tab-pane>
<a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" :initQueryLoading="initQueryLoading" />
</div>
</a-tab-pane>
<a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
</a-button>
<a-button type="primary" class="ops-button-ghost" ghost @click="handleExport">
<ops-icon type="veops-export" />{{ $t('export') }}
</a-button>
</a-space>
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
<vxe-table
ref="xTable"
show-overflow
show-header-overflow
:data="ciHistory"
size="small"
:height="tableHeight"
highlight-hover-row
:span-method="mergeRowMethod"
:scroll-y="{ enabled: false, gt: 20 }"
:scroll-x="{ enabled: false, gt: 0 }"
border
resizable
class="ops-unstripe-table"
>
<template #empty>
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span>
</a-empty>
</template>
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
<vxe-table-column
field="username"
:title="$t('user')"
:filters="[]"
:filter-method="filterUsernameMethod"
></vxe-table-column>
<vxe-table-column
field="operate_type"
:filters="[
{ value: 0, label: $t('new') },
{ value: 1, label: $t('delete') },
{ value: 2, label: $t('update') },
]"
:filter-method="filterOperateMethod"
:title="$t('operation')"
>
<template #default="{ row }">
{{ operateTypeMap[row.operate_type] }}
</template>
</vxe-table-column>
<vxe-table-column
field="attr_alias"
:title="$t('cmdb.attribute')"
:filters="[]"
:filter-method="filterAttrMethod"
></vxe-table-column>
<vxe-table-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
<span v-else>{{ row.old }}</span>
</template>
</vxe-table-column>
<vxe-table-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
<span v-else>{{ row.new }}</span>
</template>
</vxe-table-column>
</vxe-table>
</div>
</a-tab-pane>
<a-tab-pane key="tab_4">
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<TriggerTable :ci_id="ci._id" />
</div>
</a-tab-pane>
<a-tab-pane key="tab_5">
<span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
</div>
</a-tab-pane>
</a-tabs>
<a-empty
v-else
:image-style="{
height: '100px',
}"
:style="{ paddingTop: '20%' }"
>
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
</a-empty>
</div>
</template>
<script>
import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
import CiRollbackForm from './ciRollbackForm.vue'
export default {
name: 'CiDetailTab',
components: {
ElDescriptions: Descriptions,
ElDescriptionsItem: DescriptionsItem,
CiDetailAttrContent,
CiDetailRelation,
TriggerTable,
RelatedItsmTable,
CiRollbackForm,
},
props: {
typeId: {
type: Number,
required: true,
},
treeViewsLevels: {
type: Array,
default: () => [],
},
attributeHistoryTableHeight: {
type: Number,
default: null
}
},
data() {
return {
ci: {},
item: [],
attributeGroups: [],
activeTabKey: 'tab_1',
rowSpanMap: {},
ciHistory: [],
ciId: null,
ci_types: [],
hasPermission: true,
itsmInstalled: true,
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
initQueryLoading: true,
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
operateTypeMap() {
return {
0: this.$t('new'),
1: this.$t('delete'),
2: this.$t('update'),
}
},
},
provide() {
return {
ci_types: () => {
return this.ci_types
},
}
},
inject: {
reload: {
from: 'reload',
default: null,
},
handleSearch: {
from: 'handleSearch',
default: null,
},
attrList: {
from: 'attrList',
default: () => [],
},
},
methods: {
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.initQueryLoading = true
this.activeTabKey = activeTabKey
if (activeTabKey === 'tab_2') {
this.$nextTick(() => {
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
})
}
this.ciId = ciId
await this.getCI()
await this.judgeItsmInstalled()
if (this.hasPermission) {
this.getAttributes()
this.getCIHistory()
const ciTypeRes = await getCITypes()
this.ci_types = ciTypeRes.ci_types
if (this.activeTabKey === 'tab_2') {
this.$refs.ciDetailRelation.init(true)
}
}
this.initQueryLoading = false
},
getAttributes() {
getCITypeGroupById(this.typeId, { need_other: 1 })
.then((res) => {
this.attributeGroups = res
})
.catch((e) => {})
},
async getCI() {
await getCIById(this.ciId)
.then((res) => {
if (res.result.length) {
this.ci = res.result[0]
} else {
this.hasPermission = false
}
})
.catch((e) => {
if (e.response.status === 404) {
this.itsmInstalled = false
}
})
},
async judgeItsmInstalled() {
await judgeItsmInstalled().catch((e) => {
this.itsmInstalled = false
})
},
getCIHistory() {
getCIHistory(this.ciId)
.then((res) => {
this.ciHistory = res
const rowSpanMap = {}
let startIndex = 0
let startCount = 1
res.forEach((item, index) => {
if (index === 0) {
return
}
if (res[index].record_id === res[startIndex].record_id) {
startCount += 1
rowSpanMap[index] = 0
if (index === res.length - 1) {
rowSpanMap[startIndex] = startCount
}
} else {
rowSpanMap[startIndex] = startCount
startIndex = index
startCount = 1
if (index === res.length - 1) {
rowSpanMap[index] = 1
}
}
})
this.rowSpanMap = rowSpanMap
})
.catch((e) => {
console.log(e)
})
},
changeTab(key) {
this.activeTabKey = key
if (key === 'tab_3') {
this.$nextTick(() => {
const $table = this.$refs.xTable
if ($table) {
const usernameColumn = $table.getColumnByField('username')
const attrColumn = $table.getColumnByField('attr_alias')
if (usernameColumn) {
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
$table.setFilter(
usernameColumn,
usernameList.map((item) => {
return {
value: item,
label: item,
}
})
)
}
if (attrColumn) {
$table.setFilter(
attrColumn,
this.attrList().map((attr) => {
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
})
)
}
}
})
}
},
filterUsernameMethod({ value, row, column }) {
return row.username === value
},
filterOperateMethod({ value, row, column }) {
return Number(row.operate_type) === Number(value)
},
filterAttrMethod({ value, row, column }) {
return row.attr_alias === value
},
refresh(editAttrName) {
this.getCI()
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
// 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => {
if (_find) {
if (this.reload) {
this.reload()
}
} else {
if (this.handleSearch) {
this.handleSearch()
}
}
}, 500)
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['created_at', 'username']
const cellValue1 = row['created_at']
const cellValue2 = row['username']
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
updateCIByself(params, editAttrName) {
const _ci = { ..._.cloneDeep(this.ci), ...params }
this.ci = _ci
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
// 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => {
if (_find) {
if (this.reload) {
this.reload()
}
} else {
if (this.handleSearch) {
this.handleSearch()
}
}
}, 500)
},
shareCi() {
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
this.$copyText(text)
.then(() => {
this.$message.success(this.$t('copySuccess'))
})
.catch(() => {
this.$message.error(this.$t('cmdb.ci.copyFailed'))
})
},
handleRollbackCI() {
this.$nextTick(() => {
this.$refs.ciRollbackForm.onOpen()
})
},
async handleExport() {
this.$refs.xTable.exportData({
filename: this.$t('cmdb.ci.history'),
sheetName: 'Sheet1',
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
data: this.ciHistory,
isMerge: true,
isColgroup: true,
})
}
},
}
</script>
<style lang="less">
.ci-detail-tab {
height: 100%;
.ant-tabs-content {
height: calc(100% - 45px);
.ant-tabs-tabpane {
height: 100%;
}
}
.ant-tabs-bar {
margin: 0;
}
.ant-tabs-extra-content {
line-height: 44px;
}
.ci-detail-attr {
height: 100%;
overflow: auto;
padding: 24px;
.el-descriptions-item__content {
cursor: default;
&:hover a {
opacity: 1 !important;
}
}
.el-descriptions:first-child > .el-descriptions__header {
margin-top: 0;
}
.el-descriptions__header {
margin-bottom: 5px;
margin-top: 20px;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control {
line-height: 19px;
}
}
}
</style>
<template>
<div :style="{ height: '100%' }">
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
<a-icon type="share-alt" />
{{ $t('cmdb.ci.share') }}
</a>
<a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
<div class="ci-detail-attr">
<el-descriptions
:title="group.name || $t('other')"
:key="group.name"
v-for="group in attributeGroups"
border
:column="3"
>
<el-descriptions-item
:label="`${attr.alias || attr.name}`"
:key="attr.name"
v-for="attr in group.attributes"
>
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
</el-descriptions-item>
</el-descriptions>
</div>
</a-tab-pane>
<a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div>
</a-tab-pane>
<a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
</a-button>
</a-space>
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
<vxe-table
ref="xTable"
show-overflow
show-header-overflow
:data="ciHistory"
size="small"
:height="tableHeight"
highlight-hover-row
:span-method="mergeRowMethod"
:scroll-y="{ enabled: false, gt: 20 }"
:scroll-x="{ enabled: false, gt: 0 }"
border
resizable
class="ops-unstripe-table"
>
<template #empty>
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span>
</a-empty>
</template>
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
<vxe-table-column
field="username"
:title="$t('user')"
:filters="[]"
:filter-method="filterUsernameMethod"
></vxe-table-column>
<vxe-table-column
field="operate_type"
:filters="[
{ value: 0, label: $t('new') },
{ value: 1, label: $t('delete') },
{ value: 2, label: $t('update') },
]"
:filter-method="filterOperateMethod"
:title="$t('operation')"
>
<template #default="{ row }">
{{ operateTypeMap[row.operate_type] }}
</template>
</vxe-table-column>
<vxe-table-column
field="attr_alias"
:title="$t('cmdb.attribute')"
:filters="[]"
:filter-method="filterAttrMethod"
></vxe-table-column>
<vxe-table-column field="old" :title="$t('cmdb.history.old')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
<span v-else>{{ row.old }}</span>
</template>
</vxe-table-column>
<vxe-table-column field="new" :title="$t('cmdb.history.new')">
<template #default="{ row }">
<span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
<span v-else>{{ row.new }}</span>
</template>
</vxe-table-column>
</vxe-table>
</div>
</a-tab-pane>
<a-tab-pane key="tab_4">
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<TriggerTable :ci_id="ci._id" />
</div>
</a-tab-pane>
<a-tab-pane key="tab_5">
<span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
<div :style="{ padding: '24px', height: '100%' }">
<RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
</div>
</a-tab-pane>
</a-tabs>
<a-empty
v-else
:image-style="{
height: '100px',
}"
:style="{ paddingTop: '20%' }"
>
<img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
</a-empty>
</div>
</template>
<script>
import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
import CiRollbackForm from './ciRollbackForm.vue'
export default {
name: 'CiDetailTab',
components: {
ElDescriptions: Descriptions,
ElDescriptionsItem: DescriptionsItem,
CiDetailAttrContent,
CiDetailRelation,
TriggerTable,
RelatedItsmTable,
CiRollbackForm,
},
props: {
typeId: {
type: Number,
required: true,
},
treeViewsLevels: {
type: Array,
default: () => [],
},
attributeHistoryTableHeight: {
type: Number,
default: null
}
},
data() {
return {
ci: {},
item: [],
attributeGroups: [],
activeTabKey: 'tab_1',
rowSpanMap: {},
ciHistory: [],
ciId: null,
ci_types: [],
hasPermission: true,
itsmInstalled: true,
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
operateTypeMap() {
return {
0: this.$t('new'),
1: this.$t('delete'),
2: this.$t('update'),
}
},
},
provide() {
return {
ci_types: () => {
return this.ci_types
},
}
},
inject: {
reload: {
from: 'reload',
default: null,
},
handleSearch: {
from: 'handleSearch',
default: null,
},
attrList: {
from: 'attrList',
default: () => [],
},
},
methods: {
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.activeTabKey = activeTabKey
if (activeTabKey === 'tab_2') {
this.$nextTick(() => {
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
})
}
this.ciId = ciId
await this.getCI()
await this.judgeItsmInstalled()
if (this.hasPermission) {
this.getAttributes()
this.getCIHistory()
getCITypes().then((res) => {
this.ci_types = res.ci_types
})
}
},
getAttributes() {
getCITypeGroupById(this.typeId, { need_other: 1 })
.then((res) => {
this.attributeGroups = res
})
.catch((e) => {})
},
async getCI() {
await getCIById(this.ciId)
.then((res) => {
if (res.result.length) {
this.ci = res.result[0]
} else {
this.hasPermission = false
}
})
.catch((e) => {
if (e.response.status === 404) {
this.itsmInstalled = false
}
})
},
async judgeItsmInstalled() {
await judgeItsmInstalled().catch((e) => {
this.itsmInstalled = false
})
},
getCIHistory() {
getCIHistory(this.ciId)
.then((res) => {
this.ciHistory = res
const rowSpanMap = {}
let startIndex = 0
let startCount = 1
res.forEach((item, index) => {
if (index === 0) {
return
}
if (res[index].record_id === res[startIndex].record_id) {
startCount += 1
rowSpanMap[index] = 0
if (index === res.length - 1) {
rowSpanMap[startIndex] = startCount
}
} else {
rowSpanMap[startIndex] = startCount
startIndex = index
startCount = 1
if (index === res.length - 1) {
rowSpanMap[index] = 1
}
}
})
this.rowSpanMap = rowSpanMap
})
.catch((e) => {
console.log(e)
})
},
changeTab(key) {
this.activeTabKey = key
if (key === 'tab_3') {
this.$nextTick(() => {
const $table = this.$refs.xTable
if ($table) {
const usernameColumn = $table.getColumnByField('username')
const attrColumn = $table.getColumnByField('attr_alias')
if (usernameColumn) {
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
$table.setFilter(
usernameColumn,
usernameList.map((item) => {
return {
value: item,
label: item,
}
})
)
}
if (attrColumn) {
$table.setFilter(
attrColumn,
this.attrList().map((attr) => {
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
})
)
}
}
})
}
},
filterUsernameMethod({ value, row, column }) {
return row.username === value
},
filterOperateMethod({ value, row, column }) {
return Number(row.operate_type) === Number(value)
},
filterAttrMethod({ value, row, column }) {
return row.attr_alias === value
},
refresh(editAttrName) {
this.getCI()
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
// 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => {
if (_find) {
if (this.reload) {
this.reload()
}
} else {
if (this.handleSearch) {
this.handleSearch()
}
}
}, 500)
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['created_at', 'username']
const cellValue1 = row['created_at']
const cellValue2 = row['username']
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
updateCIByself(params, editAttrName) {
const _ci = { ..._.cloneDeep(this.ci), ...params }
this.ci = _ci
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
// 修改的字段为树形视图订阅的字段 则全部reload
setTimeout(() => {
if (_find) {
if (this.reload) {
this.reload()
}
} else {
if (this.handleSearch) {
this.handleSearch()
}
}
}, 500)
},
shareCi() {
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
this.$copyText(text)
.then(() => {
this.$message.success(this.$t('copySuccess'))
})
.catch(() => {
this.$message.error(this.$t('cmdb.ci.copyFailed'))
})
},
handleRollbackCI() {
this.$nextTick(() => {
this.$refs.ciRollbackForm.onOpen()
})
},
},
}
</script>
<style lang="less">
.ci-detail-tab {
height: 100%;
.ant-tabs-content {
height: calc(100% - 45px);
.ant-tabs-tabpane {
height: 100%;
}
}
.ant-tabs-bar {
margin: 0;
}
.ant-tabs-extra-content {
line-height: 44px;
}
.ci-detail-attr {
height: 100%;
overflow: auto;
padding: 24px;
.el-descriptions-item__content {
cursor: default;
&:hover a {
opacity: 1 !important;
}
}
.el-descriptions:first-child > .el-descriptions__header {
margin-top: 0;
}
.el-descriptions__header {
margin-bottom: 5px;
margin-top: 20px;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control {
line-height: 19px;
}
}
}
</style>

View File

@@ -29,7 +29,7 @@
? attr.default.default
: attr.default.default.split(',')
: attr.default.default
: attr.is_list ? [] : null,
: null,
},
]"
:placeholder="$t('placeholder2')"

View File

@@ -1,10 +1,6 @@
<template>
<a-modal width="800px" :visible="visible" @ok="handleOK" @cancel="handleCancel" :closable="false">
<Discovery
:isSelected="true"
:style="{ maxHeight: '75vh', overflow: 'auto' }"
v-if="visible"
/>
<Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" />
<template #footer>
<a-space>
<a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
@@ -18,7 +14,7 @@
<script>
import _ from 'lodash'
import Discovery from '../discovery'
import { postCITypeDiscovery } from '../../api/discovery'
export default {
name: 'ADModal',
components: { Discovery },
@@ -53,17 +49,20 @@ export default {
},
async handleOK() {
if (this.selectedIds && this.selectedIds.length) {
const adCITypeList = this.selectedIds.map((item, index) => {
return {
adr_id: item.id,
id: new Date().getTime() + index,
extra_option: {
alias: ''
},
isClient: true,
}
const promises = this.selectedIds.map(({ id, type }) => {
return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 })
})
this.$emit('pushCITypeList', adCITypeList)
await Promise.all(promises)
.then((res) => {
this.getCITypeDiscovery(res[0].id)
this.$message.success(this.$t('addSuccess'))
})
.catch(() => {
this.getCITypeDiscovery()
})
.finally(() => {
this.handleCancel()
})
}
this.handleCancel()
},

View File

@@ -1,105 +0,0 @@
<template>
<div class="ad-container" :style="{ height: `${windowHeight - 130}px` }">
<div class="ad-btns">
<div
:class="['ad-btns-item', activeKey === item.key ? 'ad-btns-item_active' : '']"
v-for="item in tabs"
:key="item.key"
@click="changeTab(item.key)"
>
{{ $t(item.label) }}
</div>
</div>
<AttrAD
v-if="activeKey === AD_TAB_KEY.ATTR"
:CITypeId="CITypeId"
></AttrAD>
<RelationAD
v-else-if="activeKey === AD_TAB_KEY.RELATION"
:CITypeId="CITypeId"
></RelationAD>
</div>
</template>
<script>
import { mapState } from 'vuex'
import AttrAD from './attrAD.vue'
import RelationAD from './relationAD.vue'
const AD_TAB_KEY = {
ATTR: '1',
RELATION: '2'
}
export default {
name: 'ADTab',
components: {
AttrAD,
RelationAD,
},
props: {
CITypeId: {
type: Number,
default: null,
},
},
data() {
return {
AD_TAB_KEY,
activeKey: AD_TAB_KEY.ATTR,
tabs: [
{
key: AD_TAB_KEY.ATTR,
label: 'cmdb.ciType.attributeAD'
},
{
key: AD_TAB_KEY.RELATION,
label: 'cmdb.ciType.relationAD'
}
]
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
},
methods: {
changeTab(activeKey) {
this.activeKey = activeKey
}
}
}
</script>
<style lang="less" scoped>
.ad-btns {
display: inline-flex;
align-items: center;
border: solid 1px @border-color-base;
margin-left: 17px;
margin-bottom: 15px;
&-item {
display: flex;
align-items: center;
justify-content: center;
padding: 6px 20px;
background-color: #FFFFFF;
cursor: pointer;
color: @text-color_2;
font-size: 14px;
font-weight: 400;
&:not(:first-child) {
border-left: solid 1px @border-color-base;
}
&_active {
background-color: @primary-color;
color: #FFFFFF;
}
}
}
</style>

View File

@@ -1,140 +0,0 @@
<template>
<CustomDrawer
:title="$t('cmdb.ciType.viewAllAttr')"
:visible="visible"
placement="right"
width="800"
:bodyStyle="{ height: '100vh' }"
@close="handleClose"
>
<vxe-table
resizable
size="mini"
:span-method="mergeRowMethod"
:data="tableData"
show-overflow
show-header-overflow
border
class="ops-stripe-table"
:height="windowHeight - 160"
>
<vxe-table-column align="center" field="groupId" :title="$t('cmdb.ciType.attrGroup')" :width="100">
<template #default="{row}">
<span>{{ row.groupName }}</span>
</template>
</vxe-table-column>
<vxe-table-column field="name" :title="$t('cmdb.ciType.attrName')" :width="150"></vxe-table-column>
<vxe-table-column field="alias" :title="$t('cmdb.ciType.attrAlias')" :width="150"></vxe-table-column>
<vxe-table-column field="typeText" :title="$t('type')" :width="100"></vxe-table-column>
<vxe-table-column field="code" :title="$t('cmdb.ciType.attrCode')">
<template #default="{row}">
<a @click="copyText(row.code)" >{{ row.code }}</a>
</template>
</vxe-table-column>
</vxe-table>
</CustomDrawer>
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
import { valueTypeMap } from '@/modules/cmdb/utils/const'
export default {
name: 'AllAttrDrawer',
data() {
return {
visible: false,
tableData: [],
}
},
inject: ['providerGroupsData'],
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
},
methods: {
async open() {
this.visible = true
const tableData = []
const typeMap = valueTypeMap()
const providerGroupsData = _.cloneDeep(this.providerGroupsData() || {})
const groupsData = providerGroupsData?.CITypeGroups || []
const otherAttrData = providerGroupsData?.otherGroupAttributes || []
groupsData.forEach((group) => {
if (group?.attributes?.length) {
const attrArr = group.attributes.map((attr) => {
if (attr.is_password) {
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
attr.groupId = group.id
attr.groupName = group.name
attr.code = ['0', '1', '6'].includes(attr.value_type) ? `{{ ${attr.name} }}` : `'''{{ ${attr.name} }}'''`
attr.typeText = typeMap?.[attr.value_type] ?? ''
return attr
})
tableData.push(...attrArr)
}
})
otherAttrData.forEach((attr) => {
if (attr.is_password) {
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
attr.groupId = -1
attr.groupName = '其他'
attr.code = `{{ ${attr.name} }}`
attr.typeText = typeMap?.[attr.value_type] ?? ''
})
tableData.push(...otherAttrData)
this.tableData = tableData
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['groupId']
const currentValue = row.groupId
if (currentValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow.groupId === currentValue) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow.groupId === currentValue) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
handleClose() {
this.visible = false
},
copyText(text) {
this.$copyText(text)
.then(() => {
this.$message.success(this.$t('copySuccess'))
})
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,28 +1,37 @@
<template>
<div class="attr-ad" :style="{ height: `${windowHeight - 130}px` }">
<div v-if="adCITypeList && adCITypeList.length">
<AttrADTabs
:adCITypeList="adCITypeList"
:currentTab="currentTab"
:getADCITypeParam="getADCITypeParam"
@changeTab="changeTab"
@changeAlias="changeAlias"
@deleteADT="deleteADT"
@clickAdd="() => $refs.adModal.open()"
/>
<AttrADTabpane
:key="`attrAdTabpane_${currentTab}`"
:ref="`attrAdTabpaneRef`"
:adr_id="currentADData.adr_id"
:CITypeId="CITypeId"
:adrList="adrList"
:adCITypeList="adCITypeList"
:currentAdt="currentADData"
:ciTypeAttributes="ciTypeAttributes"
:currentAdr="getADCITypeParam(currentADData.adr_id, undefined, true)"
@openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)"
@handleSave="saveTabpane"
/>
<a-tabs size="small" v-model="currentTab">
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
<a-space slot="tab">
<span v-if="item.extra_option && item.extra_option.alias">{{ item.extra_option.alias }}</span>
<span v-else>{{ getADCITypeParam(item.adr_id) }}</span>
<a-icon type="close-circle" @click="(e) => deleteADT(e, item)" />
</a-space>
<AttrADTabpane
:ref="`attrAdTabpane_${item.id}`"
:adr_id="item.adr_id"
:adrList="adrList"
:adCITypeList="adCITypeList"
:currentAdt="item"
:ciTypeAttributes="ciTypeAttributes"
:currentAdr="getADCITypeParam(item.adr_id, undefined, true)"
@openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)"
@handleSave="getCITypeDiscovery"
/>
</a-tab-pane>
<a-space
@click="
() => {
$refs.adModal.open()
}
"
slot="tabBarExtraContent"
:style="{ cursor: 'pointer' }"
>
<ops-icon type="icon-xianxing-tianjia" :style="{ color: '#2F54EB' }" /><a>{{ $t('add') }}</a>
</a-space>
</a-tabs>
</div>
<a-empty
v-else
@@ -45,41 +54,28 @@
{{ $t('add') }}
</a-button>
</a-empty>
<ADModal
ref="adModal"
:CITypeId="CITypeId"
@pushCITypeList="pushCITypeList"
@addPlugin="openEditDrawer(null, 'add', 'plugin')"
/>
<ADModal ref="adModal" :CITypeId="CITypeId" @addPlugin="openEditDrawer(null, 'add', 'agent')" />
<EditDrawer ref="editDrawer" :is_inner="false" @updateNotInner="updateNotInner" />
</div>
</template>
<script>
import _ from 'lodash'
import { mapState } from 'vuex'
import ADModal from './adModal.vue'
import {
getDiscovery,
getCITypeDiscovery,
deleteCITypeDiscovery,
postCITypeDiscovery,
deleteDiscovery,
putCITypeDiscovery
} from '../../api/discovery'
import { getCITypeAttributesById } from '../../api/CITypeAttr'
import ADModal from './adModal.vue'
import AttrADTabpane from './attrADTabpane.vue'
import EditDrawer from '../discovery/editDrawer.vue'
import AttrADTabs from './attrADTabs.vue'
export default {
name: 'AttrAutoDiscovery',
components: {
ADModal,
AttrADTabpane,
EditDrawer,
AttrADTabs
},
components: { ADModal, AttrADTabpane, EditDrawer },
props: {
CITypeId: {
type: Number,
@@ -90,24 +86,15 @@ export default {
return {
ciTypeAttributes: [],
adrList: [],
serviceCITYpeList: [],
clientCITypeList: [],
adCITypeList: [],
currentTab: '',
deletePlugin: false,
queryLoaded: false,
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
currentADData() {
return this?.adCITypeList?.find((item) => item?.id === this?.currentTab) ?? {}
},
adCITypeList() {
const uniqueArray = _.differenceBy(this.clientCITypeList, this.serviceCITYpeList, 'id')
return [...this.serviceCITYpeList, ...uniqueArray]
}
},
provide() {
return {
@@ -117,9 +104,9 @@ export default {
watch: {
currentTab: {
handler() {
if (this.currentTab && this.queryLoaded) {
if (this.currentTab) {
this.$nextTick(() => {
this.$refs[`attrAdTabpaneRef`].init()
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
})
}
},
@@ -132,10 +119,9 @@ export default {
this.ciTypeAttributes = res.attributes.map((item) => {
return { ...item, value: item.name, label: item.name }
})
this.queryLoaded = true
if (this.currentTab) {
this.$nextTick(() => {
this.$refs[`attrAdTabpaneRef`].init()
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
})
}
})
@@ -148,34 +134,15 @@ export default {
},
async getCITypeDiscovery(currentTab) {
await getCITypeDiscovery(this.CITypeId).then((res) => {
const serviceCITYpeList = res.filter((item) => item.adr_id)
serviceCITYpeList.forEach((item) => {
const _find = this.adrList.find((adr) => adr.id === item.adr_id)
item.icon = _find?.option?.icon || {}
})
this.serviceCITYpeList = serviceCITYpeList
this.$nextTick(() => {
if (this.adCITypeList && this.adCITypeList.length && !this.currentTab) {
this.currentTab = this.adCITypeList[0].id
}
if (currentTab) {
this.currentTab = currentTab
}
})
this.adCITypeList = res.filter((item) => item.adr_id)
if (this.adCITypeList && this.adCITypeList.length && !this.currentTab) {
this.currentTab = this.adCITypeList[0].id
}
if (currentTab) {
this.currentTab = currentTab
}
})
},
pushCITypeList(list) {
list.forEach((item) => {
const _find = this.adrList.find((adr) => adr.id === item.adr_id)
item.icon = _find?.option?.icon || {}
})
this.$set(this, 'clientCITypeList', [
...this.clientCITypeList,
...list
])
this.currentTab = list[0].id
},
getADCITypeParam(adr_id, params = 'name', isAll = false) {
const _find = this.adrList.find((item) => item.id === adr_id)
if (_find) {
@@ -185,119 +152,52 @@ export default {
return _find[`${params}`]
}
},
async deleteADT(item) {
async deleteADT(e, item) {
e.preventDefault()
e.stopPropagation()
const that = this
const is_plugin = this.getADCITypeParam(item.adr_id, 'is_plugin')
this.$confirm({
title: that.$t('cmdb.ciType.confirmDeleteADT', { pluginName: `${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}` }),
content: (h) => {
if (!is_plugin) {
return ''
}
return (
<div>
<a-checkbox
v-model={that.deletePlugin}
>
{that.$t('cmdb.ciType.deletePlugin')}
</a-checkbox>
</div>
)
},
onOk () {
if (item.isClient) {
const adtIndex = that.clientCITypeList.findIndex((listItem) => listItem.id === item.id)
if (adtIndex !== -1) {
that.clientCITypeList.splice(adtIndex, 1)
that.currentTab = that?.adCITypeList?.[0]?.id ?? ''
if (is_plugin && that.deletePlugin) {
that.deleteDiscovery(item.adr_id)
}
content: (h) => (
<div>
<a-checkbox v-model={that.deletePlugin}>{that.$t('cmdb.ciType.deletePlugin')}</a-checkbox>
</div>
),
onOk() {
deleteCITypeDiscovery(item.id).then(async () => {
if (that.currentTab === item.id) {
that.currentTab = ''
}
} else {
deleteCITypeDiscovery(item.id).then(async () => {
if (that.currentTab === item.id) {
that.currentTab = ''
}
that.$message.success(that.$t('deleteSuccess'))
that.getCITypeDiscovery()
if (is_plugin && that.deletePlugin) {
that.deleteDiscovery(item.adr_id)
}
that.deletePlugin = false
})
}
that.$message.success(that.$t('deleteSuccess'))
that.getCITypeDiscovery()
if (that.deletePlugin) {
await deleteDiscovery(item.adr_id).finally(() => {
that.deletePlugin = false
})
}
that.deletePlugin = false
})
},
onCancel() {
that.deletePlugin = false
},
})
},
deleteDiscovery(id) {
deleteDiscovery(id).finally(async () => {
this.deletePlugin = false
await this.getDiscovery()
})
},
openEditDrawer(data, type, adType) {
this.$refs.editDrawer.open(data, type, adType)
},
async updateNotInner(adr) {
const _idx = this.adCITypeList.findIndex((item) => item.adr_id === adr.id)
await this.getDiscovery()
let res
if (_idx < 0) {
const ciType = {
adr_id: adr.id,
id: new Date().getTime(),
extra_option: {
alias: ''
},
isClient: true,
}
this.pushCITypeList([ciType])
res = await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
}
await this.getDiscovery()
await this.getCITypeDiscovery(res?.id ?? undefined)
this.$nextTick(() => {
this.$refs[`attrAdTabpaneRef`].init()
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
})
},
changeTab(id) {
console.log('changeTab', id)
this.currentTab = id
},
changeAlias({ id, value, isClient }) {
if (isClient) {
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
}
}
putCITypeDiscovery(id, params).then(async () => {
this.$message.success(this.$t('saveSuccess'))
await this.getCITypeDiscovery()
})
}
},
saveTabpane(id) {
const adtIndex = this.clientCITypeList.findIndex((listItem) => listItem.id === this.currentTab)
if (adtIndex !== -1) {
this.clientCITypeList.splice(adtIndex, 1)
}
this.getCITypeDiscovery(id)
}
},
}
</script>
@@ -306,7 +206,6 @@ export default {
.attr-ad {
position: relative;
padding: 0 20px;
.attr-ad-header {
width: 100%;
display: inline-flex;
@@ -317,13 +216,7 @@ export default {
border-left: 4px solid @primary-color;
font-size: 16px;
color: rgba(0, 0, 0, 0.75);
margin-top: 30px;
}
.attr-ad-header-margin {
margin-bottom: 0px;
}
.attr-ad-footer {
width: 60%;
text-align: right;

View File

@@ -1,156 +0,0 @@
<template>
<a-row class="attr-ad-form">
<a-col :span="24">
<a-form-item
label="CIDR"
:labelCol="labelCol"
:wrapperCol="{ span: 18 }"
labelAlign="right"
style="width: 100%; margin-top: 20px"
>
<div class="cidr-tag">
<div
v-for="(item) in list"
:key="item.id"
class="cidr-tag-item"
>
<a-tooltip :title="item.value">
<span class="cidr-tag-text">{{ item.value }}</span>
</a-tooltip>
<a-icon
class="cidrv-tag-close"
type="close"
@click.stop="clickClose(item.id)"
/>
</div>
<a-input
v-if="showAddInput"
class="cidr-tag-input"
autofocus
@blur="addPreValue"
@pressEnter="showAddInput = false"
></a-input>
<a v-else class="cidr-tag-add" @click="showAddInput = true">+ {{ $t('new') }}</a>
</div>
</a-form-item>
</a-col>
</a-row>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
export default {
name: 'CIDRTags',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Array,
default: () => [],
},
},
data() {
return {
showAddInput: false,
}
},
inject: ['provide_labelCol'],
computed: {
list: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
clickClose(id) {
const list = _.cloneDeep(this.value)
const index = list.findIndex((item) => item.id === id)
if (index !== -1) {
list.splice(index, 1)
this.$emit('change', list)
}
},
addPreValue(e) {
this.showAddInput = false
const val = e.target.value
if (!val) {
return
}
const list = _.cloneDeep(this.value)
list.push({
value: val,
id: uuidv4()
})
this.$emit('change', list)
}
}
}
</script>
<style lang="less" scoped>
.cidr-tag {
width: max-content;
max-width: 100%;
padding: 6px 9px;
border-radius: 2px;
border: 1px solid #E4E7ED;
background: #FFF;
display: flex;
flex-wrap: wrap;
gap: 8px;
&-item {
padding: 3px 6px;
background-color: #F0F5FF;
display: flex;
align-items: center;
}
&-text {
font-size: 12px;
font-weight: 400;
color: #1D2129;
line-height: 18px;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
max-width: 100px;
overflow: hidden;
}
&-close {
font-size: 12px;
color: #1D2129;
margin-left: 4px;
cursor: pointer;
}
&-input {
max-width: 120px;
height: 26px;
line-height: 26px;
padding: 3px 6px;
}
&-add {
border: dashed 1px #e4e7ed;
padding: 3px 6px;
font-size: 12px;
font-weight: 400;
color: #1D2129;
line-height: 18px;
cursor: pointer;
}
}
</style>

View File

@@ -1,77 +0,0 @@
<template>
<div class="cloud-tabs">
<div
v-for="(item) in tabList"
:key="item.key"
:class="['cloud-tabs-item', activeKey === item.key ? 'cloud-tabs-item-active' : '']"
@click="clickTab(item.key)"
>
{{ $t(item.text) }}
</div>
</div>
</template>
<script>
import { tabList, TAB_KEY } from '../constants.js'
export default {
name: 'CloudTab',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: String,
default: TAB_KEY.CUSTOM,
},
},
computed: {
activeKey: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
},
data() {
return {
tabList
}
},
methods: {
clickTab(key) {
this.$emit('change', key)
}
}
}
</script>
<style lang="less" scoped >
.cloud-tabs {
display: flex;
align-items: center;
margin-bottom: 26px;
&-item {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 400;
color: #4E5969;
background-color: #F7F8FA;
width: 105px;
height: 32px;
cursor: pointer;
&-active {
border: solid 1px #B1C9FF;
background-color: #E1EFFF;
color: #2F54EB;
}
}
}
</style>

View File

@@ -1,15 +0,0 @@
export const TAB_KEY = {
CUSTOM: 'custom',
CONFIG: 'config'
}
export const tabList = [
{
key: TAB_KEY.CUSTOM,
text: 'cmdb.ad.tabCustom'
},
{
key: TAB_KEY.CONFIG,
text: 'cmdb.ad.tabConfig'
}
]

View File

@@ -1,52 +0,0 @@
<template>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 192.168.0.0/16`" :label="$t('cmdb.ciType.portScanLabel1')">
<a-input v-model="formData.cidr" />
</a-form-model-item>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 8000-8800`" :label="$t('cmdb.ciType.portScanLabel2')">
<a-input v-model="formData.ports" />
</a-form-model-item>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 0x1234`" :label="$t('cmdb.ciType.portScanLabel3')">
<a-input v-model="formData.enable_cidr" />
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
name: 'PortScanConfig',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,154 +0,0 @@
<template>
<div class="private-cloud-wrap">
<CloudTab
v-model="formData.tabActive"
@change="handleTabChange"
/>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
v-if="formData.tabActive === TAB_KEY.CONFIG"
:required="true"
:label="$t('cmdb.ad.tabConfig')"
>
<a-select
showSearch
optionFilterProp="title"
v-model="formData._reference"
@change="handleSelectChange"
>
<a-select-option
v-for="(item) in accountsList"
:key="item.id"
:value="item.id"
:title="item.name"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.host')">
<a-input
:disabled="formData.tabActive === TAB_KEY.CONFIG"
v-model="formData.host"
/>
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.account')">
<a-input
:disabled="formData.tabActive === TAB_KEY.CONFIG"
v-model="formData.account"
/>
</a-form-model-item>
<a-form-model-item v-if="formData.tabActive === TAB_KEY.CUSTOM" :required="true" :label="$t('cmdb.ciType.password')">
<a-input-password v-model="formData.password" />
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.ciType.vcenterName')">
<a-input v-model="formData.vcenterName" />
</a-form-model-item>
</a-form-model>
</div>
</template>
<script>
import { getHTTPAccounts } from '@/modules/cmdb/api/discovery'
import { TAB_KEY } from '../constants.js'
import CloudTab from '../cloudTab/index.vue'
export default {
name: 'VCenterForm',
components: {
CloudTab
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
data() {
return {
TAB_KEY,
accountsList: [],
}
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
async init(id) {
const res = await getHTTPAccounts({
adr_id: id
})
this.accountsList = res?.length ? res : []
this.$nextTick(() => {
const { _reference = '', host = '', account = '', tabActive } = this?.formData || {}
const findSelect = this.accountsList?.find((item) => item.id === _reference)
const newFormData = findSelect?.config || {}
const changeData = {
...this.value,
_reference: findSelect?.id ?? '',
}
if (tabActive === TAB_KEY.CONFIG) {
changeData.host = newFormData?.host ?? host
changeData.account = newFormData?.account ?? account
}
this.$emit('change', changeData)
})
},
handleTabChange(key) {
if (key === TAB_KEY.CONFIG) {
this.handleSelectChange(this.formData._referenceValue)
}
},
handleSelectChange(id) {
const accountConfig = this.accountsList.find((item) => item.id === id)?.config || {}
const { host, account } = this?.value
this.$emit('change', {
...this.value,
host: accountConfig?.host ?? host ?? '',
account: accountConfig?.account ?? account ?? ''
})
}
}
}
</script>
<style lang="less" scoped>
.private-cloud-wrap {
margin-left: 17px;
.input-disabled {
/deep/ input {
color: rgba(0, 0, 0, 0.25);
background-color: #f5f5f5;
pointer-events: none;
opacity: 1;
}
}
}
</style>

View File

@@ -1,142 +0,0 @@
<template>
<div class="public-cloud-wrap">
<CloudTab
v-model="formData.tabActive"
@change="handleTabChange"
/>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
v-if="formData.tabActive === TAB_KEY.CONFIG"
:required="true"
:label="$t('cmdb.ad.tabConfig')"
>
<a-select
showSearch
optionFilterProp="title"
v-model="formData._reference"
@change="handleSelectChange"
>
<a-select-option
v-for="(item) in accountsList"
:key="item.id"
:value="item.id"
:title="item.name"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item :required="true" label="key">
<a-input-password
:class="[formData.tabActive === TAB_KEY.CONFIG ? 'input-disabled' : '']"
v-model="formData.key"
/>
</a-form-model-item>
<a-form-model-item v-if="formData.tabActive === TAB_KEY.CUSTOM" :required="true" label="secret">
<a-input-password v-model="formData.secret" />
</a-form-model-item>
</a-form-model>
</div>
</template>
<script>
import { getHTTPAccounts } from '@/modules/cmdb/api/discovery'
import { TAB_KEY } from '../constants.js'
import CloudTab from '../cloudTab/index.vue'
export default {
name: 'PublicCloud',
components: {
CloudTab
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
data() {
return {
TAB_KEY,
accountsList: []
}
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
async init(id) {
const res = await getHTTPAccounts({
adr_id: id
})
this.accountsList = res?.length ? res : []
this.$nextTick(() => {
const { _reference = '', key = '', tabActive } = this?.formData || {}
const findSelect = this.accountsList?.find((item) => item.id === _reference)
const newFormData = findSelect?.config || {}
const changeData = {
...this.value,
_reference: findSelect?.id ?? '',
}
if (tabActive === TAB_KEY.CONFIG) {
changeData.key = newFormData?.key ?? key
}
this.$emit('change', changeData)
})
},
handleTabChange(key) {
if (key === TAB_KEY.CONFIG) {
this.handleSelectChange(this.formData._reference)
}
},
handleSelectChange(id) {
const accountConfig = this.accountsList.find((item) => item.id === id)?.config || {}
const { key } = this?.value
this.$emit('change', {
...this.value,
key: accountConfig?.key ?? key ?? '',
})
}
}
}
</script>
<style lang="less" scoped>
.public-cloud-wrap {
margin-left: 17px;
.input-disabled {
/deep/ input {
color: rgba(0, 0, 0, 0.25);
background-color: #f5f5f5;
pointer-events: none;
opacity: 1;
}
}
}
</style>

View File

@@ -1,11 +1,11 @@
<template>
<div class="attr-ad-tab-pane" :style="{ height: `${windowHeight - 254}px` }">
<div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }">
<a
v-if="!adrIsInner"
:style="{ position: 'absolute', right: 0, top: 0 }"
@click="
() => {
$emit('openEditDrawer', currentAdr, 'edit', 'plugin')
$emit('openEditDrawer', currentAdr, 'edit', 'agent')
}
"
>
@@ -14,61 +14,76 @@
<span>{{ $t('edit') }}</span>
</a-space>
</a>
<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 === DISCOVERY_CATEGORY_TYPE.AGENT"
ref="attrMapTable"
:ruleType="adrType"
:tableData="tableData"
:ciTypeAttributes="ciTypeAttributes"
:uniqueKey="uniqueKey"
/>
<HttpSnmpAD
v-else
:isEdit="true"
ref="httpSnmpAd"
:ruleType="adrType"
:ruleName="adrName"
:ciTypeAttributes="ciTypeAttributes"
:adCITypeList="adCITypeList"
:currentTab="adr_id"
:uniqueKey="uniqueKey"
:currentAdt="currentAdt"
:style="{ marginBottom: '20px' }"
/>
</div>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.SNMP">
<div class="attr-ad-header">{{ $t('cmdb.ciType.nodeConfig') }}</div>
<a-form :form="nodeSettingForm" layout="inline" class="attr-ad-snmp-form">
<NodeSetting ref="nodeSetting" :initNodes="nodes" />
<CIDRTags v-model="cidrList" />
</a-form>
</template>
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
<a-form-model
:model="form"
:labelCol="labelCol"
labelAlign="right"
:wrapperCol="{ span: 14 }"
class="attr-ad-form"
<div>{{ $t('alias') }}<a-input v-model="alias" style="width:200px;" /></div>
<div class="attr-ad-header">{{ $t('cmdb.ciType.attributeMap') }}</div>
<vxe-table
v-if="adrType === 'agent'"
ref="xTable"
:edit-config="{ trigger: 'click', mode: 'cell' }"
size="mini"
stripe
class="ops-stripe-table"
:data="tableData"
:style="{ width: '700px', marginBottom: '20px' }"
>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.adExecTarget')">
<vxe-colgroup :title="$t('cmdb.ciType.autoDiscovery')">
<vxe-column field="name" :title="$t('name')"> </vxe-column>
<vxe-column field="type" :title="$t('type')"> </vxe-column>
<vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-colgroup>
<vxe-colgroup :title="$t('cmdb.ciType.attributes')">
<vxe-column field="attr" :title="$t('name')" :edit-render="{}">
<template #default="{row}">
{{ row.attr }}
</template>
<template #edit="{ row }">
<vxe-select
filterable
clearable
v-model="row.attr"
type="text"
:options="ciTypeAttributes"
transfer
></vxe-select>
</template>
</vxe-column>
</vxe-colgroup>
</vxe-table>
<HttpSnmpAD
v-else
:isEdit="true"
ref="httpSnmpAd"
:ruleType="adrType"
:ruleName="adrName"
:ciTypeAttributes="ciTypeAttributes"
:adCITypeList="adCITypeList"
:currentTab="adr_id"
:style="{ marginBottom: '20px' }"
/>
<a-form-model
v-if="adrType === 'http'"
:model="form2"
:labelCol="{ span: 2 }"
:wrapperCol="{ span: 8 }"
:style="{ margin: '20px 0' }"
>
<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>
<a-form :form="form3" v-if="adrType === 'snmp'" class="attr-ad-snmp-form">
<a-col :span="24">
<a-form-item :label="$t('cmdb.ciType.node')" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }">
<MonitorNodeSetting ref="monitorNodeSetting" :initNodes="nodes" :form="form3" />
</a-form-item>
</a-col>
</a-form>
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
<a-form-model :model="form" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }">
<a-form-model-item :label="$t('cmdb.ciType.adExecTarget')">
<CustomRadio v-model="agent_type" :radioList="agentTypeRadioList">
<a-input
:style="{ width: '300px' }"
@@ -86,77 +101,18 @@
>
<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
:labelCol="labelCol"
:label="$t('cmdb.ciType.adAutoInLib')"
:extra="$t('cmdb.ciType.adAutoInLibTip')"
>
<a-form-model-item :label="$t('cmdb.ciType.adAutoInLib')">
<a-switch v-model="form.auto_accept" />
</a-form-model-item>
<a-form-model-item
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
:label="$t('cmdb.ciType.adInterval')"
:required="true"
>
<el-popover v-model="cronVisible" trigger="click">
<template slot>
<Vcrontab
v-if="adrType"
ref="cronTab"
:hideComponent="['second', 'year']"
:expression="cron"
:hasFooter="true"
@fill="crontabFill"
@hide="hideCron"
></Vcrontab>
</template>
<a-input
v-model="cron"
slot="reference"
:placeholder="$t('cmdb.ciType.cronTips')"
/>
</el-popover>
</a-form-model-item>
</a-form-model>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.HTTP">
<template v-if="isPrivateCloud">
<template v-if="privateCloudName === PRIVATE_CLOUD_NAME.VCenter">
<div class="attr-ad-header">{{ $t('cmdb.ciType.privateCloud') }}</div>
<VcenterForm
v-model="privateCloudForm"
ref="httpForm"
/>
</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> -->
<PublicCloud
v-model="publicCloudForm"
ref="httpForm"
/>
</template>
</template>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT">
<div class="attr-ad-header">{{ $t('cmdb.ciType.portScanConfig') }}</div>
<PortScanConfig v-model="portScanConfigForm" />
</template>
<AttrADTest
:adtId="currentAdt.id"
/>
<div class="attr-ad-header">{{ $t('cmdb.ciType.adInterval') }}</div>
<CustomRadio :radioList="radioList" v-model="interval">
<span v-show="interval === 'interval'" slot="extra_interval">
<a-input-number v-model="intervalValue" :min="1" /> {{ $t('seconds') }}
</span>
</CustomRadio>
<div class="attr-ad-footer">
<a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
@@ -166,40 +122,17 @@
</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 { DISCOVERY_CATEGORY_TYPE, PRIVATE_CLOUD_NAME } from '@/modules/cmdb/views/discovery/constants.js'
import { TAB_KEY } from './attrAD/constants.js'
import { putCITypeDiscovery } from '../../api/discovery'
import HttpSnmpAD from '../../components/httpSnmpAD'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
import CMDBExprDrawer from '@/components/CMDBExprDrawer'
import NodeSetting from '@/modules/cmdb/components/nodeSetting/index.vue'
import AttrADTest from './attrADTest.vue'
import { Popover } from 'element-ui'
import VcenterForm from './attrAD/privateCloud/vcenterForm.vue'
import PublicCloud from './attrAD/publicCloud/index.vue'
import PortScanConfig from './attrAD/portScanConfig/index.vue'
import CIDRTags from './attrAD/cidrTags/index.vue'
import MonitorNodeSetting from '@/components/MonitorNodeSetting'
export default {
name: 'AttrADTabpane',
components: {
Vcrontab,
HttpSnmpAD,
CMDBExprDrawer,
NodeSetting,
AttrMapTable,
AttrADTest,
ElPopover: Popover,
VcenterForm,
PublicCloud,
PortScanConfig,
CIDRTags
},
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
props: {
adr_id: {
type: Number,
@@ -225,10 +158,6 @@ export default {
type: Array,
default: () => [],
},
CITypeId: {
type: Number,
default: null,
},
},
data() {
return {
@@ -237,199 +166,93 @@ export default {
agent_id: '',
auto_accept: false,
query_expr: '',
enabled: true,
},
publicCloudForm: {
form2: {
key: '',
secret: '',
_reference: '',
tabActive: TAB_KEY.CUSTOM,
},
privateCloudForm: {
host: '',
account: '',
password: '',
// insecure: false,
vcenterName: '',
_reference: '',
tabActive: TAB_KEY.CUSTOM,
},
portScanConfigForm: {
cidr: '',
ports: '',
enable_cidr: '',
},
interval: 'interval', // interval cron
cron: '',
cronVisible: false,
intervalValue: 3,
agent_type: 'agent_id',
nodes: [
{
id: uuidv4(),
ip: '',
community: 'public',
community: '',
version: '',
},
],
nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }),
uniqueKey: '',
isPrivateCloud: false,
privateCloudName: '',
PRIVATE_CLOUD_NAME,
DISCOVERY_CATEGORY_TYPE,
isClient: false, // 是否前端新增临时数据
cidrList: [],
}
},
provide() {
return {
provide_labelCol: () => {
return this.labelCol
},
form3: this.$form.createForm(this, { name: 'snmp_form' }),
alias: '',
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
user: (state) => state.user,
userRoles: (state) => state.user.roles,
}),
adrType() {
return this.currentAdr?.type || ''
return this.currentAdr.type
},
adrName() {
return this?.currentAdr?.option?.en || this.currentAdr?.name || ''
return this.currentAdr.name
},
adrIsInner() {
return this.currentAdr?.is_inner || ''
return this.currentAdr.is_inner
},
agentTypeRadioList() {
const radios = [
const { permissions = [] } = this.userRoles
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') {
return [
{ value: 'all', label: this.$t('cmdb.ciType.allNodes') },
{ value: 'agent_id', label: this.$t('cmdb.ciType.specifyNodes') },
{ value: 'query_expr', label: this.$t('cmdb.ciType.selectFromCMDBTips') },
]
}
return [
{ value: 'agent_id', label: this.$t('cmdb.ciType.specifyNodes') },
{ value: 'query_expr', label: this.$t('cmdb.ciType.selectFromCMDBTips') },
]
const permissions = this?.user?.roles?.permissions
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
radios.unshift({ value: 'all', label: this.$t('cmdb.ciType.allNodes') })
}
if (this.adrType !== DISCOVERY_CATEGORY_TYPE.AGENTv || this?.currentAdr?.is_plugin) {
radios.unshift({ value: 'master', label: this.$t('cmdb.ciType.masterNode') })
}
return radios
},
labelCol() {
const isEn = this.$i18n.locale === 'en'
return {
xl: {
span: isEn ? 4 : 2
},
lg: {
span: isEn ? 5 : 3
},
sm: {
span: isEn ? 6 : 4
}
}
}
radioList() {
return [
{ value: 'interval', label: this.$t('cmdb.ciType.byInterval') },
// { value: 'cron', label: '按cron', layout: 'vertical' },
]
},
},
mounted() {},
methods: {
init() {
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 === DISCOVERY_CATEGORY_TYPE.HTTP) {
const {
category = undefined,
key = '',
secret = '',
host = '',
account = '',
password = '',
// insecure = false,
vcenterName = '',
_reference = ''
} = _findADT?.extra_option ?? {}
if (_find?.option?.category === 'private_cloud') {
this.isPrivateCloud = true
this.privateCloudName = _find?.option?.en || ''
switch (this.privateCloudName) {
case PRIVATE_CLOUD_NAME.VCenter:
this.privateCloudForm = {
host,
account,
password,
// insecure,
vcenterName,
_reference,
tabActive: _reference ? TAB_KEY.CONFIG : TAB_KEY.CUSTOM
}
break
default:
break
}
} else {
this.isPrivateCloud = false
this.publicCloudForm = {
key,
secret,
_reference,
tabActive: _reference ? TAB_KEY.CONFIG : TAB_KEY.CUSTOM
}
this.alias = _findADT?.extra_option?.alias ?? ''
if (this.adrType === 'http') {
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
this.form2 = {
key,
secret,
}
this.$refs.httpSnmpAd.setCurrentCate(category)
this.$nextTick(() => {
this.$refs.httpForm.init(this.adr_id)
})
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT) {
const {
cidr = '',
ports = '',
enable_cidr = '',
} = _findADT?.extra_option ?? {}
this.portScanConfigForm = {
cidr,
ports,
enable_cidr
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
const nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
if (this.adrType === 'snmp') {
this.nodes = _findADT?.extra_option?.nodes ?? [
{
id: uuidv4(),
ip: '',
community: 'public',
community: '',
version: '',
},
]
this.nodes = nodes
this.$nextTick(() => {
this.$refs.nodeSetting.initNodesFunc()
})
let cidrList = []
const cidr = _findADT?.extra_option?.cidr
if (Array.isArray(cidr) && cidr?.length) {
cidrList = cidr.map((v) => {
return {
id: uuidv4(),
value: v?.value ? v.value : v
}
this.$refs.monitorNodeSetting.initNodesFunc()
this.$nextTick(() => {
this.$refs.monitorNodeSetting.setNodeField()
})
}
this.cidrList = cidrList
})
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
if (this.adrType === 'agent') {
this.tableData = (_find?.attributes || []).map((item) => {
if (_findADT.attributes) {
return {
@@ -450,61 +273,49 @@ export default {
}
this.form = {
auto_accept: _findADT?.auto_accept || false,
agent_id: _findADT?.agent_id && _findADT?.agent_id !== '0x0000' ? _findADT.agent_id : '',
agent_id: _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 = _findADT.agent_id === '0x0000' ? 'master' : 'agent_id'
this.agent_type = 'agent_id'
} else {
this.agent_type = this.agentTypeRadioList[0].value
}
this.cron = _findADT?.cron || ''
if (_findADT.interval || (!_findADT.interval && !_findADT.cron)) {
this.interval = 'interval'
this.intervalValue = _findADT.interval || ''
} else {
this.interval = 'cron'
this.cron = `0 ${_findADT.cron}`
}
},
getAttrNameByAttrName(attrName) {
const _find = this.ciTypeAttributes.find((item) => item.name === attrName)
return _find?.alias || _find?.name || ''
},
crontabFill(cron) {
this.cron = cron
},
handleSave() {
const { currentAdt } = this
const { currentAdt, alias } = this
let params
if (this.adrType === DISCOVERY_CATEGORY_TYPE.HTTP) {
const { isError, data: cloudOption } = this.validateHTTPForm()
if (isError) {
return
}
if (this.adrType === 'http') {
params = {
extra_option: {
...cloudOption,
...this.form2,
category: this.$refs.httpSnmpAd.currentCate,
},
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT) {
const portScanConfigForm = _.omitBy(this.portScanConfigForm, _.isEmpty) || {}
if (this.adrType === 'snmp') {
params = {
extra_option: {
...portScanConfigForm,
},
extra_option: { nodes: this.$refs.monitorNodeSetting?.getNodeValue() ?? [] },
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
params = {
extra_option: {
nodes: this.$refs.nodeSetting?.getNodeValue() ?? [],
cidr: this?.cidrList?.map((item) => item.value) || []
},
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
const $table = this.$refs.attrMapTable
if (this.adrType === 'agent') {
const $table = this.$refs.xTable
const { fullData: _tableData } = $table.getTableData()
const attributes = {}
_tableData.forEach((td) => {
@@ -529,14 +340,17 @@ export default {
attributes,
}
}
if (this.interval === 'cron') {
this.$refs.cronTab.submitFill()
}
params = {
...params,
...this.form,
type_id: this.CITypeId,
adr_id: currentAdt.adr_id,
cron: this.cron,
interval: this.interval === 'interval' ? this.intervalValue : null,
cron: this.interval === 'cron' ? this.cron : null,
}
if (this.agent_type === 'agent_id' || this.agent_type === 'all') {
params.query_expr = ''
if (this.agent_type === 'agent_id' && !params.agent_id) {
@@ -544,7 +358,6 @@ export default {
return
}
}
if (this.agent_type === 'query_expr' || this.agent_type === 'all') {
params.agent_id = ''
if (this.agent_type === 'query_expr' && !params.query_expr) {
@@ -552,209 +365,31 @@ export default {
return
}
}
if (this.agent_type === 'master') {
params.agent_id = '0x0000'
}
if (!this.cron) {
this.$message.error(this.$t('cmdb.ciType.cronRequiredTip'))
return
}
if (currentAdt?.extra_option) {
params.extra_option = {
...(currentAdt?.extra_option || {}),
...(params?.extra_option || {})
}
}
// 去除合并后的旧配置
if (params.extra_option) {
params.extra_option = this.handleOldExtraOption(params.extra_option)
}
if (currentAdt?.isClient) {
postCITypeDiscovery(this.CITypeId, params).then((res) => {
this.$message.success(this.$t('saveSuccess'))
this.$emit('handleSave', res.id)
})
params.extra_option.alias = alias
} else {
putCITypeDiscovery(currentAdt.id, params).then((res) => {
this.$message.success(this.$t('saveSuccess'))
this.$emit('handleSave', res.id)
})
params.extra_option = {}
params.extra_option.alias = alias
}
putCITypeDiscovery(currentAdt.id, params).then((res) => {
this.$message.success(this.$t('saveSuccess'))
this.$emit('handleSave')
})
},
/**
* HTTP 表单校验
* 公有云 私有云
*/
validateHTTPForm() {
let isError = false
let data = {}
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'publicCloudForm']
if (formData.tabActive === TAB_KEY.CONFIG) {
if (!formData._reference) {
isError = true
this.$message.error(this.$t('cmdb.ad.configErrTip'))
}
data._reference = formData._reference
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data.vcenterName = formData.vcenterName
}
return {
isError,
data
}
}
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data = _.pick(this.privateCloudForm, ['host', 'account', 'password', 'vcenterName'])
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 {
data = _.pick(this.publicCloudForm, ['key', 'secret'])
const publicCloudErros = {
'key': `${this.$t('placeholder1')} key`,
'secret': `${this.$t('placeholder1')} secret`
}
const findError = Object.keys(this.publicCloudForm).find((key) => !this.publicCloudForm[key] && publicCloudErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(publicCloudErros[findError]))
}
}
return {
isError,
data
}
},
/**
* 去除多余旧配置
*/
handleOldExtraOption(option) {
let extra_option = _.cloneDeep(option)
// VCenter 旧配置
if (extra_option?.insecure) {
Reflect.deleteProperty(extra_option, 'insecure')
}
// 根据 HTTP 选项去除多余属性
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'publicCloudForm']
switch (formData.tabActive) {
case TAB_KEY.CUSTOM:
Reflect.deleteProperty(extra_option, '_reference')
break
case TAB_KEY.CONFIG:
extra_option = _.omit(extra_option, ['host', 'account', 'password', 'key', 'secret'])
break
default:
break
}
return extra_option
},
handleOpenCmdb() {
this.$refs.cmdbDrawer.open()
},
copySuccess(text) {
this.form = {
...this.form,
query_expr: `${text}`,
}
},
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>
<style lang="less" scoped>
.attr-ad-tab-pane {
overflow-y: auto;
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;
}
.attr-ad-form {
/deep/ .ant-form-item-label {
margin-left: 17px;
}
/deep/ .ant-form-item-control-wrapper {
// margin-left: -40px;
}
}
.public-cloud-info {
color: @text-color_3;
font-size: 12px;
font-weight: 400;
margin-left: 17px;
margin-bottom: 20px;
}
.radio-master-tip {
font-size: 12px;
color: #86909c;
line-height: 14px;
}
}
<style lang="less">
.attr-ad-snmp-form {
.ant-form-item {
margin-bottom: 0;

View File

@@ -1,183 +0,0 @@
<template>
<div class="attr-ad-tabs">
<div
v-for="item in adCITypeList"
:key="item.id"
:class="['attr-ad-tab', currentTab === item.id ? 'attr-ad-tab_active' : '']"
@click="changeTab(item.id)"
>
<img
v-if="item.icon.id && item.icon.url"
:src="`/api/common-setting/v1/file/${item.icon.url}`"
class="attr-ad-tab-icon"
/>
<ops-icon
v-else-if="item.icon.name"
:type="item.icon.name || 'caise-chajian'"
:style="{ color: item.icon.color }"
class="attr-ad-tab-icon"
/>
<a-input
v-if="nameEditId === item.id"
v-model="nameEditValue"
:ref="`name-edit-${item.id}`"
size="small"
:autofocus="true"
@blur="changeAlias(item.isClient || false)"
/>
<span v-else class="attr-ad-tab-name">
{{ item.extra_option && item.extra_option.alias ? item.extra_option.alias : getADCITypeParam(item.adr_id) }}
</span>
<a-icon
type="edit"
class="attr-ad-tab-edit"
@click="(e) => openNameEdit(e, item)"
/>
<a-icon
type="delete"
class="attr-ad-tab-delete"
@click="(e) => deleteADT(e, item)"
/>
</div>
<a-icon
type="plus-circle"
class="attr-ad-tabs-add"
@click="clickAdd"
></a-icon>
</div>
</template>
<script>
export default {
name: 'AttrADTabs',
props: {
currentTab: {
type: [String, Number],
default: ''
},
adCITypeList: {
type: Array,
default: () => [],
},
getADCITypeParam: {
type: Function,
default: () => ''
}
},
data() {
return {
nameEditId: '',
nameEditValue: '',
}
},
methods: {
changeTab(id) {
this.$emit('changeTab', id)
},
openNameEdit(e, item) {
e.preventDefault()
e.stopPropagation()
this.nameEditId = item.id
if (item?.extra_option?.alias) {
this.nameEditValue = item.extra_option.alias
}
this.$nextTick(() => {
if (this.$refs?.[`name-edit-${item.id}`]?.[0]) {
this.$refs[`name-edit-${item.id}`][0].focus()
}
})
},
changeAlias(isClient) {
this.$emit('changeAlias', {
id: this.nameEditId,
value: this.nameEditValue,
isClient
})
this.$nextTick(() => {
this.nameEditId = ''
this.nameEditValue = ''
})
},
deleteADT(e, item) {
e.preventDefault()
e.stopPropagation()
this.$emit('deleteADT', item)
},
clickAdd() {
this.$emit('clickAdd')
}
}
}
</script>
<style lang="less" scoepd>
.attr-ad-tabs {
display: flex;
align-items: center;
width: 100%;
overflow-x: auto;
padding-bottom: 10px;
.attr-ad-tab {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 24px;
margin-right: 12px;
background-color: @primary-color_7;
cursor: pointer;
flex-shrink: 0;
&-name {
font-weight: 400;
font-size: 12px;
}
&-icon {
font-size: 12px;
width: 12px;
height: 12px;
margin-right: 4px;
}
&-edit {
display: none;
font-size: 10px;
color: @text-color_4;
margin-left: 4px;
}
&-delete {
display: none;
font-size: 10px;
color: @func-color_1;
margin-left: 6px;
}
&_active {
border: solid 1px @primary-color_8;
background-color: @primary-color_6;
.attr-ad-tab-name {
color: @primary-color;
}
}
&:hover {
.attr-ad-tab-edit {
display: inline-block;
}
.attr-ad-tab-delete {
display: inline-block;
}
}
}
&-add {
padding: 11px;
background-color: @primary-color_7;
font-size: 12px;
color: @text-color_4;
}
}
</style>

View File

@@ -1,205 +0,0 @@
<template>
<div>
<div class="attr-ad-header attr-ad-header-margin">{{ $t('cmdb.ciType.configCheckTitle') }}</div>
<div class="attr-ad-content">
<div class="ad-test-title-info">{{ $t('cmdb.ciType.checkTestTip') }}</div>
<div
class="ad-test-btn"
@click="showCheckModal"
>
{{ $t('cmdb.ciType.checkTestBtn') }}
</div>
<div class="ad-test-btn-info">{{ $t('cmdb.ciType.checkTestTip2') }}</div>
<!-- <div
class="ad-test-btn"
@click="showTestModal"
>
{{ $t('cmdb.ciType.checkTestBtn1') }}
</div>
<div class="ad-test-btn-info">{{ $t('cmdb.ciType.checkTestTip3') }}</div> -->
</div>
<a-modal
v-model="checkModalVisible"
:footer="null"
:width="900"
>
<div class="check-modal-title">{{ $t('cmdb.ciType.checkModalTitle') }}</div>
<div class="check-modal-info">{{ $t('cmdb.ciType.checkModalTip') }}</div>
<div class="check-modal-info">{{ $t('cmdb.ciType.checkModalTip1') }}</div>
<div class="check-modal-info">{{ $t('cmdb.ciType.checkModalTip2') }}</div>
<ops-table
size="mini"
:data="checkTableData"
:scroll-y="{ enabled: true }"
height="400"
class="check-modal-table"
>
<vxe-column field="oneagent_name" :title="$t('cmdb.ciType.checkModalColumn1')"></vxe-column>
<vxe-column field="oneagent_id" :title="$t('cmdb.ciType.checkModalColumn2')"></vxe-column>
<vxe-column
field="status"
:min-width="70"
:title="$t('cmdb.ciType.checkModalColumn3')"
>
<template #default="{ row }">
<div
:class="['check-modal-status', row.status ? 'check-modal-status-online' : 'check-modal-status-offline']"
>
{{ $t(`cmdb.ciType.${row.status ? 'checkModalColumnStatus1' : 'checkModalColumnStatus2'}`) }}
</div>
</template>
</vxe-column>
<vxe-column field="sync_at" :title="$t('cmdb.ciType.checkModalColumn4')"></vxe-column>
</ops-table>
</a-modal>
<a-modal
v-model="testModalVisible"
:footer="null"
:width="596"
>
<div class="check-modal-title">{{ $t('cmdb.ciType.testModalTitle') }}</div>
<p class="test-modal-text">{{ testResultText }}</p>
</a-modal>
</div>
</template>
<script>
import {
getAdtSyncHistories,
postAdtTest,
getAdtTestResult
} from '@/modules/cmdb/api/discovery.js'
import moment from 'moment'
export default {
name: 'AttrADTest',
props: {
adtId: {
type: Number,
default: 0,
}
},
data() {
return {
checkModalVisible: false,
checkTableData: [],
testModalVisible: false,
testResultText: '',
}
},
methods: {
async showCheckModal() {
await this.queryCheckTableData()
this.checkModalVisible = true
},
async queryCheckTableData() {
const res = await getAdtSyncHistories(this.adtId)
if (res?.result?.length) {
const newTableData = res.result
newTableData.forEach((item) => {
const syncTime = moment(item.sync_at).valueOf()
const nowTime = new Date().getTime()
item.status = nowTime - syncTime <= 10 * 60 * 1000
})
this.checkTableData = newTableData
} else {
this.checkTableData = []
}
},
async showTestModal() {
await this.queryTestResult()
this.testModalVisible = true
},
async queryTestResult() {
const res = await postAdtTest(this.adtId)
const exec_id = res?.exec_id
if (exec_id) {
const res = await getAdtTestResult(exec_id)
if (res?.stdout) {
this.testResultText = res.stdout
}
}
}
},
}
</script>
<style lang="less" scoped>
.attr-ad-content {
margin-left: 17px;
margin-bottom: 20px;
.ad-test-title-info {
color: @text-color_3;
font-size: 12px;
font-weight: 400;
}
.ad-test-btn {
margin-top: 30px;
padding: 5px 12px;
background-color: #F4F9FF;
border: solid 1px @primary-color_8;
display: inline-block;
cursor: pointer;
color: @link-color;
font-size: 12px;
font-weight: 400;
}
.ad-test-btn-info {
margin-top: 4px;
color: @text-color_3;
font-size: 12px;
font-weight: 400;
}
}
.check-modal-table {
margin-top: 14px;
}
.check-modal-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
}
.check-modal-info {
color: @text-color_3;
font-size: 12px;
font-weight: 400;
}
.check-modal-status {
display: inline-block;
padding: 2px 11px;
font-size: 12px;
font-weight: 400;
&-online {
background-color: #E5F6DF;
color: #30AD2D;
}
&-offline {
background-color: #FFDADA;
color: #F14E4E;
}
}
.test-modal-text {
margin-top: 14px;
padding: 12px;
width: 100%;
height: 312px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
border: solid 1px @border-color-base;
}
</style>

View File

@@ -180,10 +180,6 @@ export default {
label: this.$t('cmdb.ciType.isIndex'),
property: 'is_index',
},
{
label: this.$t('cmdb.ciType.isDynamic'),
property: 'is_dynamic',
},
]
},
inherited() {
@@ -241,8 +237,8 @@ export default {
<style lang="less" scoped>
.attribute-card {
width: 172px;
height: 75px;
width: 182px;
height: 80px;
background: @primary-color_6;
border-radius: 2px;
position: relative;
@@ -308,7 +304,7 @@ export default {
}
}
.attribute-card-footer {
width: 172px;
width: 182px;
height: 30px;
padding: 0 8px;
position: absolute;

View File

@@ -157,6 +157,33 @@
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item
:label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('required')"
>
<a-switch
@change="(checked) => onChange(checked, 'is_required')"
name="is_required"
v-decorator="['is_required', { rules: [], valuePropName: 'checked' }]"
/>
</a-form-item>
</a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item
:label-col="{ span: 8 }"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch
:disabled="isShowComputedArea"
@change="onChange"
name="is_unique"
v-decorator="['is_unique', { rules: [], valuePropName: 'checked' }]"
/>
</a-form-item>
</a-col>
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
<a-form-item
:hidden="currentValueType === '2' ? false : true"
@@ -169,7 +196,7 @@
>{{ $t('cmdb.ciType.index') }}
<a-tooltip :title="$t('cmdb.ciType.indexTips')">
<a-icon
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
@click="
@@ -190,37 +217,10 @@
/>
</a-form-item>
</a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-col :span="6">
<a-form-item
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')"
>
<a-switch
:disabled="isShowComputedArea"
@change="onChange"
name="is_unique"
v-decorator="['is_unique', { rules: [], valuePropName: 'checked' }]"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 8 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('required')"
>
<a-switch
@change="(checked) => onChange(checked, 'is_required')"
name="is_required"
v-decorator="['is_required', { rules: [], valuePropName: 'checked' }]"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item
:label-col="currentValueType === '2' ? { span: 12 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
>
<template slot="label">
<span
@@ -228,7 +228,7 @@
>{{ $t('cmdb.ciType.defaultShow') }}
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
<a-icon
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
@click="
@@ -295,37 +295,6 @@
/>
</a-form-item>
</a-col>
<a-col span="6">
<a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 12 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol"
>
<template slot="label">
<span
style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.isDynamic') }}
<a-tooltip :title="$t('cmdb.ciType.dynamicTips')">
<a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
@click="
(e) => {
e.stopPropagation()
e.preventDefault()
}
"
/>
</a-tooltip>
</span>
</template>
<a-switch
@change="(checked) => onChange(checked, 'is_dynamic')"
name="is_dynamic"
v-decorator="['is_dynamic', { rules: [], valuePropName: 'checked' }]"
/>
</a-form-item>
</a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row>
<a-col :span="24" v-if="!['6'].includes(currentValueType)">
@@ -375,10 +344,6 @@
name="is_computed"
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]"
/>
<div v-show="isShowComputedArea" class="computed-attr-tip">
<div>1. {{ $t('cmdb.ciType.computedAttrTip1') }}</div>
<div>2. {{ $t('cmdb.ciType.computedAttrTip2') }}</div>
</div>
<ComputedArea
showCalcComputed
ref="computedArea"
@@ -569,7 +534,6 @@ export default {
is_index: _record.is_index,
is_sortable: _record.is_sortable,
is_computed: _record.is_computed,
is_dynamic: _record.is_dynamic,
})
}
console.log(_record)
@@ -799,13 +763,7 @@ export default {
}
</script>
<style lang="less" scoped>
.computed-attr-tip {
font-size: 12px;
line-height: 22px;
color: #a5a9bc;
}
</style>
<style lang="less" scoped></style>
<style lang="less">
.attribute-edit-form {
.jsoneditor-outer {

Some files were not shown because too many files have changed in this diff Show More