mirror of
https://github.com/veops/cmdb.git
synced 2025-09-04 11:46:54 +08:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d4b661c77f | ||
|
75cd7bde77 | ||
|
ec912d3a65 | ||
|
42f02b4986 | ||
|
a13b999820 | ||
|
5f53b0dd0e | ||
|
df22085ff9 | ||
|
06148b402d | ||
|
3fe020505a | ||
|
b34e83124f | ||
|
cdc52d3f80 | ||
|
b3a80d5678 | ||
|
a2e3061bba | ||
|
a8eb5126ea | ||
|
adac2129fc | ||
|
e660c901ce | ||
|
ff002c0a1e | ||
|
88593d6da7 | ||
|
6fa0dd5bc5 | ||
|
3200942373 | ||
|
4fd705cc59 | ||
|
74827ce187 | ||
|
4ed1eb6062 | ||
|
7792204658 | ||
|
8621108906 | ||
|
6437af19b9 | ||
|
735ddb334c | ||
|
4a8032202e | ||
|
c7acea6422 | ||
|
ac4c93de8e | ||
|
8d044cf935 | ||
|
54747fa789 | ||
|
545f1bb30b | ||
|
dc77bca17c | ||
|
4973278c5a | ||
|
d1c9361e47 | ||
|
28c57cacd9 | ||
|
711dcc4bd7 | ||
|
491d3cce00 | ||
|
27354a3927 | ||
|
78495eb976 | ||
|
ae900c7d3b | ||
|
50134e6a0b |
0
.github/config.yml
vendored
0
.github/config.yml
vendored
65
.github/workflows/docker-build-and-release.yaml
vendored
Normal file
65
.github/workflows/docker-build-and-release.yaml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: api-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
|
||||
|
||||
jobs:
|
||||
setup-environment:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
release-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup-environment]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
TAG: ${{ github.sha }}
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.21.8"
|
||||
cache: false
|
||||
- name: Login to GitHub Package Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
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 }}
|
||||
- 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 }}
|
26
Makefile
26
Makefile
@@ -1,6 +1,4 @@
|
||||
MYSQL_ROOT_PASSWORD ?= root
|
||||
MYSQL_PORT ?= 3306
|
||||
REDIS_PORT ?= 6379
|
||||
include ./Makefile.variable
|
||||
|
||||
default: help
|
||||
help: ## display this help
|
||||
@@ -50,3 +48,25 @@ 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 \
|
||||
.
|
21
Makefile.variable
Normal file
21
Makefile.variable
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
@@ -190,6 +190,7 @@ def cmdb_counter():
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
i = 0
|
||||
today = datetime.date.today()
|
||||
while True:
|
||||
try:
|
||||
db.session.remove()
|
||||
@@ -200,6 +201,10 @@ 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
|
||||
@@ -493,3 +498,48 @@ 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
|
||||
|
||||
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)
|
||||
|
||||
db.session.commit()
|
||||
|
@@ -1,34 +1,47 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
|
||||
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
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 CMDB_QUEUE
|
||||
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.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 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 flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
from api.models.cmdb import AutoDiscoveryRuleSyncHistory
|
||||
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):
|
||||
@@ -100,6 +113,22 @@ class AutoDiscoveryRuleCRUD(DBMixin):
|
||||
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'):
|
||||
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
|
||||
|
||||
@@ -115,6 +144,22 @@ class AutoDiscoveryRuleCRUD(DBMixin):
|
||||
if other and other.id != existed.id:
|
||||
return abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
|
||||
|
||||
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.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):
|
||||
@@ -122,13 +167,35 @@ 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)
|
||||
|
||||
return self._can_update(**kwargs)
|
||||
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
|
||||
|
||||
|
||||
class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
@@ -147,14 +214,34 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
return cls.cls.get_by(type_id=type_id, to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get(cls, ci_id, oneagent_id, last_update_at=None):
|
||||
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 == "http":
|
||||
for i in DEFAULT_HTTP:
|
||||
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 == "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):
|
||||
result = []
|
||||
rules = cls.cls.get_by(to_dict=True)
|
||||
|
||||
for rule in rules:
|
||||
if rule.get('relation'):
|
||||
continue
|
||||
|
||||
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'):
|
||||
if not (current_user.username == "cmdb_agent" or current_user.uid == rule['uid']):
|
||||
rule['extra_option'].pop('secret', None)
|
||||
@@ -165,7 +252,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
result.append(rule)
|
||||
elif rule['query_expr']:
|
||||
query = rule['query_expr'].lstrip('q').lstrip('=')
|
||||
s = search(query, fl=['_id'], count=1000000)
|
||||
s = ci_search(query, fl=['_id'], count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
@@ -182,9 +269,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
if adr.type in (AutoDiscoveryType.SNMP, AutoDiscoveryType.HTTP):
|
||||
continue
|
||||
|
||||
if not rule['updated_at']:
|
||||
continue
|
||||
|
||||
result.append(rule)
|
||||
|
||||
new_last_update_at = ""
|
||||
@@ -195,6 +279,9 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
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:
|
||||
@@ -213,7 +300,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
agent_id = agent_id.strip()
|
||||
q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}"
|
||||
|
||||
s = search(q.format(current_user.username, agent_id.strip()))
|
||||
s = ci_search(q.format(current_user.username, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
@@ -222,7 +309,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
s = search(q.format(current_user.nickname, agent_id.strip()))
|
||||
s = ci_search(q.format(current_user.nickname, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
@@ -236,7 +323,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
if query_expr.startswith('q='):
|
||||
query_expr = query_expr[2:]
|
||||
|
||||
s = search(query_expr, count=1000000)
|
||||
s = ci_search(query_expr, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
for i in response:
|
||||
@@ -254,13 +341,21 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
def _can_add(**kwargs):
|
||||
|
||||
if kwargs.get('adr_id'):
|
||||
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
|
||||
# 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 adr.type == "http":
|
||||
kwargs.setdefault('extra_option', dict)
|
||||
en_name = None
|
||||
for i in DEFAULT_HTTP:
|
||||
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']]
|
||||
break
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
@@ -268,6 +363,11 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
|
||||
|
||||
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():
|
||||
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
|
||||
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
return kwargs
|
||||
@@ -276,7 +376,29 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
existed = self.cls.get_by_id(kwargs['_id']) or abort(
|
||||
404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id'])))
|
||||
|
||||
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
adr = AutoDiscoveryRule.get_by_id(existed.adr_id) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(existed.adr_id)))
|
||||
if adr.type == "http":
|
||||
kwargs.setdefault('extra_option', dict)
|
||||
en_name = None
|
||||
for i in DEFAULT_HTTP:
|
||||
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']]
|
||||
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():
|
||||
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
|
||||
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
if current_user.uid != existed.uid:
|
||||
@@ -292,7 +414,15 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
|
||||
|
||||
return super(AutoDiscoveryCITypeCRUD, self).update(_id, filter_none=False, **kwargs)
|
||||
inst = self._can_update(_id=_id, **kwargs)
|
||||
if 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()
|
||||
|
||||
obj = inst.update(_id=_id, filter_none=False, **kwargs)
|
||||
|
||||
return obj
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
if AutoDiscoveryCICRUD.get_by_adt_id(kwargs['_id']):
|
||||
@@ -303,6 +433,56 @@ 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_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
|
||||
@@ -391,16 +571,24 @@ 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)
|
||||
@@ -420,6 +608,13 @@ 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
|
||||
@@ -435,6 +630,13 @@ 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
|
||||
@@ -447,32 +649,34 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
ci_id = None
|
||||
if adt.attributes:
|
||||
ci_dict = {adt.attributes[k]: v for k, v in adc.instance.items() if k in adt.attributes}
|
||||
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, **ci_dict)
|
||||
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:
|
||||
relation_ads = AutoDiscoveryCITypeRelation.get_by(ad_type_id=adt.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
|
||||
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)
|
||||
|
||||
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.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
relation_ci_id = response and response[0]['_id']
|
||||
if relation_ci_id:
|
||||
for relation_ci in response:
|
||||
relation_ci_id = relation_ci['_id']
|
||||
try:
|
||||
CIRelationManager.add(ci_id, relation_ci_id)
|
||||
CIRelationManager.add(ci_id, relation_ci_id, valid=False)
|
||||
except:
|
||||
try:
|
||||
CIRelationManager.add(relation_ci_id, ci_id)
|
||||
CIRelationManager.add(relation_ci_id, ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -485,14 +689,35 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
class AutoDiscoveryHTTPManager(object):
|
||||
@staticmethod
|
||||
def get_categories(name):
|
||||
return (ClOUD_MAP.get(name) or {}).get('categories') or []
|
||||
categories = (ClOUD_MAP.get(name) or {}) or []
|
||||
for item in copy.deepcopy(categories):
|
||||
item.pop('map', None)
|
||||
|
||||
return categories
|
||||
|
||||
def get_resources(self, name):
|
||||
en_name = None
|
||||
for i in DEFAULT_HTTP:
|
||||
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']]
|
||||
|
||||
return []
|
||||
|
||||
@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())
|
||||
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 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 []
|
||||
|
||||
@@ -506,3 +731,62 @@ class AutoDiscoverySNMPManager(object):
|
||||
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
|
||||
|
@@ -3,13 +3,13 @@
|
||||
from api.lib.cmdb.const import AutoDiscoveryType
|
||||
|
||||
DEFAULT_HTTP = [
|
||||
dict(name="阿里云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
dict(name="阿里云", en="aliyun", 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,
|
||||
dict(name="腾讯云", en="tencentcloud", 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,
|
||||
dict(name="华为云", en="huaweicloud", 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,
|
||||
dict(name="AWS", en="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,
|
||||
@@ -23,31 +23,47 @@ DEFAULT_HTTP = [
|
||||
]
|
||||
|
||||
ClOUD_MAP = {
|
||||
"aliyun": {
|
||||
"categories": ["云服务器 ECS"],
|
||||
"aliyun": [{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": "templates/aliyun_ecs.json",
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 ECS": "ali.ecs",
|
||||
}
|
||||
},
|
||||
}],
|
||||
|
||||
"tencentcloud": {
|
||||
"categories": ["云服务器 CVM"],
|
||||
"tencentcloud": [{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 CVM"],
|
||||
"map": {
|
||||
"云服务器 CVM": "templates/tencent_cvm.json",
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 CVM": "tencent.cvm",
|
||||
}
|
||||
},
|
||||
}],
|
||||
|
||||
"huaweicloud": {
|
||||
"categories": ["云服务器 ECS"],
|
||||
"huaweicloud": [{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": "templates/huaweicloud_ecs.json",
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 ECS": "huawei.ecs",
|
||||
}
|
||||
},
|
||||
}],
|
||||
|
||||
"aws": {
|
||||
"categories": ["云服务器 EC2"],
|
||||
"aws": [{
|
||||
"category": "计算",
|
||||
"items": ["云服务器 EC2"],
|
||||
"map": {
|
||||
"云服务器 EC2": "templates/aws_ec2.json",
|
||||
},
|
||||
"collect_key_map": {
|
||||
"云服务器 EC2": "aws.ec2",
|
||||
}
|
||||
},
|
||||
}],
|
||||
}
|
||||
|
@@ -2,12 +2,19 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
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
|
||||
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 CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
@@ -448,7 +455,67 @@ class CMDBCounterCache(object):
|
||||
|
||||
cache.set(cls.KEY2, result, timeout=0)
|
||||
|
||||
return result
|
||||
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()
|
||||
|
||||
@classmethod
|
||||
def get_adc_counter(cls):
|
||||
|
@@ -223,7 +223,7 @@ class CIManager(object):
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
"""
|
||||
|
||||
:param unique_key: is a attribute
|
||||
:param unique_key: is an attribute
|
||||
:param unique_value:
|
||||
:param type_id:
|
||||
:return:
|
||||
@@ -383,12 +383,12 @@ class CIManager(object):
|
||||
computed_attrs.append(attr.to_dict())
|
||||
elif attr.is_password:
|
||||
if attr.name in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.name)
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id][0])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -421,7 +421,8 @@ class CIManager(object):
|
||||
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 = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
record_id, has_dynamic = 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)
|
||||
@@ -431,7 +432,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: # has change
|
||||
if record_id or has_dynamic: # has changed
|
||||
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
|
||||
|
||||
if ref_ci_dict: # add relations
|
||||
@@ -440,7 +441,6 @@ 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,12 +465,12 @@ class CIManager(object):
|
||||
computed_attrs.append(attr.to_dict())
|
||||
elif attr.is_password:
|
||||
if attr.name in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.name)
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
|
||||
elif attr.alias in ci_dict:
|
||||
password_dict[attr.id] = ci_dict.pop(attr.alias)
|
||||
password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
|
||||
|
||||
if attr.re_check and password_dict.get(attr.id):
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id])
|
||||
value_manager.check_re(attr.re_check, password_dict[attr.id][0])
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
@@ -495,7 +495,8 @@ class CIManager(object):
|
||||
ci_dict.pop(k)
|
||||
|
||||
try:
|
||||
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
record_id, has_dynamic = value_manager.create_or_update_attr_value(
|
||||
ci, ci_dict, key2attr, ticket_id=ticket_id)
|
||||
except BadRequest as e:
|
||||
raise e
|
||||
|
||||
@@ -503,25 +504,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: # has change
|
||||
if record_id or has_dynamic: # has changed
|
||||
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)
|
||||
|
||||
@@ -736,7 +737,7 @@ class CIManager(object):
|
||||
fields=None, value_tables=None, unique_required=False, excludes=None):
|
||||
"""
|
||||
|
||||
:param ci_ids: list of CI instance ID, eg. ['1', '2']
|
||||
:param ci_ids: list of CI instance ID, e.g. ['1', '2']
|
||||
:param ret_key: name, id or alias
|
||||
:param fields:
|
||||
:param value_tables:
|
||||
@@ -761,6 +762,7 @@ 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]
|
||||
@@ -777,14 +779,18 @@ class CIManager(object):
|
||||
if existed is None:
|
||||
if value:
|
||||
value_table.create(ci_id=ci_id, attr_id=attr_id, value=encrypt_value)
|
||||
changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)]
|
||||
if not is_dynamic:
|
||||
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)
|
||||
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW, PASSWORD_DEFAULT_SHOW, type_id)]
|
||||
if not is_dynamic:
|
||||
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW,
|
||||
PASSWORD_DEFAULT_SHOW, type_id)]
|
||||
else:
|
||||
existed.delete()
|
||||
changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)]
|
||||
if not is_dynamic:
|
||||
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'))
|
||||
@@ -1274,52 +1280,83 @@ class CIRelationManager(object):
|
||||
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_id.isnot(None))
|
||||
CITypeRelation.parent_attr_ids.isnot(None))
|
||||
for item in child_items:
|
||||
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)
|
||||
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
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
|
||||
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
|
||||
CITypeRelation.child_attr_id.isnot(None))
|
||||
CITypeRelation.child_attr_ids.isnot(None))
|
||||
for item in parent_items:
|
||||
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)
|
||||
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
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
|
||||
|
||||
@classmethod
|
||||
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 = None
|
||||
for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
|
||||
ci_type_relation['child_attr_ids'] or []):
|
||||
|
||||
parent_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
_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_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_value_table = TableMap(attr=parent_attr).table
|
||||
child_value_table = TableMap(attr=child_attr).table
|
||||
|
||||
child_value2ci_ids = {}
|
||||
for child in child_values:
|
||||
child_value2ci_ids.setdefault(child.value, []).append(child.ci_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'])
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
for parent_ci_id, child_ci_id in (relations or []):
|
||||
try:
|
||||
cls.add(parent_ci_id, child_ci_id, valid=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class CITriggerManager(object):
|
||||
|
@@ -35,6 +35,7 @@ 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
|
||||
@@ -49,12 +50,12 @@ from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
from api.models.cmdb import TopologyView
|
||||
|
||||
|
||||
class CITypeManager(object):
|
||||
@@ -228,6 +229,9 @@ 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 []):
|
||||
@@ -251,16 +255,22 @@ 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.delete(commit=False)
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
try:
|
||||
from api.models.cmdb import CITypeReconciliation
|
||||
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
item.soft_delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -686,9 +696,19 @@ 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, 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)
|
||||
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)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -757,10 +777,12 @@ class CITypeRelationManager(object):
|
||||
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 CITypeAttributesCache.get2(item.parent_id)]
|
||||
type2attributes[item.parent_id] = [i[1].to_dict() for i in
|
||||
CITypeAttributeManager.get_all_attributes(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 CITypeAttributesCache.get2(item.child_id)]
|
||||
type2attributes[item.child_id] = [i[1].to_dict() for i in
|
||||
CITypeAttributeManager.get_all_attributes(item.child_id)]
|
||||
res[idx]['relation_type'] = item.relation_type.to_dict()
|
||||
|
||||
return res, type2attributes
|
||||
@@ -795,8 +817,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_id"] = relation_inst.parent_attr_id
|
||||
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
|
||||
ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids
|
||||
ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@@ -837,7 +859,9 @@ class CITypeRelationManager(object):
|
||||
if ci_type is None:
|
||||
return nodes, edges
|
||||
|
||||
nodes.append(ci_type.to_dict())
|
||||
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):
|
||||
@@ -906,7 +930,7 @@ class CITypeRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
|
||||
parent_attr_id=None, child_attr_id=None):
|
||||
parent_attr_ids=None, child_attr_ids=None):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
@@ -921,21 +945,22 @@ class CITypeRelationManager(object):
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
old_parent_attr_id = None
|
||||
old_parent_attr_ids, old_child_attr_ids = None, None
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
old_parent_attr_id = existed.parent_attr_id
|
||||
old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids)
|
||||
old_child_attr_ids = copy.deepcopy(existed.child_attr_ids)
|
||||
existed = existed.update(relation_type_id=relation_type_id,
|
||||
constraint=constraint,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
parent_attr_ids=parent_attr_ids,
|
||||
child_attr_ids=child_attr_ids,
|
||||
filter_none=False)
|
||||
else:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id,
|
||||
parent_attr_id=parent_attr_id,
|
||||
child_attr_id=child_attr_id,
|
||||
parent_attr_ids=parent_attr_ids,
|
||||
child_attr_ids=child_attr_ids,
|
||||
constraint=constraint)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
@@ -949,10 +974,10 @@ class CITypeRelationManager(object):
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
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()))
|
||||
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(),))
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
|
||||
@@ -1246,8 +1271,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_id'),
|
||||
id2obj_dicts[added_id].get('child_attr_id'),
|
||||
id2obj_dicts[added_id].get('parent_attr_ids'),
|
||||
id2obj_dicts[added_id].get('child_attr_ids'),
|
||||
)
|
||||
else:
|
||||
obj = cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
|
@@ -55,9 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 删除关系
|
||||
UPDATE_RECONCILIATION = "15" # 删除关系
|
||||
DELETE_RECONCILIATION = "16" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 新增数据合规
|
||||
UPDATE_RECONCILIATION = "15" # 修改数据合规
|
||||
DELETE_RECONCILIATION = "16" # 删除数据合规
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
|
@@ -13,6 +13,7 @@ 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
|
||||
@@ -306,7 +307,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.filter(CITriggerHistory.type_id == type_id)
|
||||
query = query.join(CI, CI.id == CITriggerHistory.ci_id).filter(CI.type_id == type_id)
|
||||
|
||||
if trigger_id:
|
||||
query = query.filter(CITriggerHistory.trigger_id == trigger_id)
|
||||
|
@@ -62,6 +62,7 @@ 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(
|
||||
|
@@ -16,7 +16,7 @@ from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.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
|
||||
@@ -172,12 +172,13 @@ class TopologyViewManager(object):
|
||||
_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 = search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
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:
|
||||
@@ -192,7 +193,6 @@ class TopologyViewManager(object):
|
||||
prefix = REDIS_PREFIX_CI_RELATION
|
||||
key = list(map(str, root_ids))
|
||||
id2node = {}
|
||||
type2meta = {}
|
||||
for level in sorted([i for i in path.keys() if int(i) > 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
|
||||
@@ -238,7 +238,7 @@ class TopologyViewManager(object):
|
||||
type2show[type_id] = attr.name
|
||||
|
||||
if id2node:
|
||||
s = search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
|
||||
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()
|
||||
|
@@ -266,6 +266,7 @@ class AttributeValueManager(object):
|
||||
:return:
|
||||
"""
|
||||
changed = []
|
||||
has_dynamic = False
|
||||
for key, value in ci_dict.items():
|
||||
attr = key2attr.get(key)
|
||||
if not attr:
|
||||
@@ -289,10 +290,16 @@ class AttributeValueManager(object):
|
||||
for idx in range(index, len(existed_attrs)):
|
||||
existed_attr = existed_attrs[idx]
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
|
||||
if not attr.is_dynamic:
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
|
||||
else:
|
||||
has_dynamic = True
|
||||
for idx in range(index, len(value)):
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
|
||||
if not attr.is_dynamic:
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
|
||||
else:
|
||||
has_dynamic = True
|
||||
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
|
||||
@@ -301,7 +308,10 @@ 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)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
if not attr.is_dynamic:
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
has_dynamic = True
|
||||
else:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
@@ -309,16 +319,22 @@ class AttributeValueManager(object):
|
||||
else:
|
||||
existed_attr.update(value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
|
||||
if not attr.is_dynamic:
|
||||
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
|
||||
else:
|
||||
has_dynamic = True
|
||||
|
||||
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]))
|
||||
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]))
|
||||
|
||||
return self.write_change2(changed, ticket_id=ticket_id)
|
||||
return self.write_change2(changed, ticket_id=ticket_id), has_dynamic
|
||||
else:
|
||||
return None, has_dynamic
|
||||
|
||||
@staticmethod
|
||||
def delete_attr_value(attr_id, ci_id, commit=True):
|
||||
|
@@ -3,6 +3,7 @@ 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):
|
||||
@@ -16,7 +17,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", []):
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
@@ -25,7 +26,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", []):
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
@@ -48,7 +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"]},
|
||||
{"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"],
|
||||
|
@@ -8,6 +8,8 @@ from api.extensions import db
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
|
||||
__author__ = 'pycook'
|
||||
|
||||
|
||||
class DBMixin(object):
|
||||
cls = None
|
||||
@@ -17,13 +19,18 @@ class DBMixin(object):
|
||||
page = get_page(page)
|
||||
page_size = get_page_size(page_size)
|
||||
if fl is None:
|
||||
query = db.session.query(cls.cls).filter(cls.cls.deleted.is_(False))
|
||||
query = db.session.query(cls.cls)
|
||||
else:
|
||||
query = db.session.query(*[getattr(cls.cls, i) for i in fl]).filter(cls.cls.deleted.is_(False))
|
||||
query = db.session.query(*[getattr(cls.cls, i) for i in fl])
|
||||
|
||||
_query = None
|
||||
if count_query:
|
||||
_query = db.session.query(func.count(cls.cls.id)).filter(cls.cls.deleted.is_(False))
|
||||
_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))
|
||||
|
||||
for k in kwargs:
|
||||
if hasattr(cls.cls, k):
|
||||
|
@@ -79,7 +79,8 @@ 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):
|
||||
def grant(rid, perms, resource_id=None, group_id=None, rebuild=True,
|
||||
source=AuditOperateSource.acl, force_update=False):
|
||||
app_id = None
|
||||
rt_id = None
|
||||
|
||||
@@ -106,8 +107,23 @@ class PermissionCRUD(object):
|
||||
if not perms:
|
||||
perms = [i.get('name') for i in ResourceTypeCRUD.get_perms(group.resource_type_id)]
|
||||
|
||||
_role_permissions = []
|
||||
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:
|
||||
|
@@ -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
|
||||
|
@@ -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("You do not have {} permission for resource: {}!") # 您没有资源: {} 的{}权限!
|
||||
no_permission = _l("For resource: {}, you do not have {} permission!") # 您没有资源: {} 的{}权限!
|
||||
no_permission2 = _l("You do not have permission to operate!") # 您没有操作权限!
|
||||
no_permission_only_owner = _l("Only the creator or administrator has permission!") # 只有创建人或者管理员才有权限!
|
||||
|
@@ -79,8 +79,11 @@ 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"))
|
||||
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
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 = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
@@ -101,6 +104,7 @@ 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}
|
||||
|
||||
@@ -562,20 +566,29 @@ 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}}]
|
||||
relation = db.Column(db.JSON) # [{ad_key: {type_id: x, attr_id: x}}], CMDB > 2.4.5: deprecated
|
||||
|
||||
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
|
||||
interval = db.Column(db.Integer) # seconds, > 2.4.5: deprecated
|
||||
cron = db.Column(db.String(128))
|
||||
|
||||
extra_option = db.Column(db.JSON)
|
||||
uid = db.Column(db.Integer, index=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):
|
||||
__tablename__ = "c_ad_ci"
|
||||
|
||||
@@ -591,6 +604,36 @@ 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 CIFilterPerms(Model):
|
||||
__tablename__ = "c_ci_filter_perms"
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import json
|
||||
import datetime
|
||||
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
@@ -25,6 +26,8 @@ from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
||||
@@ -87,6 +90,13 @@ 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))
|
||||
|
||||
|
||||
@@ -249,3 +259,21 @@ 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()
|
||||
|
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-05-28 18:05+0800\n"
|
||||
"POT-Creation-Date: 2024-06-20 19:12+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 "You do not have {} permission for resource: {}!"
|
||||
msgid "For resource: {}, you do not have {} permission!"
|
||||
msgstr "您没有资源: {} 的{}权限!"
|
||||
|
||||
#: api/lib/resp_format.py:33
|
||||
@@ -238,241 +238,245 @@ msgstr "因为CI已经存在,不能删除模型"
|
||||
msgid "The inheritance cannot be deleted because the CI already exists"
|
||||
msgstr "因为CI已经存在,不能删除继承关系"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:67
|
||||
#: api/lib/cmdb/resp_format.py:65
|
||||
msgid "The model is inherited and cannot be deleted"
|
||||
msgstr "该模型被继承, 不能删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:68
|
||||
msgid ""
|
||||
"The model cannot be deleted because the model is referenced by the "
|
||||
"relational view {}"
|
||||
msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:69
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
msgid "Model group {} does not exist"
|
||||
msgstr "模型分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:70
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
msgid "Model group {} already exists"
|
||||
msgstr "模型分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:71
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
msgid "Model relationship {} does not exist"
|
||||
msgstr "模型关系 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:72
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
msgid "Attribute group {} already exists"
|
||||
msgstr "属性分组 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:73
|
||||
#: api/lib/cmdb/resp_format.py:74
|
||||
msgid "Attribute group {} does not exist"
|
||||
msgstr "属性分组 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:75
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
msgid "Attribute group <{0}> - attribute <{1}> does not exist"
|
||||
msgstr "属性组<{0}> - 属性<{1}> 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:76
|
||||
#: api/lib/cmdb/resp_format.py:77
|
||||
msgid "The unique constraint already exists!"
|
||||
msgstr "唯一约束已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:78
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
|
||||
msgstr "唯一约束的属性不能是 JSON 和 多值"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:79
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
msgid "Duplicated trigger"
|
||||
msgstr "重复的触发器"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:80
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
msgid "Trigger {} does not exist"
|
||||
msgstr "触发器 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:81
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
msgid "Duplicated reconciliation rule"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:82
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Reconciliation rule {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:92
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
#: api/lib/cmdb/resp_format.py:102
|
||||
msgid "Out of range value, the maximum value is 2147483647"
|
||||
msgstr "超过最大值限制, 最大值是2147483647"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:107
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:110
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:113
|
||||
#: api/lib/cmdb/resp_format.py:114
|
||||
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
|
||||
msgstr "该自动发现规则被模型引用, 不能删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:115
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
msgid "The application of auto-discovery rules cannot be defined repeatedly!"
|
||||
msgstr "自动发现规则的应用不能重复定义!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
msgid "The auto-discovery you want to modify: {} does not exist!"
|
||||
msgstr "您要修改的自动发现: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: api/lib/cmdb/resp_format.py:121
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:122
|
||||
#: api/lib/cmdb/resp_format.py:123
|
||||
msgid "This rule already has auto-discovery instances and cannot be deleted!"
|
||||
msgstr "该规则已经有自动发现的实例, 不能被删除!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:124
|
||||
#: api/lib/cmdb/resp_format.py:125
|
||||
msgid "The default auto-discovery rule is already referenced by model {}!"
|
||||
msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:126
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
msgid "The unique_key method must return a non-empty string!"
|
||||
msgstr "unique_key方法必须返回非空字符串!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:129
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
msgid "The list returned by the attributes method cannot be empty!"
|
||||
msgstr "attributes方法返回的list不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:131
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:134
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
#: api/lib/cmdb/resp_format.py:136
|
||||
msgid "CI filter authorization is currently not supported or query"
|
||||
msgstr "CI过滤授权 暂时不支持 或 查询"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:138
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
msgid "You do not have permission to operate this CI!"
|
||||
msgstr "您没有该CI的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:141
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
#: api/lib/cmdb/resp_format.py:143
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:144
|
||||
#: 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:145
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
#: api/lib/cmdb/resp_format.py:147
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:148
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
msgid "Topology view {} already exists"
|
||||
msgstr "拓扑视图 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
#: api/lib/cmdb/resp_format.py:150
|
||||
msgid "Topology group {} already exists"
|
||||
msgstr "拓扑视图分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:151
|
||||
#: api/lib/cmdb/resp_format.py:152
|
||||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
import json
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
from flask import abort
|
||||
@@ -10,15 +11,19 @@ from flask_login import current_user
|
||||
|
||||
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 AutoDiscoveryCounterCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryExecHistoryCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryHTTPManager
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleSyncHistoryCRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoverySNMPManager
|
||||
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP
|
||||
from api.lib.cmdb.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.cmdb.search.ci import search as ci_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
|
||||
@@ -37,14 +42,19 @@ class AutoDiscoveryRuleView(APIView):
|
||||
|
||||
rebuild = False
|
||||
exists = {i['name'] for i in res}
|
||||
for i in DEFAULT_HTTP:
|
||||
for i in copy.deepcopy(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)
|
||||
@@ -98,7 +108,8 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
|
||||
|
||||
|
||||
class AutoDiscoveryRuleHTTPView(APIView):
|
||||
url_prefix = ("/adr/http/<string:name>/categories", "/adr/http/<string:name>/attributes",
|
||||
url_prefix = ("/adr/http/<string:name>/categories",
|
||||
"/adr/http/<string:name>/attributes",
|
||||
"/adr/snmp/<string:name>/attributes")
|
||||
|
||||
def get(self, name):
|
||||
@@ -106,16 +117,21 @@ class AutoDiscoveryRuleHTTPView(APIView):
|
||||
return self.jsonify(AutoDiscoverySNMPManager.get_attributes())
|
||||
|
||||
if "attributes" in request.url:
|
||||
category = request.values.get('category')
|
||||
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, category))
|
||||
resource = request.values.get('resource')
|
||||
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, resource))
|
||||
|
||||
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
|
||||
|
||||
|
||||
class AutoDiscoveryCITypeView(APIView):
|
||||
url_prefix = ("/adt/ci_types/<int:type_id>", "/adt/<int:adt_id>")
|
||||
url_prefix = ("/adt/ci_types/<int:type_id>",
|
||||
"/adt/ci_types/<int:type_id>/attributes",
|
||||
"/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'):
|
||||
@@ -146,6 +162,27 @@ 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")
|
||||
|
||||
@@ -220,9 +257,8 @@ class AutoDiscoveryRuleSyncView(APIView):
|
||||
oneagent_id = request.values.get('oneagent_id')
|
||||
last_update_at = request.values.get('last_update_at')
|
||||
|
||||
query = "{},oneagent_id:{}".format(oneagent_name, oneagent_id)
|
||||
current_app.logger.info(query)
|
||||
s = search(query)
|
||||
query = "oneagent_id:{}".format(oneagent_id)
|
||||
s = ci_search(query)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
@@ -230,7 +266,77 @@ class AutoDiscoveryRuleSyncView(APIView):
|
||||
current_app.logger.error(traceback.format_exc())
|
||||
return abort(400, str(e))
|
||||
|
||||
ci_id = response and response[0]["_id"]
|
||||
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(ci_id, oneagent_id, last_update_at)
|
||||
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)
|
||||
|
||||
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))
|
||||
numfound, res = AutoDiscoveryExecHistoryCRUD.search(page=page,
|
||||
page_size=page_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))
|
||||
|
@@ -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_id = request.values.get("parent_attr_id")
|
||||
child_attr_id = request.values.get("child_attr_id")
|
||||
parent_attr_ids = request.values.get("parent_attr_ids")
|
||||
child_attr_ids = request.values.get("child_attr_ids")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
|
||||
parent_attr_id, child_attr_id)
|
||||
parent_attr_ids, child_attr_ids)
|
||||
|
||||
return self.jsonify(ctr_id=ctr_id)
|
||||
|
||||
|
@@ -31,6 +31,7 @@ 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
|
||||
|
@@ -59,7 +59,7 @@
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vuedraggable": "^2.23.0",
|
||||
"vuex": "^3.1.1",
|
||||
"vxe-table": "3.6.9",
|
||||
"vxe-table": "3.7.10",
|
||||
"vxe-table-plugin-export-xlsx": "2.0.0",
|
||||
"xe-utils": "3",
|
||||
"xlsx": "0.15.0",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1716896994700') format('woff2'),
|
||||
url('iconfont.woff?t=1716896994700') format('woff'),
|
||||
url('iconfont.ttf?t=1716896994700') format('truetype');
|
||||
src: url('iconfont.woff2?t=1718872392430') format('woff2'),
|
||||
url('iconfont.woff?t=1718872392430') format('woff'),
|
||||
url('iconfont.ttf?t=1718872392430') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,214 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.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";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,370 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"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",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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: '' }
|
||||
]
|
||||
}
|
||||
|
@@ -160,7 +160,7 @@ export default {
|
||||
landline: 'landline',
|
||||
zipCode: 'zip code',
|
||||
IDCard: 'ID card',
|
||||
ip: 'IP',
|
||||
ip: 'IPv4',
|
||||
email: 'email',
|
||||
link: 'link',
|
||||
monetaryAmount: 'monetary amount',
|
||||
|
@@ -160,7 +160,7 @@ export default {
|
||||
landline: '座机',
|
||||
zipCode: '邮政编码',
|
||||
IDCard: '身份证号',
|
||||
ip: 'IP地址',
|
||||
ip: 'IPv4地址',
|
||||
email: '邮箱',
|
||||
link: '链接',
|
||||
monetaryAmount: '货币金额',
|
||||
|
@@ -46,10 +46,10 @@ export function getHttpAttributes(name, params) {
|
||||
}
|
||||
|
||||
export function getSnmpAttributes(name) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/snmp/${name}/attributes`,
|
||||
method: 'GET',
|
||||
})
|
||||
return axios({
|
||||
url: `/v0.1/adr/snmp/${name}/attributes`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeDiscovery(type_id) {
|
||||
@@ -118,3 +118,65 @@ 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
|
||||
})
|
||||
}
|
||||
|
196
cmdb-ui/src/modules/cmdb/components/attrMapTable/index.vue
Normal file
196
cmdb-ui/src/modules/cmdb/components/attrMapTable/index.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<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>
|
||||
<vxe-select
|
||||
filterable
|
||||
clearable
|
||||
v-model="row.attr"
|
||||
type="text"
|
||||
:options="ciTypeAttributes"
|
||||
transfer
|
||||
:placeholder="$t('cmdb.ciType.attrMapTableAttrPlaceholder')"
|
||||
></vxe-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 !== '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>
|
||||
export default {
|
||||
name: 'AttrMapTable',
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
ciTypeAttributes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
ruleType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
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%;
|
||||
}
|
||||
|
||||
&-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>
|
@@ -0,0 +1,39 @@
|
||||
<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>
|
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="http-ad-category">
|
||||
<div class="http-ad-category-preview" v-if="currentCate">
|
||||
<div class="category-side">
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.category"
|
||||
class="category-side-item"
|
||||
>
|
||||
<div class="category-side-title">{{ category.category }}</div>
|
||||
<div class="category-side-children">
|
||||
<div
|
||||
v-for="item in category.items"
|
||||
:key="item"
|
||||
:class="['category-side-children-item', item === currentCate ? 'category-side-children-item_active' : '']"
|
||||
@click="clickCategory(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</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 in filterCategories"
|
||||
:key="category.category"
|
||||
class="category-item"
|
||||
>
|
||||
<div class="category-title">{{ category.category }}</div>
|
||||
<div class="category-children">
|
||||
<div
|
||||
v-for="item in category.items"
|
||||
:key="item"
|
||||
:class="['category-children-item', item === currentCate ? 'category-children-item_active' : '']"
|
||||
@click="clickCategory(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</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: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchValue: ''
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</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(:first-child) {
|
||||
margin-top: 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;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,68 +1,45 @@
|
||||
<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 class="http-snmp-ad">
|
||||
<HttpADCategory
|
||||
v-if="!isEdit && ruleType === 'http'"
|
||||
:categories="categories"
|
||||
:currentCate="currentCate"
|
||||
:tableData="tableData"
|
||||
@clickCategory="setCurrentCate"
|
||||
/>
|
||||
<template v-else>
|
||||
<a-select v-if="ruleType === 'http'" :style="{ marginBottom: '10px' }" 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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery'
|
||||
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
|
||||
import ADPreviewTable from './adPreviewTable.vue'
|
||||
import HttpADCategory from './httpADCategory.vue'
|
||||
|
||||
export default {
|
||||
name: 'HttpSnmpAD',
|
||||
components: {
|
||||
AttrMapTable,
|
||||
ADPreviewTable,
|
||||
HttpADCategory
|
||||
},
|
||||
props: {
|
||||
ruleName: {
|
||||
type: String,
|
||||
@@ -88,10 +65,15 @@ export default {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categories: [],
|
||||
categoriesSelect: [],
|
||||
currentCate: '',
|
||||
tableData: [],
|
||||
}
|
||||
@@ -115,7 +97,7 @@ export default {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => {
|
||||
getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { resource: newVal }).then((res) => {
|
||||
if (this.isEdit) {
|
||||
this.formatTableData(res)
|
||||
} else {
|
||||
@@ -131,7 +113,7 @@ export default {
|
||||
this.currentCate = ''
|
||||
this.$nextTick(() => {
|
||||
const { ruleType, ruleName } = newVal
|
||||
if (ruleType === 'snmp' && ruleName) {
|
||||
if (['snmp'].includes(ruleType) && ruleName) {
|
||||
getSnmpAttributes(ruleName).then((res) => {
|
||||
if (this.isEdit) {
|
||||
this.formatTableData(res)
|
||||
@@ -143,8 +125,15 @@ export default {
|
||||
if (ruleType === 'http' && ruleName) {
|
||||
getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => {
|
||||
this.categories = res
|
||||
if (res && res.length) {
|
||||
this.currentCate = res[0]
|
||||
const categoriesSelect = []
|
||||
res.forEach((category) => {
|
||||
if (category?.items?.length) {
|
||||
categoriesSelect.push(...category.items)
|
||||
}
|
||||
})
|
||||
this.categoriesSelect = categoriesSelect
|
||||
if (this.isEdit && categoriesSelect?.length) {
|
||||
this.currentCate = categoriesSelect[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -181,12 +170,16 @@ export default {
|
||||
})
|
||||
},
|
||||
getTableData() {
|
||||
const $table = this.$refs.xTable
|
||||
const $table = this.$refs.attrMapTable
|
||||
const { fullData } = $table.getTableData()
|
||||
return fullData || []
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style>
|
||||
.http-snmp-ad {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
207
cmdb-ui/src/modules/cmdb/components/nodeSetting/index.vue
Normal file
207
cmdb-ui/src/modules/cmdb/components/nodeSetting/index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="node-setting-wrap">
|
||||
<a-row v-for="(node) in nodes" :key="node.id">
|
||||
<a-col :span="6">
|
||||
<a-form-item :label="$t('cmdb.ciType.nodeSettingIp')">
|
||||
<a-input
|
||||
allowClear
|
||||
v-decorator="[
|
||||
`node_ip_${node.id}`,
|
||||
{
|
||||
rules: [
|
||||
{ required: false, message: $t('cmdb.ciType.nodeSettingIpTip') },
|
||||
{
|
||||
pattern:
|
||||
'^(?:(?: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: $t('cmdb.ciType.nodeSettingIpTip1'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('cmdb.ciType.nodeSettingIpTip')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-form-item :label="$t('cmdb.ciType.nodeSettingCommunity')">
|
||||
<a-input
|
||||
allowClear
|
||||
v-decorator="[
|
||||
`node_community_${node.id}`,
|
||||
{
|
||||
rules: [{ required: false, message: $t('cmdb.ciType.nodeSettingCommunityTip') }],
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('cmdb.ciType.nodeSettingCommunityTip')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="5">
|
||||
<a-form-item :label="$t('cmdb.ciType.nodeSettingVersion')">
|
||||
<a-select
|
||||
v-decorator="[
|
||||
`node_version_${node.id}`,
|
||||
{
|
||||
rules: [{ required: false, message: $t('cmdb.ciType.nodeSettingVersionTip') }],
|
||||
},
|
||||
]"
|
||||
: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-option value="3">
|
||||
v3
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<div class="action">
|
||||
<a @click="() => copyNode(node.id)">
|
||||
<a-icon type="copy" />
|
||||
</a>
|
||||
<a @click="() => removeNode(node.id, 1)">
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a @click="addNode">
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</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: '',
|
||||
version: '',
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
[`node_ip_${newNode.id}`]: newNode.ip,
|
||||
[`node_community_${newNode.id}`]: newNode.community,
|
||||
[`node_version_${newNode.id}`]: newNode.version,
|
||||
})
|
||||
})
|
||||
},
|
||||
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 newNode = {
|
||||
id: uuidv4(),
|
||||
ip: this.form.getFieldValue(`node_ip_${id}`),
|
||||
community: this.form.getFieldValue(`node_community_${id}`),
|
||||
version: this.form.getFieldValue(`node_version_${id}`),
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
[`node_ip_${newNode.id}`]: newNode.ip,
|
||||
[`node_community_${newNode.id}`]: newNode.community,
|
||||
[`node_version_${newNode.id}`]: newNode.version,
|
||||
})
|
||||
})
|
||||
},
|
||||
getInfoValuesFromForm(values) {
|
||||
return this.nodes.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
ip: values[`node_ip_${item.id}`],
|
||||
community: values[`node_community_${item.id}`],
|
||||
version: values[`node_version_${item.id}`],
|
||||
}
|
||||
})
|
||||
},
|
||||
setNodeField() {
|
||||
if (this.nodes && this.nodes.length) {
|
||||
this.nodes.forEach((item) => {
|
||||
this.form.setFieldsValue({
|
||||
[`node_ip_${item.id}`]: item.ip,
|
||||
[`node_community_${item.id}`]: item.community,
|
||||
[`node_version_${item.id}`]: item.version,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
getNodeValue() {
|
||||
const values = this.form.getFieldsValue()
|
||||
return this.getInfoValuesFromForm(values)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.node-setting-wrap {
|
||||
margin-left: 17px;
|
||||
|
||||
.ant-row {
|
||||
// display: flex;
|
||||
|
||||
/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>
|
@@ -1,317 +1,319 @@
|
||||
<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>
|
||||
<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>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -167,16 +167,20 @@
|
||||
#default="{ row }"
|
||||
>
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
<a
|
||||
v-else-if="col.is_link && row[col.field]"
|
||||
:href="
|
||||
row[col.field].startsWith('http') || row[col.field].startsWith('https')
|
||||
? `${row[col.field]}`
|
||||
: `http://${row[col.field]}`
|
||||
"
|
||||
target="_blank"
|
||||
>{{ row[col.field] }}</a
|
||||
>
|
||||
<template v-else-if="col.is_link && row[col.field]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="col.is_password && row[col.field]"
|
||||
:ci_id="row._id"
|
||||
|
@@ -163,6 +163,12 @@ 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'),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
|
@@ -269,7 +269,7 @@ export default {
|
||||
return { nodes, edges }
|
||||
},
|
||||
exsited_ci() {
|
||||
const _exsited_ci = [this.typeId]
|
||||
const _exsited_ci = [this.ciId]
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
if (this.firstCIs[parent.name]) {
|
||||
this.firstCIs[parent.name].forEach((parentCi) => {
|
||||
|
@@ -100,35 +100,37 @@ 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)
|
||||
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],
|
||||
},
|
||||
],
|
||||
})
|
||||
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],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
newEdges.push({
|
||||
id: `${r._id}`,
|
||||
|
@@ -29,7 +29,7 @@
|
||||
? attr.default.default
|
||||
: attr.default.default.split(',')
|
||||
: attr.default.default
|
||||
: null,
|
||||
: attr.is_list ? [] : null,
|
||||
},
|
||||
]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
|
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<a-modal width="800px" :visible="visible" @ok="handleOK" @cancel="handleCancel" :closable="false">
|
||||
<Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" />
|
||||
<Discovery
|
||||
:isSelected="true"
|
||||
:style="{ maxHeight: '75vh', overflow: 'auto' }"
|
||||
v-if="visible"
|
||||
/>
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
|
||||
@@ -14,7 +18,7 @@
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Discovery from '../discovery'
|
||||
import { postCITypeDiscovery } from '../../api/discovery'
|
||||
|
||||
export default {
|
||||
name: 'ADModal',
|
||||
components: { Discovery },
|
||||
@@ -49,20 +53,17 @@ export default {
|
||||
},
|
||||
async handleOK() {
|
||||
if (this.selectedIds && this.selectedIds.length) {
|
||||
const promises = this.selectedIds.map(({ id, type }) => {
|
||||
return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 })
|
||||
const adCITypeList = this.selectedIds.map((item, index) => {
|
||||
return {
|
||||
adr_id: item.id,
|
||||
id: new Date().getTime() + index,
|
||||
extra_option: {
|
||||
alias: ''
|
||||
},
|
||||
isClient: true,
|
||||
}
|
||||
})
|
||||
await Promise.all(promises)
|
||||
.then((res) => {
|
||||
this.getCITypeDiscovery(res[0].id)
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.getCITypeDiscovery()
|
||||
})
|
||||
.finally(() => {
|
||||
this.handleCancel()
|
||||
})
|
||||
this.$emit('pushCITypeList', adCITypeList)
|
||||
}
|
||||
this.handleCancel()
|
||||
},
|
||||
|
105
cmdb-ui/src/modules/cmdb/views/ci_types/adTab.vue
Normal file
105
cmdb-ui/src/modules/cmdb/views/ci_types/adTab.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<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>
|
@@ -1,37 +1,28 @@
|
||||
<template>
|
||||
<div class="attr-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||
<div v-if="adCITypeList && adCITypeList.length">
|
||||
<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>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
@@ -54,28 +45,41 @@
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
<ADModal ref="adModal" :CITypeId="CITypeId" @addPlugin="openEditDrawer(null, 'add', 'agent')" />
|
||||
<ADModal
|
||||
ref="adModal"
|
||||
:CITypeId="CITypeId"
|
||||
@pushCITypeList="pushCITypeList"
|
||||
@addPlugin="openEditDrawer(null, 'add', 'plugin')"
|
||||
/>
|
||||
<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 },
|
||||
components: {
|
||||
ADModal,
|
||||
AttrADTabpane,
|
||||
EditDrawer,
|
||||
AttrADTabs
|
||||
},
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -86,7 +90,8 @@ export default {
|
||||
return {
|
||||
ciTypeAttributes: [],
|
||||
adrList: [],
|
||||
adCITypeList: [],
|
||||
serviceCITYpeList: [],
|
||||
clientCITypeList: [],
|
||||
currentTab: '',
|
||||
deletePlugin: false,
|
||||
}
|
||||
@@ -95,6 +100,13 @@ export default {
|
||||
...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 {
|
||||
@@ -106,7 +118,7 @@ export default {
|
||||
handler() {
|
||||
if (this.currentTab) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
|
||||
this.$refs[`attrAdTabpaneRef`].init()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -121,7 +133,7 @@ export default {
|
||||
})
|
||||
if (this.currentTab) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
|
||||
this.$refs[`attrAdTabpaneRef`].init()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -134,15 +146,34 @@ export default {
|
||||
},
|
||||
async getCITypeDiscovery(currentTab) {
|
||||
await getCITypeDiscovery(this.CITypeId).then((res) => {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
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) {
|
||||
@@ -152,52 +183,115 @@ export default {
|
||||
return _find[`${params}`]
|
||||
}
|
||||
},
|
||||
async deleteADT(e, item) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
async deleteADT(item) {
|
||||
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) => (
|
||||
<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 = ''
|
||||
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)
|
||||
}
|
||||
}
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
that.getCITypeDiscovery()
|
||||
if (that.deletePlugin) {
|
||||
await deleteDiscovery(item.adr_id).finally(() => {
|
||||
that.deletePlugin = false
|
||||
})
|
||||
}
|
||||
that.deletePlugin = false
|
||||
})
|
||||
} 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
|
||||
})
|
||||
}
|
||||
},
|
||||
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)
|
||||
let res
|
||||
if (_idx < 0) {
|
||||
res = await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
|
||||
}
|
||||
await this.getDiscovery()
|
||||
await this.getCITypeDiscovery(res?.id ?? undefined)
|
||||
if (_idx < 0) {
|
||||
const ciType = {
|
||||
adr_id: adr.id,
|
||||
id: new Date().getTime(),
|
||||
extra_option: {
|
||||
alias: ''
|
||||
},
|
||||
isClient: true,
|
||||
}
|
||||
this.pushCITypeList([ciType])
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
|
||||
this.$refs[`attrAdTabpaneRef`].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 params = {
|
||||
extra_option: {
|
||||
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>
|
||||
@@ -206,6 +300,7 @@ export default {
|
||||
.attr-ad {
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
|
||||
.attr-ad-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
@@ -216,7 +311,13 @@ 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;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }">
|
||||
<div class="attr-ad-tab-pane" :style="{ height: `${windowHeight - 254}px` }">
|
||||
<a
|
||||
v-if="!adrIsInner"
|
||||
:style="{ position: 'absolute', right: 0, top: 0 }"
|
||||
@click="
|
||||
() => {
|
||||
$emit('openEditDrawer', currentAdr, 'edit', 'agent')
|
||||
$emit('openEditDrawer', currentAdr, 'edit', 'plugin')
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -14,75 +14,43 @@
|
||||
<span>{{ $t('edit') }}</span>
|
||||
</a-space>
|
||||
</a>
|
||||
<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' }"
|
||||
>
|
||||
<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-attributemap-main">
|
||||
<AttrMapTable
|
||||
v-if="adrType === '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"
|
||||
:style="{ marginBottom: '20px' }"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="adrType === 'snmp'">
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.nodeConfig') }}</div>
|
||||
<a-form :form="form3" layout="inline" class="attr-ad-snmp-form">
|
||||
<NodeSetting ref="nodeSetting" :initNodes="nodes" :form="form3" />
|
||||
</a-form>
|
||||
</template>
|
||||
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
|
||||
<a-form-model :model="form" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }">
|
||||
<a-form-model
|
||||
:model="form"
|
||||
:labelCol="labelCol"
|
||||
labelAlign="left"
|
||||
:wrapperCol="{ span: 14 }"
|
||||
class="attr-ad-form"
|
||||
>
|
||||
<a-form-model-item :label="$t('cmdb.ciType.adExecTarget')">
|
||||
<CustomRadio v-model="agent_type" :radioList="agentTypeRadioList">
|
||||
<a-input
|
||||
@@ -103,16 +71,60 @@
|
||||
</a-input>
|
||||
</CustomRadio>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('cmdb.ciType.adAutoInLib')">
|
||||
<a-form-model-item
|
||||
:labelCol="labelCol"
|
||||
:label="$t('cmdb.ciType.adAutoInLib')"
|
||||
:extra="$t('cmdb.ciType.adAutoInLibTip')"
|
||||
>
|
||||
<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')"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
<template v-if="adrType === 'http'">
|
||||
<div class="attr-ad-header attr-ad-header-margin">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
|
||||
<div class="public-cloud-info">{{ $t('cmdb.ciType.cloudAccessKeyTip') }}</div>
|
||||
<a-form-model
|
||||
:model="form2"
|
||||
labelAlign="left"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="{ span: 6 }"
|
||||
class="attr-ad-form"
|
||||
>
|
||||
<a-form-model-item label="key">
|
||||
<a-input-password v-model="form2.key" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="secret">
|
||||
<a-input-password v-model="form2.secret" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
|
||||
<AttrADTest
|
||||
:adtId="currentAdt.id"
|
||||
/>
|
||||
|
||||
<div class="attr-ad-footer">
|
||||
<a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
|
||||
@@ -125,14 +137,26 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { mapState } from 'vuex'
|
||||
import Vcrontab from '@/components/Crontab'
|
||||
import { putCITypeDiscovery } from '../../api/discovery'
|
||||
import { putCITypeDiscovery, postCITypeDiscovery } from '../../api/discovery'
|
||||
|
||||
import HttpSnmpAD from '../../components/httpSnmpAD'
|
||||
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
|
||||
import CMDBExprDrawer from '@/components/CMDBExprDrawer'
|
||||
import MonitorNodeSetting from '@/components/MonitorNodeSetting'
|
||||
import NodeSetting from '@/modules/cmdb/components/nodeSetting/index.vue'
|
||||
import AttrADTest from './attrADTest.vue'
|
||||
import { Popover } from 'element-ui'
|
||||
|
||||
export default {
|
||||
name: 'AttrADTabpane',
|
||||
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
|
||||
components: {
|
||||
Vcrontab,
|
||||
HttpSnmpAD,
|
||||
CMDBExprDrawer,
|
||||
NodeSetting,
|
||||
AttrMapTable,
|
||||
AttrADTest,
|
||||
ElPopover: Popover
|
||||
},
|
||||
props: {
|
||||
adr_id: {
|
||||
type: Number,
|
||||
@@ -158,6 +182,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -171,7 +199,7 @@ export default {
|
||||
key: '',
|
||||
secret: '',
|
||||
},
|
||||
interval: 'interval', // interval cron
|
||||
interval: 'cron', // interval cron
|
||||
cron: '',
|
||||
intervalValue: 3,
|
||||
agent_type: 'agent_id',
|
||||
@@ -184,50 +212,57 @@ export default {
|
||||
},
|
||||
],
|
||||
form3: this.$form.createForm(this, { name: 'snmp_form' }),
|
||||
alias: '',
|
||||
cronVisible: false,
|
||||
uniqueKey: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
userRoles: (state) => state.user.roles,
|
||||
user: (state) => state.user,
|
||||
}),
|
||||
adrType() {
|
||||
return this.currentAdr.type
|
||||
return this.currentAdr?.type || ''
|
||||
},
|
||||
adrName() {
|
||||
return this.currentAdr.name
|
||||
return this.currentAdr?.name || ''
|
||||
},
|
||||
adrIsInner() {
|
||||
return this.currentAdr.is_inner
|
||||
return this.currentAdr?.is_inner || ''
|
||||
},
|
||||
agentTypeRadioList() {
|
||||
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 [
|
||||
const radios = [
|
||||
{ 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 === 'agent') {
|
||||
radios.unshift({ value: 'all', label: this.$t('cmdb.ciType.allNodes') })
|
||||
}
|
||||
|
||||
return radios
|
||||
},
|
||||
radioList() {
|
||||
return [
|
||||
{ value: 'interval', label: this.$t('cmdb.ciType.byInterval') },
|
||||
// { value: 'cron', label: '按cron', layout: 'vertical' },
|
||||
{ value: 'cron', label: '按cron', layout: 'vertical' },
|
||||
]
|
||||
},
|
||||
labelCol() {
|
||||
const span = this.$i18n.locale === 'en' ? 4 : 2
|
||||
return {
|
||||
span
|
||||
}
|
||||
}
|
||||
},
|
||||
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.alias = _findADT?.extra_option?.alias ?? ''
|
||||
this.uniqueKey = _find?.unique_key ?? ''
|
||||
|
||||
if (this.adrType === 'http') {
|
||||
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
|
||||
this.form2 = {
|
||||
@@ -237,7 +272,7 @@ export default {
|
||||
this.$refs.httpSnmpAd.setCurrentCate(category)
|
||||
}
|
||||
if (this.adrType === 'snmp') {
|
||||
this.nodes = _findADT?.extra_option?.nodes ?? [
|
||||
this.nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
|
||||
{
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
@@ -246,9 +281,9 @@ export default {
|
||||
},
|
||||
]
|
||||
this.$nextTick(() => {
|
||||
this.$refs.monitorNodeSetting.initNodesFunc()
|
||||
this.$refs.nodeSetting.initNodesFunc()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.monitorNodeSetting.setNodeField()
|
||||
this.$refs.nodeSetting.setNodeField()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -283,23 +318,16 @@ export default {
|
||||
} else {
|
||||
this.agent_type = this.agentTypeRadioList[0].value
|
||||
}
|
||||
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 || ''
|
||||
|
||||
this.interval = 'cron'
|
||||
this.cron = _findADT?.cron || ''
|
||||
},
|
||||
|
||||
crontabFill(cron) {
|
||||
this.cron = cron
|
||||
},
|
||||
handleSave() {
|
||||
const { currentAdt, alias } = this
|
||||
const { currentAdt } = this
|
||||
let params
|
||||
if (this.adrType === 'http') {
|
||||
params = {
|
||||
@@ -311,11 +339,11 @@ export default {
|
||||
}
|
||||
if (this.adrType === 'snmp') {
|
||||
params = {
|
||||
extra_option: { nodes: this.$refs.monitorNodeSetting?.getNodeValue() ?? [] },
|
||||
extra_option: { nodes: this.$refs.nodeSetting?.getNodeValue() ?? [] },
|
||||
}
|
||||
}
|
||||
if (this.adrType === 'agent') {
|
||||
const $table = this.$refs.xTable
|
||||
const $table = this.$refs.attrMapTable
|
||||
const { fullData: _tableData } = $table.getTableData()
|
||||
const attributes = {}
|
||||
_tableData.forEach((td) => {
|
||||
@@ -340,17 +368,14 @@ export default {
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
if (this.interval === 'cron') {
|
||||
this.$refs.cronTab.submitFill()
|
||||
}
|
||||
|
||||
params = {
|
||||
...params,
|
||||
...this.form,
|
||||
type_id: this.CITypeId,
|
||||
adr_id: currentAdt.adr_id,
|
||||
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) {
|
||||
@@ -358,6 +383,7 @@ 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) {
|
||||
@@ -365,16 +391,29 @@ export default {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (params.extra_option) {
|
||||
params.extra_option.alias = alias
|
||||
} else {
|
||||
params.extra_option = {}
|
||||
params.extra_option.alias = alias
|
||||
|
||||
if (!this.cron) {
|
||||
this.$message.error(this.$t('cmdb.ciType.cronRequiredTip'))
|
||||
return
|
||||
}
|
||||
|
||||
if (currentAdt?.isClient) {
|
||||
if (currentAdt?.extra_option) {
|
||||
params.extra_option = {
|
||||
...(params?.extra_option || {}),
|
||||
...(currentAdt?.extra_option || {})
|
||||
}
|
||||
}
|
||||
postCITypeDiscovery(this.CITypeId, params).then((res) => {
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.$emit('handleSave', res.id)
|
||||
})
|
||||
} else {
|
||||
putCITypeDiscovery(currentAdt.id, params).then((res) => {
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.$emit('handleSave', res.id)
|
||||
})
|
||||
}
|
||||
putCITypeDiscovery(currentAdt.id, params).then((res) => {
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.$emit('handleSave')
|
||||
})
|
||||
},
|
||||
handleOpenCmdb() {
|
||||
this.$refs.cmdbDrawer.open()
|
||||
@@ -385,11 +424,41 @@ export default {
|
||||
query_expr: `${text}`,
|
||||
}
|
||||
},
|
||||
hideCron() {
|
||||
this.cronVisible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="less" scoped>
|
||||
.attr-ad-tab-pane {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.attr-ad-snmp-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
|
183
cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabs.vue
Normal file
183
cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabs.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<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>
|
205
cmdb-ui/src/modules/cmdb/views/ci_types/attrADTest.vue
Normal file
205
cmdb-ui/src/modules/cmdb/views/ci_types/attrADTest.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<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>
|
@@ -180,6 +180,10 @@ export default {
|
||||
label: this.$t('cmdb.ciType.isIndex'),
|
||||
property: 'is_index',
|
||||
},
|
||||
{
|
||||
label: this.$t('cmdb.ciType.isDynamic'),
|
||||
property: 'is_dynamic',
|
||||
},
|
||||
]
|
||||
},
|
||||
inherited() {
|
||||
|
@@ -157,33 +157,6 @@
|
||||
</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"
|
||||
@@ -196,7 +169,7 @@
|
||||
>{{ $t('cmdb.ciType.index') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.indexTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
@@ -217,10 +190,37 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<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:3px;left:-17px;color:#2f54eb;"
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
@@ -295,6 +295,37 @@
|
||||
/>
|
||||
</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)">
|
||||
@@ -534,6 +565,7 @@ 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)
|
||||
|
@@ -150,42 +150,19 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
|
||||
<a-form-item
|
||||
:hidden="currentValueType === '2' ? false : true"
|
||||
: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="6" v-if="currentValueType === '2'">
|
||||
<a-form-item :label-col="horizontalFormItemLayout.labelCol" :wrapper-col="horizontalFormItemLayout.wrapperCol">
|
||||
<template slot="label">
|
||||
<span
|
||||
style="position:relative;white-space:pre;"
|
||||
>{{ $t('cmdb.ciType.index') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.indexTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
@@ -206,10 +183,37 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
|
||||
<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
|
||||
@@ -217,7 +221,7 @@
|
||||
>{{ $t('cmdb.ciType.defaultShow') }}
|
||||
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
style="position:absolute;top:2px;left:-17px;color:#2f54eb;"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
@click="
|
||||
@@ -284,6 +288,37 @@
|
||||
/>
|
||||
</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', initialValue: currentValueType === '6' ? true: false }]"
|
||||
/>
|
||||
</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)">
|
||||
@@ -404,8 +439,8 @@ export default {
|
||||
}
|
||||
|
||||
console.log(values)
|
||||
const { is_required, default_show, default_value } = values
|
||||
const data = { is_required, default_show }
|
||||
const { is_required, default_show, default_value, is_dynamic } = values
|
||||
const data = { is_required, default_show, is_dynamic }
|
||||
delete values.is_required
|
||||
delete values.default_show
|
||||
if (values.value_type === '0' && default_value) {
|
||||
|
@@ -5,21 +5,20 @@
|
||||
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" :tab="$t('cmdb.ciType.relation')">
|
||||
<RelationTable :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
<RelationTable v-if="activeKey === '2'" :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" :tab="$t('cmdb.ciType.trigger')">
|
||||
<a-tab-pane key="3" :tab="$t('cmdb.ciType.autoDiscoveryTab')">
|
||||
<ADTab v-if="activeKey === '3'" :CITypeId="CITypeId"></ADTab>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" :tab="$t('cmdb.ciType.trigger')">
|
||||
<TriggerTable ref="triggerTable" :CITypeId="CITypeId"></TriggerTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" :tab="$t('cmdb.ciType.attributeAD')">
|
||||
<AttrAD :CITypeId="CITypeId"></AttrAD>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" :tab="$t('cmdb.ciType.relationAD')">
|
||||
<RelationAD :CITypeId="CITypeId"></RelationAD>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
|
||||
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
||||
<div class="citype-detail-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
<template v-if="activeKey === '6'">
|
||||
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
|
||||
<div class="citype-detail-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
@@ -29,8 +28,7 @@
|
||||
import AttributesTable from './attributesTable'
|
||||
import RelationTable from './relationTable'
|
||||
import TriggerTable from './triggerTable.vue'
|
||||
import AttrAD from './attrAD.vue'
|
||||
import RelationAD from './relationAD.vue'
|
||||
import ADTab from './adTab.vue'
|
||||
import GrantComp from '../../components/cmdbGrant/grantComp.vue'
|
||||
|
||||
export default {
|
||||
@@ -39,8 +37,7 @@ export default {
|
||||
AttributesTable,
|
||||
RelationTable,
|
||||
TriggerTable,
|
||||
AttrAD,
|
||||
RelationAD,
|
||||
ADTab,
|
||||
GrantComp,
|
||||
},
|
||||
props: {
|
||||
@@ -67,7 +64,7 @@ export default {
|
||||
if (activeKey === '1') {
|
||||
this.$refs.attributesTable.getCITypeGroupData()
|
||||
}
|
||||
if (activeKey === '3') {
|
||||
if (activeKey === '5') {
|
||||
this.$refs.triggerTable.getTableData()
|
||||
}
|
||||
})
|
||||
|
@@ -6,7 +6,12 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="script" :disabled="!canDefineComputed">
|
||||
<span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
|
||||
<codemirror style="z-index: 9999" :options="cmOptions" v-model="compute_script"></codemirror>
|
||||
<codemirror
|
||||
style="z-index: 9999"
|
||||
:options="cmOptions"
|
||||
v-model="compute_script"
|
||||
@input="onCodeChange"
|
||||
></codemirror>
|
||||
</a-tab-pane>
|
||||
<template slot="tabBarExtraContent" v-if="showCalcComputed">
|
||||
<a-button type="primary" size="small" @click="handleCalcComputed">
|
||||
@@ -49,8 +54,26 @@ export default {
|
||||
height: '200px',
|
||||
theme: 'monokai',
|
||||
tabSize: 4,
|
||||
lineWrapping: true,
|
||||
indentUnit: 4,
|
||||
lineWrapping: false,
|
||||
readOnly: !this.canDefineComputed,
|
||||
extraKeys: {
|
||||
Tab: (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
cm.replaceSelection(Array(cm.getOption('indentUnit') + 1).join(' '), 'end', '+input')
|
||||
}
|
||||
},
|
||||
'Shift-Tab': (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('subtract')
|
||||
} else {
|
||||
const cursor = cm.getCursor()
|
||||
cm.setCursor({ line: cursor.line, ch: cursor.ch - 4 })
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -83,6 +106,9 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
onCodeChange(v) {
|
||||
this.compute_script = v.replace('\t', ' ')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,95 +1,124 @@
|
||||
<template>
|
||||
<div class="relation-ad" :style="{ height: `${windowHeight - 130}px` }">
|
||||
<div class="relation-ad-item" v-for="item in relationList" :key="item.id">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '200px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
v-model="item.attrName"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeADTAttributes"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.selectAttributes')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.name,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div :title="node.label" slot="option-label" slot-scope="{ node }">
|
||||
<div>{{ node.label }}</div>
|
||||
<div :style="{ fontSize: '12px', color: '#cbcbcb', lineHeight: '12px' }">{{ node.raw.desc }}</div>
|
||||
</div>
|
||||
</treeselect>
|
||||
<a><a-icon type="swap"/></a>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '200px', margin: '0 10px', '--custom-height': '32px' }"
|
||||
v-model="item.type_name"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeGroup"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.selectCIType')"
|
||||
:disableBranchNodes="true"
|
||||
@select="changeType(item)"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name || $t('other'),
|
||||
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>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '200px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
v-model="item.attr_name"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="item.attributes"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.selectAttributes')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
title: node.alias || node.name,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<div class="relation-ad" :style="{ height: `${windowHeight - 200}px` }">
|
||||
<div class="relation-ad-table-tip">
|
||||
<ops-icon class="relation-ad-table-tip-icon" type="cmdb-prompt" />
|
||||
<span class="relation-ad-table-tip-text">1. {{ $t('cmdb.ciType.relationADTip') }}</span>
|
||||
<span class="relation-ad-table-tip-text">2. {{ $t('cmdb.ciType.relationADTip2') }}</span>
|
||||
<span class="relation-ad-table-tip-text">3. {{ $t('cmdb.ciType.relationADTip3') }}</span>
|
||||
</div>
|
||||
<div class="relation-ad-footer">
|
||||
<a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
|
||||
<!-- <div class="relation-ad-tip">{{ $t('cmdb.ciType.relationADTip') }}</div> -->
|
||||
<div class="relation-ad-header">
|
||||
<div class="relation-ad-header-left">{{ $t('cmdb.ciType.relationADHeader1') }}</div>
|
||||
<div class="relation-ad-header-left">{{ $t('cmdb.ciType.relationADHeader2') }}</div>
|
||||
</div>
|
||||
<div class="relation-ad-main">
|
||||
<div class="relation-ad-item" v-for="item in relationList" :key="item.id">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '230px', '--custom-height': '32px' }"
|
||||
v-model="item.ad_key"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeADTAttributes"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.relationADSelectAttr')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div :title="node.label" slot="option-label" slot-scope="{ node }">
|
||||
<div>{{ node.label }}</div>
|
||||
<!-- <div :style="{ fontSize: '12px', color: '#cbcbcb', lineHeight: '12px' }">{{ node.raw.desc }}</div> -->
|
||||
</div>
|
||||
</treeselect>
|
||||
<div
|
||||
class="relation-ad-item-link"
|
||||
>
|
||||
<div class="relation-ad-item-link-left"></div>
|
||||
<div class="relation-ad-item-link-right"></div>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '230px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
v-model="item.peer_type_id"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="relationOptions"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.relationADSelectCIType')"
|
||||
:disableBranchNodes="true"
|
||||
@select="changeType(item)"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value || $t('other'),
|
||||
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>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '230px', marginRight: '18px', '--custom-height': '32px' }"
|
||||
v-model="item.peer_attr_id"
|
||||
:multiple="false"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="item.attributes"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.relationADSelectModelAttr')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.alias || node.name,
|
||||
title: node.alias || node.name,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<div class="relation-ad-item-action">
|
||||
<a @click="copyRelation(item)">
|
||||
<a-icon type="copy" />
|
||||
</a>
|
||||
<a @click="deleteRelation(item)">
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a @click="addRelation">
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relation-ad-footer">
|
||||
<a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -99,9 +128,15 @@ import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
import { getDiscovery, getCITypeDiscovery, postCITypeDiscovery, putCITypeDiscovery } from '../../api/discovery'
|
||||
import {
|
||||
getCITypeAttributes,
|
||||
getCITypeRelations,
|
||||
postCITypeRelations
|
||||
} from '../../api/discovery'
|
||||
import {
|
||||
getCITypeChildren,
|
||||
getCITypeParent
|
||||
} from '../../api/CITypeRelation.js'
|
||||
|
||||
export default {
|
||||
name: 'RelationAutoDiscovery',
|
||||
@@ -114,11 +149,11 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
relationList: [],
|
||||
ciTypeADTAttributes: [],
|
||||
ciTypeGroup: [],
|
||||
relationList: [], // 关系自动发现数据
|
||||
ciTypeADTAttributes: [], // 自动发现 options
|
||||
adt_id: null,
|
||||
adrList: [],
|
||||
relationOptions: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -126,64 +161,62 @@ export default {
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
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) }
|
||||
})
|
||||
})
|
||||
},
|
||||
async mounted() {
|
||||
await this.getDiscovery()
|
||||
this.getCITypeDiscovery()
|
||||
await this.getCITypeAttributes()
|
||||
await this.getCITypeRelationOptions()
|
||||
this.getCITypeRelations()
|
||||
},
|
||||
methods: {
|
||||
async getDiscovery() {
|
||||
await getDiscovery().then((res) => {
|
||||
this.adrList = res
|
||||
async getCITypeAttributes() {
|
||||
const res = await getCITypeAttributes(this.CITypeId)
|
||||
this.ciTypeADTAttributes = res.map((item) => {
|
||||
return {
|
||||
id: item,
|
||||
value: item,
|
||||
label: item
|
||||
}
|
||||
})
|
||||
},
|
||||
getCITypeDiscovery() {
|
||||
getCITypeDiscovery(this.CITypeId).then(async (res) => {
|
||||
// Options for the first drop-down box
|
||||
const _ciTypeADTAttributes = []
|
||||
res
|
||||
.filter((adt) => adt.adr_id)
|
||||
.forEach((adt) => {
|
||||
const _find = this.adrList.find((adr) => adr.id === adt.adr_id)
|
||||
if (_find && _find.attributes) {
|
||||
_ciTypeADTAttributes.push(..._find.attributes)
|
||||
}
|
||||
})
|
||||
console.log(_ciTypeADTAttributes)
|
||||
this.ciTypeADTAttributes = _.uniqBy(_ciTypeADTAttributes, 'name')
|
||||
// Options for the first drop-down box
|
||||
const _find = res.find((adt) => !adt.adr_id)
|
||||
if (_find) {
|
||||
this.adt_id = _find.id
|
||||
async getCITypeRelationOptions() {
|
||||
const childRes = await getCITypeChildren(this.CITypeId)
|
||||
const parentRes = await getCITypeParent(this.CITypeId)
|
||||
const options = [...childRes.children, ...parentRes.parents]
|
||||
|
||||
options.forEach((item) => {
|
||||
item.value = item.id
|
||||
item.label = item.alias || item.name
|
||||
const attributes = item?.attributes?.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
attributes.forEach((attr) => {
|
||||
attr.value = attr.id
|
||||
attr.label = attr.alias || attr.name
|
||||
})
|
||||
item.attributes = attributes
|
||||
})
|
||||
this.relationOptions = options
|
||||
},
|
||||
async getCITypeRelations() {
|
||||
getCITypeRelations(this.CITypeId).then(async (res) => {
|
||||
if (res?.length) {
|
||||
// this.adt_id = _find.id
|
||||
const _relationList = []
|
||||
const keys = Object.keys(_find.relation)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const { attributes } = await getCITypeAttributesById(_find.relation[`${keys[i]}`].type_name)
|
||||
res.forEach((item) => {
|
||||
const attributes = this?.relationOptions?.find((option) => option?.value === item.peer_type_id)?.attributes || []
|
||||
_relationList.push({
|
||||
id: uuidv4(),
|
||||
attrName: keys[i],
|
||||
type_name: _find.relation[`${keys[i]}`].type_name,
|
||||
attr_name: _find.relation[`${keys[i]}`].attr_name,
|
||||
ad_key: item.ad_key,
|
||||
peer_type_id: item.peer_type_id,
|
||||
peer_attr_id: item.peer_attr_id,
|
||||
attributes,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.relationList = _relationList.length
|
||||
? _relationList
|
||||
: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
attrName: undefined,
|
||||
type_name: undefined,
|
||||
attr_name: undefined,
|
||||
ad_key: undefined,
|
||||
peer_type_id: undefined,
|
||||
peer_attr_id: undefined,
|
||||
attributes: [],
|
||||
},
|
||||
]
|
||||
@@ -192,9 +225,9 @@ export default {
|
||||
this.relationList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
attrName: undefined,
|
||||
type_name: undefined,
|
||||
attr_name: undefined,
|
||||
ad_key: undefined,
|
||||
peer_type_id: undefined,
|
||||
peer_attr_id: undefined,
|
||||
attributes: [],
|
||||
},
|
||||
]
|
||||
@@ -202,48 +235,57 @@ export default {
|
||||
})
|
||||
},
|
||||
changeType(item) {
|
||||
console.log(item)
|
||||
this.$nextTick(() => {
|
||||
getCITypeAttributesById(item.type_name).then((res) => {
|
||||
item.attr_name = undefined
|
||||
item.attributes = res.attributes.map((item) => {
|
||||
return { ...item, value: item.id, label: item.alias || item.name }
|
||||
})
|
||||
})
|
||||
const peer_type_id = item.peer_type_id
|
||||
const attributes = this?.relationOptions?.find((option) => option?.value === peer_type_id)?.attributes
|
||||
|
||||
item.attributes = attributes
|
||||
item.peer_attr_id = undefined
|
||||
})
|
||||
},
|
||||
addRelation() {
|
||||
const _relationList = _.cloneDeep(this.relationList)
|
||||
_relationList.push({
|
||||
id: uuidv4(),
|
||||
attrName: undefined,
|
||||
type_name: undefined,
|
||||
attr_name: undefined,
|
||||
ad_key: undefined,
|
||||
peer_type_id: undefined,
|
||||
peer_attr_id: undefined,
|
||||
attributes: [],
|
||||
})
|
||||
this.relationList = _relationList
|
||||
},
|
||||
copyRelation(item) {
|
||||
const _relationList = _.cloneDeep(this.relationList)
|
||||
_relationList.push({
|
||||
...item,
|
||||
id: uuidv4()
|
||||
})
|
||||
this.relationList = _relationList
|
||||
},
|
||||
|
||||
deleteRelation(item) {
|
||||
if (this.relationList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.deleteRelationAdTip'))
|
||||
return
|
||||
}
|
||||
const _idx = this.relationList.findIndex(({ id }) => item.id === id)
|
||||
if (_idx > -1) {
|
||||
this.relationList.splice(_idx, 1)
|
||||
}
|
||||
},
|
||||
|
||||
async handleSave() {
|
||||
const _relation = {}
|
||||
this.relationList.forEach(({ attrName, type_name, attr_name }) => {
|
||||
if (attrName) {
|
||||
_relation[`${attrName}`] = { type_name, attr_name }
|
||||
const _relation = this.relationList.map(({ ad_key, peer_attr_id, peer_type_id }) => {
|
||||
return {
|
||||
ad_key,
|
||||
peer_attr_id,
|
||||
peer_type_id
|
||||
}
|
||||
})
|
||||
if (_relation) {
|
||||
if (this.adt_id) {
|
||||
await putCITypeDiscovery(this.adt_id, { relation: _relation })
|
||||
} else {
|
||||
await postCITypeDiscovery(this.CITypeId, { relation: _relation })
|
||||
}
|
||||
if (_relation.length) {
|
||||
await postCITypeRelations(this.CITypeId, { relations: _relation })
|
||||
this.$message.success(this.$t('saveSuccess'))
|
||||
this.getCITypeDiscovery()
|
||||
this.getCITypeRelations()
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -254,14 +296,103 @@ export default {
|
||||
.relation-ad {
|
||||
overflow: auto;
|
||||
padding: 0 20px;
|
||||
|
||||
&-tip {
|
||||
color: @text-color_4;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
|
||||
&-left {
|
||||
width: 230px;
|
||||
margin-right: 63px;
|
||||
}
|
||||
}
|
||||
|
||||
&-main {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.relation-ad-item {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
margin-top: 10px;
|
||||
|
||||
&-link {
|
||||
position: relative;
|
||||
height: 1px;
|
||||
width: 63px;
|
||||
background-color: @border-color-base;
|
||||
|
||||
&-left {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
z-index: 10;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: @primary-color;
|
||||
border: solid 3px #E2E7FC;
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
&-right {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -5px;
|
||||
right: 0px;
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
border-radius: 1px 0px 0px 1px;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
.relation-ad-footer {
|
||||
width: 690px;
|
||||
|
||||
&-table-tip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 16px;
|
||||
color: @text-color_2;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
border: solid 1px @primary-color_8;
|
||||
background-color: @primary-color_5;
|
||||
border-radius: 2px;
|
||||
|
||||
&-icon {
|
||||
font-size: 16px;
|
||||
color: @primary-color;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
&:not(:last-child) {
|
||||
padding-right: 10px;
|
||||
margin-right: 10px;
|
||||
border-right: solid 1px @primary-color_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
// width: 690px;
|
||||
text-align: right;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 20px 20px' }">
|
||||
<div class="relation-table" :style="{ padding: '0 20px 20px' }">
|
||||
<a-button
|
||||
v-if="!isInGrantComp"
|
||||
style="margin-bottom: 10px"
|
||||
@@ -10,6 +10,7 @@
|
||||
>{{ $t('cmdb.ciType.addRelation') }}</a-button
|
||||
>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
stripe
|
||||
:data="tableData"
|
||||
size="small"
|
||||
@@ -18,6 +19,7 @@
|
||||
highlight-hover-row
|
||||
keep-source
|
||||
class="ops-stripe-table"
|
||||
min-height="500"
|
||||
:row-class-name="rowClass"
|
||||
:edit-config="{ trigger: 'dblclick', mode: 'cell', showIcon: false }"
|
||||
resizable
|
||||
@@ -43,7 +45,7 @@
|
||||
<span v-else>{{ constraintMap[row.constraint] }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<vxe-column :width="300" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
@@ -56,43 +58,73 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(row.isParent ? row.attributes : attributes, row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(row.isParent ? attributes : row.attributes, row.child_attr_id) }}</span
|
||||
<template
|
||||
v-for="item in row.parentAndChildAttrList"
|
||||
>
|
||||
<div
|
||||
:key="item.id"
|
||||
v-if="item.parentAttrId && item.childAttrId"
|
||||
>
|
||||
{{ getAttrNameById(row.isParent ? row.attributes : attributes, item.parentAttrId) }}=>
|
||||
{{ getAttrNameById(row.isParent ? attributes : row.attributes, item.childAttrId) }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<div
|
||||
v-for="item in tableAttrList"
|
||||
:key="item.id"
|
||||
class="table-attribute-row"
|
||||
>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
v-model="item.parentAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? row.attributes : attributes)"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<span class="table-attribute-row-link">=></span>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
v-model="item.childAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(row.isParent ? attributes : row.attributes)"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="removeTableAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="addTableAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
@@ -179,13 +211,16 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-row
|
||||
v-for="item in modalAttrList"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-col :span="10">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.parentAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(attributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -196,12 +231,12 @@
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-col :span="9">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.childAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -209,6 +244,20 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="removeModalAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="addModalAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -227,6 +276,7 @@ import {
|
||||
} from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
|
||||
@@ -259,9 +309,11 @@ export default {
|
||||
tableData: [],
|
||||
parentTableData: [],
|
||||
attributes: [],
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
tableAttrList: [],
|
||||
modalAttrList: [],
|
||||
modalChildAttributes: [],
|
||||
currentEditData: null,
|
||||
isContinueCloseEdit: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -292,13 +344,16 @@ export default {
|
||||
if (!this.isInGrantComp) {
|
||||
await this.getCITypeParent()
|
||||
}
|
||||
this.getCITypeChildren()
|
||||
await this.getCITypeChildren()
|
||||
},
|
||||
async getCITypeParent() {
|
||||
await getCITypeParent(this.CITypeId).then((res) => {
|
||||
this.parentTableData = res.parents.map((item) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(item)
|
||||
|
||||
return {
|
||||
...item,
|
||||
parentAndChildAttrList,
|
||||
source_ci_type_name: this.CITypeName,
|
||||
source_ci_type_id: this.CITypeId,
|
||||
isParent: true,
|
||||
@@ -306,11 +361,14 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
getCITypeChildren() {
|
||||
getCITypeChildren(this.CITypeId).then((res) => {
|
||||
async getCITypeChildren() {
|
||||
await getCITypeChildren(this.CITypeId).then((res) => {
|
||||
const data = res.children.map((obj) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(obj)
|
||||
|
||||
return {
|
||||
...obj,
|
||||
parentAndChildAttrList,
|
||||
source_ci_type_name: this.CITypeName,
|
||||
source_ci_type_id: this.CITypeId,
|
||||
}
|
||||
@@ -322,6 +380,20 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleAttrList(data) {
|
||||
const length = Math.min(data?.parent_attr_ids?.length || 0, data.child_attr_ids?.length || 0)
|
||||
const parentAndChildAttrList = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
parentAndChildAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: data?.parent_attr_ids?.[i] ?? '',
|
||||
childAttrId: data?.child_attr_ids?.[i] ?? ''
|
||||
})
|
||||
}
|
||||
return parentAndChildAttrList
|
||||
},
|
||||
|
||||
getCITypes() {
|
||||
getCITypes().then((res) => {
|
||||
this.CITypes = res.ci_types
|
||||
@@ -342,6 +414,13 @@ export default {
|
||||
handleCreate() {
|
||||
this.drawerTitle = this.$t('cmdb.ciType.addRelation')
|
||||
this.visible = true
|
||||
this.$set(this, 'modalAttrList', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
}
|
||||
])
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
source_ci_type_id: this.CITypeId,
|
||||
@@ -365,19 +444,22 @@ export default {
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.modalAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
@@ -386,6 +468,37 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
handleOpenGrant(record) {
|
||||
this.$refs.cmdbGrant.open({
|
||||
name: `${record.source_ci_type_name} -> ${record.name}`,
|
||||
@@ -401,25 +514,75 @@ export default {
|
||||
if (row.isParent) return 'relation-table-parent'
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
this.$nextTick(async () => {
|
||||
if (this.isContinueCloseEdit) {
|
||||
const editRecord = this.$refs.xTable.getEditRecord()
|
||||
const { row: editRow, column } = editRecord
|
||||
this.currentEditData = {
|
||||
row: editRow,
|
||||
column
|
||||
}
|
||||
return
|
||||
}
|
||||
const tableAttrList = []
|
||||
|
||||
const length = Math.min(row?.parent_attr_ids?.length || 0, row.child_attr_ids?.length || 0)
|
||||
if (length) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: row?.parent_attr_ids?.[i] ?? undefined,
|
||||
childAttrId: row?.child_attr_ids?.[i] ?? undefined
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
}
|
||||
this.$set(this, 'tableAttrList', tableAttrList)
|
||||
})
|
||||
},
|
||||
async handleEditClose({ row }) {
|
||||
const { source_ci_type_id: parentId, id: childrenId, constraint, relation_type } = row
|
||||
const { parent_attr_id, child_attr_id } = this
|
||||
const _find = this.relationTypes.find((item) => item.name === relation_type)
|
||||
const relation_type_id = _find?.id
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
if (this.currentEditData) {
|
||||
this.currentEditData = null
|
||||
return
|
||||
}
|
||||
|
||||
this.isContinueCloseEdit = true
|
||||
|
||||
const { source_ci_type_id: parentId, id: childrenId, constraint, relation_type } = row
|
||||
const _find = this.relationTypes.find((item) => item.name === relation_type)
|
||||
const relation_type_id = _find?.id
|
||||
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.tableAttrList)
|
||||
if (!validate) {
|
||||
this.isContinueCloseEdit = false
|
||||
return
|
||||
}
|
||||
|
||||
await createRelation(row.isParent ? childrenId : parentId, row.isParent ? parentId : childrenId, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
}).finally(() => {
|
||||
this.getData()
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).finally(async () => {
|
||||
await this.getData()
|
||||
this.isContinueCloseEdit = false
|
||||
|
||||
if (this.currentEditData) {
|
||||
setTimeout(async () => {
|
||||
const { fullData } = this.$refs.xTable.getTableData()
|
||||
const findEdit = fullData.find((item) => item.id === this?.currentEditData?.row?.id)
|
||||
await this.$refs.xTable.setEditRow(findEdit, 'attributeAssociation')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getAttrNameById(attributes, id) {
|
||||
@@ -427,7 +590,9 @@ export default {
|
||||
return _find?.alias ?? _find?.name ?? id
|
||||
},
|
||||
changeChild(value) {
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.childAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
@@ -436,10 +601,75 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
addTableAttr() {
|
||||
this.tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
removeTableAttr(id) {
|
||||
if (this.tableAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.tableAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.tableAttrList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
addModalAttr() {
|
||||
this.modalAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
|
||||
removeModalAttr(id) {
|
||||
if (this.modalAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.modalAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.modalAttrList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.relation-table {
|
||||
/deep/ .vxe-cell {
|
||||
max-height: max-content !important;
|
||||
}
|
||||
}
|
||||
.table-attribute-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-link {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
&-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ops-stripe-table .vxe-body--row.row--stripe.relation-table-divider {
|
||||
background-color: #b1b8d3 !important;
|
||||
|
@@ -91,7 +91,7 @@
|
||||
show-search
|
||||
>
|
||||
<a-select-option
|
||||
v-for="attr in commonAttributes.filter((attr) => !attr.is_password)"
|
||||
v-for="attr in commonAttributes.filter((attr) => !attr.is_password && attr.value_type !== '6')"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
>{{ attr.alias || attr.name }}</a-select-option
|
||||
|
36
cmdb-ui/src/modules/cmdb/views/discovery/agentTable.vue
Normal file
36
cmdb-ui/src/modules/cmdb/views/discovery/agentTable.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<vxe-table
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
show-overflow
|
||||
keep-source
|
||||
ref="xTable"
|
||||
max-height="100%"
|
||||
:data="tableData"
|
||||
:scroll-y="{enabled: true}"
|
||||
>
|
||||
<vxe-column field="name" :title="$t('name')">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.name" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="type" :title="$t('type')"></vxe-column>
|
||||
<vxe-column field="desc" :title="$t('desc')"></vxe-column>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AgentTable',
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
6
cmdb-ui/src/modules/cmdb/views/discovery/constants.js
Normal file
6
cmdb-ui/src/modules/cmdb/views/discovery/constants.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export const DISCOVERY_CATEGORY_TYPE = {
|
||||
AGENT: 'agent',
|
||||
SNMP: 'snmp',
|
||||
HTTP: 'http',
|
||||
PLUGIN: 'plugin'
|
||||
}
|
@@ -2,34 +2,81 @@
|
||||
<div
|
||||
:class="{
|
||||
'discovery-card': true,
|
||||
'discovery-card-http': rule.type === DISCOVERY_CATEGORY_TYPE.HTTP,
|
||||
'discovery-card-small': isSelected,
|
||||
'discovery-card-small-selected': isSelected && selectedIds().findIndex((item) => item.id === rule.id) > -1,
|
||||
}"
|
||||
@click="clickCard"
|
||||
>
|
||||
<div class="discovery-bottom"></div>
|
||||
<div
|
||||
class="discovery-card-inner"
|
||||
v-if="rule.is_inner"
|
||||
>
|
||||
<span class="discovery-card-inner-text">{{ $t('cmdb.ad.innerFlag') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="discovery-background-top"
|
||||
:style="{ background: borderTopColorMap[rule.name] || '' }"
|
||||
></div>
|
||||
<div class="discovery-top">
|
||||
<div class="discovery-header">
|
||||
<img
|
||||
v-if="icon.id && icon.url"
|
||||
class="discovery-header-icon"
|
||||
:src="`/api/common-setting/v1/file/${icon.url}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon v-else :type="icon.name || 'caise-chajian'" :style="{ fontSize: '30px', color: icon.color }" />
|
||||
<ops-icon
|
||||
v-else
|
||||
:type="icon.name || 'caise-chajian'"
|
||||
:style="{ color: icon.color }"
|
||||
class="discovery-header-icon"
|
||||
/>
|
||||
<span :title="rule.name">{{ rule.name }}</span>
|
||||
</div>
|
||||
<template v-if="!isSelected">
|
||||
<a-divider :style="{ margin: '5px 0' }" />
|
||||
<div
|
||||
class="discovery-resources"
|
||||
v-if="rule.type === DISCOVERY_CATEGORY_TYPE.HTTP && rule.resources.length"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ $t('cmdb.ad.discoveryCardResoureTip') }}
|
||||
</template>
|
||||
<div class="discovery-resources-left">
|
||||
<ops-icon class="discovery-resources-icon" type="cmdb-discovery_resources" />
|
||||
<span class="discovery-resources-count">{{ rule.resources.length }}{{ $i18n.locale === 'zh' ? '个' : '' }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<div class="discovery-resources-right">
|
||||
<template v-for="(item, index) in rule.resources">
|
||||
<span
|
||||
:key="index"
|
||||
v-if="index < 2"
|
||||
class="discovery-resources-item"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</template>
|
||||
<span v-if="rule.resources.length >= 2" class="discovery-resources-item">
|
||||
<ops-icon type="veops-more" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider
|
||||
:style="{ margin: rule.type === DISCOVERY_CATEGORY_TYPE.HTTP ? '10px 0' : '7px 0' }"
|
||||
/>
|
||||
<div class="discovery-footer">
|
||||
<a-space v-if="rule.type === 'agent'">
|
||||
<a @click="handleEdit"><ops-icon type="icon-xianxing-edit"/></a>
|
||||
<a-space v-if="rule.type === 'agent' && rule.is_plugin">
|
||||
<a @click="handleEdit">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a
|
||||
v-if="isDeletable"
|
||||
@click="handleDelete"
|
||||
:style="{ color: 'red' }"
|
||||
><ops-icon
|
||||
type="icon-xianxing-delete"
|
||||
/></a>
|
||||
>
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-space>
|
||||
<a v-else @click="handleEdit"><a-icon type="eye"/></a>
|
||||
<span>{{ rule.is_plugin ? 'Plugin' : $t('cmdb.custom_dashboard.default') }}</span>
|
||||
@@ -40,6 +87,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DISCOVERY_CATEGORY_TYPE } from './constants.js'
|
||||
|
||||
export default {
|
||||
name: 'DiscoveryCard',
|
||||
props: {
|
||||
@@ -52,6 +101,17 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
DISCOVERY_CATEGORY_TYPE,
|
||||
borderTopColorMap: {
|
||||
'阿里云': '#FFB287',
|
||||
'腾讯云': '#87BEFF',
|
||||
'华为云': '#FFB8B8',
|
||||
'AWS': '#FFC187',
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
return this.rule?.option?.icon ?? { color: '', name: 'caise-wuliji' }
|
||||
@@ -95,7 +155,32 @@ export default {
|
||||
position: relative;
|
||||
margin-bottom: 40px;
|
||||
margin-right: 40px;
|
||||
.discovery-bottom {
|
||||
|
||||
&-inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 4;
|
||||
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
border-left: 50px solid transparent;
|
||||
border-top: 30px solid @primary-color_4;
|
||||
|
||||
&-text {
|
||||
width: 30px;
|
||||
position: absolute;
|
||||
top: -28px;
|
||||
right: 3px;
|
||||
text-align: right;
|
||||
|
||||
color: @primary-color;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.discovery-background-top {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
@@ -105,6 +190,7 @@ export default {
|
||||
background: linear-gradient(90.54deg, #879fff 1.32%, #a0ddff 99.13%);
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.discovery-top {
|
||||
width: 100%;
|
||||
height: calc(100% - 5px);
|
||||
@@ -118,7 +204,7 @@ export default {
|
||||
padding: 12px;
|
||||
.discovery-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> i {
|
||||
@@ -130,17 +216,73 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 22px;
|
||||
max-height: 22px;
|
||||
max-width: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.discovery-resources {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&-count {
|
||||
margin-left: 3px;
|
||||
color: @text-color_3;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-item {
|
||||
padding: 3px 6px;
|
||||
border-radius: 12px;
|
||||
background-color: @layout-content-background;
|
||||
|
||||
color: @text-color_3;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
max-width: 95px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discovery-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
> span {
|
||||
color: #a5a9bc;
|
||||
background-color: #d8eaff;
|
||||
color: #86909c;
|
||||
background-color: #f0f5ff;
|
||||
border-radius: 2px;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-http {
|
||||
width: 263px;
|
||||
height: 142px;
|
||||
|
||||
.discovery-header {
|
||||
&-icon {
|
||||
font-size: 30px !important;
|
||||
max-height: 30px !important;
|
||||
max-width: 30px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,6 +291,11 @@ export default {
|
||||
width: 170px;
|
||||
height: 80px;
|
||||
cursor: pointer;
|
||||
// &:hover {
|
||||
// .discovery-top {
|
||||
// background-color: #f0f1f5;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.discovery-card-small:hover,
|
||||
.discovery-card-small-selected {
|
||||
|
@@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<CustomDrawer width="800px" :title="title" :visible="visible" @close="handleClose">
|
||||
<template v-if="adType === 'agent'">
|
||||
<CustomDrawer
|
||||
width="800px"
|
||||
:title="title"
|
||||
:visible="visible"
|
||||
:bodyStyle="{ height: 'calc(-108px + 100vh)' }"
|
||||
@close="handleClose"
|
||||
>
|
||||
<AgentTable
|
||||
v-if="adType === DISCOVERY_CATEGORY_TYPE.AGENT"
|
||||
:tableData="tableData"
|
||||
/>
|
||||
<template v-else-if="adType === DISCOVERY_CATEGORY_TYPE.PLUGIN">
|
||||
<a-form-model
|
||||
ref="autoDiscoveryForm"
|
||||
:model="form"
|
||||
@@ -47,8 +57,9 @@
|
||||
icon="plus"
|
||||
:style="{ marginBottom: '10px' }"
|
||||
@click="insertEvent(-1)"
|
||||
>{{ $t('new') }}</a-button
|
||||
>
|
||||
{{ $t('new') }}
|
||||
</a-button>
|
||||
<vxe-table
|
||||
size="mini"
|
||||
stripe
|
||||
@@ -77,7 +88,11 @@
|
||||
<vxe-input v-model="row.desc" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="$t('operation')" width="60" v-if="!form.is_plugin">
|
||||
<vxe-column
|
||||
:title="$t('operation')"
|
||||
width="60"
|
||||
v-if="!form.is_plugin"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<a-space v-if="$refs.xTable.isActiveByRow(row)">
|
||||
<a @click="saveRowEvent(row)"><a-icon type="save"/></a>
|
||||
@@ -103,15 +118,24 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomIconSelect from '@/components/CustomIconSelect'
|
||||
import { postDiscovery, putDiscovery } from '../../api/discovery'
|
||||
import { DISCOVERY_CATEGORY_TYPE } from './constants.js'
|
||||
|
||||
import AgentTable from './agentTable.vue'
|
||||
import CustomIconSelect from '@/components/CustomIconSelect'
|
||||
import HttpSnmpAD from '../../components/httpSnmpAD'
|
||||
import CustomCodeMirror from '@/components/CustomCodeMirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/theme/monokai.css'
|
||||
|
||||
export default {
|
||||
name: 'EditDrawer',
|
||||
components: { CustomIconSelect, CustomCodeMirror, HttpSnmpAD },
|
||||
components: {
|
||||
CustomIconSelect,
|
||||
CustomCodeMirror,
|
||||
HttpSnmpAD,
|
||||
AgentTable
|
||||
},
|
||||
props: {
|
||||
is_inner: {
|
||||
type: Boolean,
|
||||
@@ -142,11 +166,12 @@ export default {
|
||||
tabSize: 4,
|
||||
lineWrapping: true,
|
||||
},
|
||||
DISCOVERY_CATEGORY_TYPE,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.adType === 'http' || this.adType === 'snmp') {
|
||||
if ([DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.AGENT].includes(this.adType)) {
|
||||
return this.ruleData.name
|
||||
}
|
||||
if (this.type === 'edit') {
|
||||
@@ -173,10 +198,15 @@ export default {
|
||||
is_plugin: true,
|
||||
}
|
||||
}
|
||||
if (adType === 'http' || adType === 'snmp') {
|
||||
if (adType === DISCOVERY_CATEGORY_TYPE.HTTP || adType === DISCOVERY_CATEGORY_TYPE.SNMP) {
|
||||
return
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (adType === DISCOVERY_CATEGORY_TYPE.AGENT) {
|
||||
this.tableData = data?.attributes ?? []
|
||||
return
|
||||
}
|
||||
|
||||
if (this.type === 'edit') {
|
||||
this.form = {
|
||||
name: data.name,
|
||||
@@ -203,7 +233,7 @@ export default {
|
||||
this.tableData = []
|
||||
this.customIcon = { name: '', color: '' }
|
||||
this.form = { name: '', is_plugin: false }
|
||||
if (this.adType === 'agent') {
|
||||
if (this.adType === DISCOVERY_CATEGORY_TYPE.PLUGIN) {
|
||||
this.$refs.autoDiscoveryForm.clearValidate()
|
||||
} else {
|
||||
// this.$refs.httpSnmpAd.currentCate = ''
|
||||
@@ -244,9 +274,10 @@ export default {
|
||||
const $table = this.$refs.xTable
|
||||
const { fullData: _tableData } = $table.getTableData()
|
||||
console.log(_tableData)
|
||||
const type = this.adType === DISCOVERY_CATEGORY_TYPE.PLUGIN ? DISCOVERY_CATEGORY_TYPE.AGENT : this.adType
|
||||
const params = {
|
||||
...this.form,
|
||||
type: this.adType,
|
||||
type,
|
||||
is_inner: this.is_inner,
|
||||
option: { icon: this.customIcon },
|
||||
attributes: this.form.is_plugin
|
||||
|
@@ -1,39 +1,90 @@
|
||||
<template>
|
||||
<div class="setting-discovery">
|
||||
<div :style="{ textAlign: 'right' }">
|
||||
<a-space v-if="!isSelected">
|
||||
<a-upload name="file" :multiple="false" accept=".json" :fileList="[]" :beforeUpload="beforeUpload">
|
||||
<a><a-icon type="upload" />{{ $t('cmdb.ad.upload') }}</a>
|
||||
</a-upload>
|
||||
<a @click="download"><a-icon type="download" />{{ $t('cmdb.ad.download') }}</a>
|
||||
</a-space>
|
||||
</div>
|
||||
<div v-for="{ type, label } in typeCategory" :key="type">
|
||||
<div class="type-header">
|
||||
<div>{{ label }}</div>
|
||||
<a-space v-if="!isSelected && type === 'agent'">
|
||||
<a @click="handleOpenEditDrawer(null, 'add', type)"><ops-icon type="icon-xianxing-tianjia"/></a>
|
||||
</a-space>
|
||||
<div v-if="!isSelected" class="setting-discovery-header">
|
||||
<a-input-search
|
||||
class="setting-discovery-search"
|
||||
:placeholder="$t('cmdb.ad.pluginSearchTip')"
|
||||
@search="onSearchDiscovery"
|
||||
/>
|
||||
<div class="setting-discovery-radio">
|
||||
<div
|
||||
v-for="{ type, label } in typeCategory"
|
||||
:key="type"
|
||||
:class="['setting-discovery-radio-item', radioKey === type ? 'setting-discovery-radio-item_active' : '']"
|
||||
@click="changeRadio(type)"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-discovery-header-action">
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="false"
|
||||
accept=".json"
|
||||
:fileList="[]"
|
||||
:beforeUpload="beforeUpload"
|
||||
>
|
||||
<a class="setting-discovery-header-action-btn">
|
||||
<a-icon type="upload" />
|
||||
{{ $t('cmdb.ad.upload') }}
|
||||
</a>
|
||||
</a-upload>
|
||||
<a
|
||||
@click="download"
|
||||
class="setting-discovery-header-action-btn"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
{{ $t('cmdb.ad.download') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-discovery-body">
|
||||
<template v-if="!showNullData">
|
||||
<div v-for="{ type, label } in typeCategory" :key="type">
|
||||
<template v-if="filterCategoryChildren[type] && (filterCategoryChildren[type].children.length || (showAddPlugin && type === DISCOVERY_CATEGORY_TYPE.PLUGIN))">
|
||||
<div class="type-header">
|
||||
<div>{{ label }}</div>
|
||||
</div>
|
||||
<a-row type="flex" justify="start">
|
||||
<DiscoveryCard
|
||||
v-for="rule in filterCategoryChildren[type].children"
|
||||
:key="rule.id"
|
||||
:rule="rule"
|
||||
:isSelected="isSelected"
|
||||
@editRule="handleOpenEditDrawer(rule, 'edit', type)"
|
||||
@deleteRule="deleteRule(rule)"
|
||||
/>
|
||||
<div
|
||||
v-if="showAddPlugin && type === DISCOVERY_CATEGORY_TYPE.PLUGIN"
|
||||
class="setting-discovery-add"
|
||||
@click="handleOpenEditDrawer(null, 'add', DISCOVERY_CATEGORY_TYPE.PLUGIN)"
|
||||
>
|
||||
<a-icon type="plus-circle" theme="twoTone" />
|
||||
<span class="setting-discovery-add-text">
|
||||
{{ $t('cmdb.ad.addPlugin') }}
|
||||
</span>
|
||||
</div>
|
||||
</a-row>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="setting-discovery-empty" v-else>
|
||||
<img class="setting-discovery-empty-img" :src="require(`@/assets/data_empty.png`)" />
|
||||
<p class="setting-discovery-empty-text">{{ $t('noData') }}</p>
|
||||
</div>
|
||||
<a-row type="flex" justify="start">
|
||||
<DiscoveryCard
|
||||
@editRule="handleOpenEditDrawer(rule, 'edit', type)"
|
||||
@deleteRule="deleteRule(rule)"
|
||||
v-for="rule in typeCategoryChildren[type]"
|
||||
:key="rule.id"
|
||||
:rule="rule"
|
||||
:isSelected="isSelected"
|
||||
/>
|
||||
</a-row>
|
||||
</div>
|
||||
<EditDrawer ref="editDrawer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getDiscovery, deleteDiscovery } from '../../api/discovery'
|
||||
import { DISCOVERY_CATEGORY_TYPE } from './constants.js'
|
||||
import DiscoveryCard from './discoveryCard.vue'
|
||||
import EditDrawer from './editDrawer.vue'
|
||||
|
||||
export default {
|
||||
name: 'AutoDiscovery',
|
||||
components: { DiscoveryCard, EditDrawer },
|
||||
@@ -45,26 +96,56 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
typeCategoryChildren: { agent: [], snmp: [], http: [] },
|
||||
typeCategoryChildren: {},
|
||||
DISCOVERY_CATEGORY_TYPE,
|
||||
radioKey: '',
|
||||
searchValue: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
typeCategory() {
|
||||
return [
|
||||
{
|
||||
type: 'agent',
|
||||
type: DISCOVERY_CATEGORY_TYPE.HTTP,
|
||||
label: this.$t('cmdb.ad.http'),
|
||||
},
|
||||
{
|
||||
type: DISCOVERY_CATEGORY_TYPE.AGENT,
|
||||
label: this.$t('cmdb.ad.agent'),
|
||||
},
|
||||
{
|
||||
type: 'snmp',
|
||||
type: DISCOVERY_CATEGORY_TYPE.SNMP,
|
||||
label: this.$t('cmdb.ad.snmp'),
|
||||
},
|
||||
{
|
||||
type: 'http',
|
||||
label: this.$t('cmdb.ad.http'),
|
||||
},
|
||||
type: DISCOVERY_CATEGORY_TYPE.PLUGIN,
|
||||
label: this.$t('cmdb.ad.plugin'),
|
||||
}
|
||||
]
|
||||
},
|
||||
filterCategoryChildren() {
|
||||
const _typeCategoryChildren = _.cloneDeep(this.typeCategoryChildren)
|
||||
const _filterCategoryChildren = Object.values(_typeCategoryChildren).reduce((obj, category) => {
|
||||
if (this.radioKey === '' || category.type === this.radioKey) {
|
||||
category.children = category.children.filter((item) => {
|
||||
return item?.name?.indexOf(this.searchValue) !== -1
|
||||
})
|
||||
obj[category.type] = category
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
return _filterCategoryChildren
|
||||
},
|
||||
showNullData() {
|
||||
const showCount = Object.values(this.filterCategoryChildren).reduce((acc, item) => {
|
||||
return acc + (item?.children?.length || 0)
|
||||
}, 0)
|
||||
return showCount === 0
|
||||
},
|
||||
showAddPlugin() {
|
||||
return !this.isSelected && this.searchValue === ''
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -76,13 +157,41 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getDiscovery() {
|
||||
const _typeCategoryChildren = { agent: [], snmp: [], http: [] }
|
||||
const _typeCategoryChildren = {
|
||||
[DISCOVERY_CATEGORY_TYPE.HTTP]: {
|
||||
type: DISCOVERY_CATEGORY_TYPE.HTTP,
|
||||
children: []
|
||||
},
|
||||
[DISCOVERY_CATEGORY_TYPE.AGENT]: {
|
||||
type: DISCOVERY_CATEGORY_TYPE.AGENT,
|
||||
children: []
|
||||
},
|
||||
[DISCOVERY_CATEGORY_TYPE.SNMP]: {
|
||||
type: DISCOVERY_CATEGORY_TYPE.SNMP,
|
||||
children: []
|
||||
},
|
||||
[DISCOVERY_CATEGORY_TYPE.PLUGIN]: {
|
||||
type: DISCOVERY_CATEGORY_TYPE.PLUGIN,
|
||||
children: []
|
||||
}
|
||||
}
|
||||
getDiscovery().then((res) => {
|
||||
this.typeCategory.forEach(({ type }) => {
|
||||
const _filterData = res.filter((list) => list.type === type && list.is_inner)
|
||||
_typeCategoryChildren[`${type}`] = _filterData
|
||||
let categoryChildren = []
|
||||
switch (type) {
|
||||
case DISCOVERY_CATEGORY_TYPE.PLUGIN:
|
||||
categoryChildren = res.filter((list) => list.is_plugin)
|
||||
break
|
||||
case DISCOVERY_CATEGORY_TYPE.AGENT:
|
||||
categoryChildren = res.filter((list) => !list.is_plugin && list.type === type)
|
||||
break
|
||||
default:
|
||||
categoryChildren = res.filter((list) => list.type === type)
|
||||
break
|
||||
}
|
||||
_typeCategoryChildren[`${type}`]['children'] = categoryChildren
|
||||
})
|
||||
this.typeCategoryChildren = _typeCategoryChildren
|
||||
this.$set(this, 'typeCategoryChildren', _typeCategoryChildren)
|
||||
})
|
||||
},
|
||||
handleOpenEditDrawer(data, type, autoType) {
|
||||
@@ -137,15 +246,106 @@ export default {
|
||||
xhr.send(formData)
|
||||
return false
|
||||
},
|
||||
|
||||
onSearchDiscovery(v) {
|
||||
this.searchValue = v
|
||||
},
|
||||
|
||||
changeRadio(key) {
|
||||
this.radioKey = key === this.radioKey ? '' : key
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-discovery {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: @border-radius-box;
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-action {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
|
||||
&-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 12px;
|
||||
border: solid 1px @primary-color_8;
|
||||
background-color: #F4F9FF;
|
||||
color: @link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-search {
|
||||
width: 254px;
|
||||
}
|
||||
|
||||
&-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
gap: 15px;
|
||||
|
||||
&-item {
|
||||
padding: 4px 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
&_active {
|
||||
background-color: @primary-color_3;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
background-color: #fff;
|
||||
border-radius: @border-radius-box;
|
||||
box-shadow: 0px 0px 4px 0px rgba(158, 171, 190, 0.25);
|
||||
padding: 20px;
|
||||
margin-top: 24px;
|
||||
|
||||
.setting-discovery-add {
|
||||
height: 105px;
|
||||
width: 180px;
|
||||
border-radius: @border-radius-base;
|
||||
border: 1px dashed @primary-color_8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&-text {
|
||||
color: @text-color_3;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-discovery-empty {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
|
||||
&-text {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&-img {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="counter-wrap">
|
||||
<div
|
||||
v-for="(group, groupIndex) in counterData"
|
||||
:key="groupIndex"
|
||||
class="counter-group"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in group"
|
||||
:key="index"
|
||||
class="counter-item"
|
||||
>
|
||||
<div class="counter-item-header">
|
||||
<ops-icon class="counter-item-icon" :type="item.icon" />
|
||||
<span
|
||||
class="counter-item-title"
|
||||
:title="$t(item.title)"
|
||||
>
|
||||
{{ $t(item.title) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="counter-item-number">{{ item.count }}</span>
|
||||
<template v-if="item.percent !== undefined">
|
||||
<span
|
||||
v-if="item.percent !== -1"
|
||||
:class="['counter-item-percent', 'counter-item-percent-' + (item.percentStatus ? 'up' : 'down')]"
|
||||
>
|
||||
<ops-icon class="counter-item-percent-icon" type="cmdb-arrow" />
|
||||
<span
|
||||
class="counter-item-percent-text"
|
||||
>
|
||||
{{ item.percent }}%
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="counter-item-percent-null"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getAdcCounter } from '@/modules/cmdb/api/discovery'
|
||||
|
||||
export default {
|
||||
name: 'AdcCounter',
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
counterDataTemplate: [
|
||||
[
|
||||
{
|
||||
title: 'cmdb.ad.ruleCount',
|
||||
icon: 'cmdb-rule',
|
||||
type: 'ruleCount',
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
title: 'cmdb.ad.execMachine',
|
||||
icon: 'cmdb-executing_machine',
|
||||
type: 'execTargetCount',
|
||||
count: 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'cmdb.ad.resource',
|
||||
icon: 'cmdb-resource',
|
||||
type: 'resource',
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
title: 'cmdb.ad.autoInventory',
|
||||
icon: 'cmdb-automatic_inventory',
|
||||
type: 'autoInventory',
|
||||
count: 0,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'cmdb.ad.newThisWeek',
|
||||
icon: 'cmdb-week_additions',
|
||||
type: 'rule_count',
|
||||
count: 0,
|
||||
percentStatus: true,
|
||||
percent: '',
|
||||
},
|
||||
{
|
||||
title: 'cmdb.ad.newThisMonth',
|
||||
icon: 'cmdb-month_additions',
|
||||
type: 'rule_count',
|
||||
count: 0,
|
||||
percentStatus: true,
|
||||
percent: '',
|
||||
}
|
||||
]
|
||||
],
|
||||
counterData: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
typeId: {
|
||||
immediate: true,
|
||||
handler(id) {
|
||||
if (id) {
|
||||
this.queryAdcCounter(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async queryAdcCounter(id) {
|
||||
const res = await getAdcCounter({
|
||||
type_id: id
|
||||
})
|
||||
console.log('getAdcCounter res', res)
|
||||
const _counterData = _.cloneDeep(this.counterDataTemplate)
|
||||
|
||||
_counterData[0][0]['count'] = res?.rule_count ?? 0
|
||||
_counterData[0][1]['count'] = res?.exec_target_count ?? 0
|
||||
|
||||
_counterData[1][0]['count'] = res?.instance_count ?? 0
|
||||
_counterData[1][1]['count'] = res?.accept_count ?? 0
|
||||
|
||||
const newWeekCount = Math.abs(res.this_week_count - res.last_week_count)
|
||||
const newWeekPrecent = res.last_week_count ? Number((newWeekCount / res.last_week_count).toFixed(2)) * 100 : -1
|
||||
_counterData[2][0]['count'] = res.this_week_count || 0
|
||||
_counterData[2][0]['percent'] = newWeekPrecent
|
||||
_counterData[2][0]['percentStatus'] = res.this_week_count >= res.last_week_count
|
||||
|
||||
const newMonthCount = Math.abs(res.this_month_count - res.last_month_count)
|
||||
const newMonthPrecent = res.last_month_count ? Number((newMonthCount / res.last_month_count).toFixed(2)) * 100 : -1
|
||||
_counterData[2][1]['count'] = res.this_month_count || 0
|
||||
_counterData[2][1]['percent'] = newMonthPrecent
|
||||
_counterData[2][1]['percentStatus'] = res.this_month_count >= res.last_month_count
|
||||
|
||||
this.counterData = _counterData
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.counter-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.counter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 82px;
|
||||
border: solid 1px @border-color-base;
|
||||
flex-grow: 0;
|
||||
width: calc((100% - 30px) / 3);
|
||||
|
||||
.counter-item {
|
||||
padding: 16px 18px;
|
||||
flex-grow: 0;
|
||||
width: 50%;
|
||||
|
||||
&-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
color: @text-color_2;
|
||||
margin-left: 4px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
&-number {
|
||||
color: @primary-color;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&-percent {
|
||||
margin-left: 5px;
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-up {
|
||||
color: #00B42A;
|
||||
}
|
||||
|
||||
&-down {
|
||||
color: #FD4C6A;
|
||||
|
||||
.counter-item-percent-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-percent-null {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="!isShow">{{ showPassword }}</span>
|
||||
<span v-else>{{ password }}</span>
|
||||
<a
|
||||
class="password-eye"
|
||||
@click="switchPassword"
|
||||
>
|
||||
<a-icon :type="isShow ? 'eye-invisible' : 'eye'"/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PasswordField',
|
||||
props: {
|
||||
password: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
showPassword: '******',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchPassword() {
|
||||
this.isShow = !this.isShow
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.password-eye {
|
||||
margin-left: 10px
|
||||
}
|
||||
</style>
|
@@ -7,42 +7,49 @@
|
||||
><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span>
|
||||
</p>
|
||||
<div
|
||||
:class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === type.id }"
|
||||
v-for="type in group.ci_types"
|
||||
:key="type.id"
|
||||
@click="clickSidebar(type.id)"
|
||||
:class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === ciType.id }"
|
||||
v-for="ciType in group.ci_types"
|
||||
:key="ciType.id"
|
||||
@click="clickSidebar(ciType.id)"
|
||||
>
|
||||
<span class="cmdb-adc-side-icon">
|
||||
<template v-if="type.icon">
|
||||
<img v-if="type.icon.split('$$')[2]" :src="`/api/common-setting/v1/file/${type.icon.split('$$')[3]}`" />
|
||||
<template v-if="ciType.icon">
|
||||
<img v-if="ciType.icon.split('$$')[2]" :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" />
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: type.icon.split('$$')[1],
|
||||
color: ciType.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="type.icon.split('$$')[0]"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ type.name[0].toUpperCase() }}</span>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</span>
|
||||
<span :title="type.alias || type.name" class="cmdb-adc-side-name">{{ type.alias || type.name }}</span>
|
||||
<span :title="ciType.alias || ciType.name" class="cmdb-adc-side-name">{{ ciType.alias || ciType.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #two>
|
||||
<div id="discovery-ci">
|
||||
<a-input-search
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
:style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }"
|
||||
@search="handleSearch"
|
||||
allowClear
|
||||
/>
|
||||
<div class="ops-list-batch-action" :style="{ marginBottom: '10px' }" v-show="!!selectedRowKeys.length">
|
||||
<span @click="batchAccept">{{ $t('cmdb.ad.accept') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
<AdcCounter :typeId="currentType" />
|
||||
<div class="discovery-ci-header">
|
||||
<a-input-search
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
:style="{ width: '200px', marginRight: '20px' }"
|
||||
@search="handleSearch"
|
||||
allowClear
|
||||
/>
|
||||
<span class="ops-list-batch-action" v-show="selectedCount">
|
||||
<span @click="batchAccept">{{ $t('cmdb.ad.accept') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedCount }) }}</span>
|
||||
</span>
|
||||
<div @click="clickLog" class="discovery-ci-log">
|
||||
<ops-icon type="a-cmdb-log1" />
|
||||
<span>{{ $t('cmdb.ad.log') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ops-table
|
||||
show-overflow
|
||||
@@ -62,56 +69,75 @@
|
||||
:checkbox-config="{ reserve: true, highlight: true, range: true }"
|
||||
:sort-config="{ remote: false, trigger: 'cell' }"
|
||||
>
|
||||
<vxe-column align="center" type="checkbox" width="60"></vxe-column>
|
||||
<vxe-column
|
||||
v-for="col in columns"
|
||||
:key="col.field"
|
||||
align="center"
|
||||
type="checkbox"
|
||||
width="60"
|
||||
fixed="left"
|
||||
></vxe-column>
|
||||
<vxe-column
|
||||
v-for="(col, index) in columns"
|
||||
:key="`${col.field}_${index}`"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template v-if="col.value_type === '6'" #default="{row}">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
align="center"
|
||||
field="is_accept"
|
||||
:title="$t('cmdb.ad.isAccept')"
|
||||
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
|
||||
:filters="[
|
||||
{ label: $t('yes'), value: true },
|
||||
{ label: $t('no'), value: false },
|
||||
]"
|
||||
>
|
||||
<template #default="{row}">
|
||||
{{ row.is_accept ? $t('yes') : $t('no') }}
|
||||
<template v-if="col.value_type === '6' || col.is_password" #default="{row}">
|
||||
<PasswordField
|
||||
v-if="col.is_password"
|
||||
:password="row[col.field]"
|
||||
/>
|
||||
<span
|
||||
v-else-if="col.value_type === '6' && row[col.field]"
|
||||
>
|
||||
{{ row[col.field] }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="accept_by"
|
||||
:title="$t('cmdb.ad.acceptBy')"
|
||||
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
|
||||
:filters="[]"
|
||||
:filters="acceptByFilters"
|
||||
></vxe-column>
|
||||
<vxe-column
|
||||
align="center"
|
||||
field="is_accept"
|
||||
:title="$t('cmdb.ad.isAccept')"
|
||||
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
|
||||
:filters="[
|
||||
{ label: $t('yes'), value: true },
|
||||
{ label: $t('no'), value: false },
|
||||
]"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{row}">
|
||||
<ops-icon
|
||||
:type="row.is_accept ? 'cmdb-warehousing' : 'cmdb-not_warehousing'"
|
||||
:style="{ color: row.is_accept ? '#00B42A' : '#A5A9BC' }"
|
||||
/>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="accept_time"
|
||||
:title="$t('cmdb.ad.acceptTime')"
|
||||
sortable
|
||||
v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }"
|
||||
v-bind="columns.length ? { width: '150px' } : { minWidth: '150px' }"
|
||||
fixed="right"
|
||||
></vxe-column>
|
||||
<vxe-column
|
||||
:title="$t('operation')"
|
||||
v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{row}">
|
||||
<a-space>
|
||||
<a-tooltip :title="$t('cmdb.ad.accept')">
|
||||
<a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="icon-xianxing-edit"/></a>
|
||||
<a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="cmdb-manual_warehousing"/></a>
|
||||
</a-tooltip>
|
||||
<a :style="{ color: 'red' }" @click="deleteADC(row)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
<a :style="{ color: 'red' }" @click="deleteADC(row)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
@@ -122,6 +148,23 @@
|
||||
</div>
|
||||
</template>
|
||||
</ops-table>
|
||||
|
||||
<a-modal
|
||||
v-model="logModalVisible"
|
||||
:footer="null"
|
||||
:width="596"
|
||||
>
|
||||
<div class="log-modal-title">{{ $t('cmdb.ad.log') }}</div>
|
||||
<p ref="logModelText" class="log-modal-text">
|
||||
<span
|
||||
v-for="(item, index) in logTextArray"
|
||||
:key="index"
|
||||
class="log-modal-text-item"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</p>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
</TwoColumnLayout>
|
||||
@@ -131,11 +174,26 @@
|
||||
import _ from 'lodash'
|
||||
import XEUtils from 'xe-utils'
|
||||
import TwoColumnLayout from '@/components/TwoColumnLayout'
|
||||
import { getADCCiTypes, getAdc, updateADCAccept, getADCCiTypesAttrs, deleteAdc } from '../../api/discovery'
|
||||
import AdcCounter from './components/adcCounter.vue'
|
||||
import PasswordField from './components/passwordField.vue'
|
||||
|
||||
import {
|
||||
getADCCiTypes,
|
||||
getAdc,
|
||||
updateADCAccept,
|
||||
getADCCiTypesAttrs,
|
||||
deleteAdc,
|
||||
getAdcExecHistories
|
||||
} from '../../api/discovery'
|
||||
import { getCITableColumns } from '../../utils/helper'
|
||||
|
||||
export default {
|
||||
name: 'DiscoveryCI',
|
||||
components: { TwoColumnLayout },
|
||||
components: {
|
||||
TwoColumnLayout,
|
||||
AdcCounter,
|
||||
PasswordField
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ci_types_list: [],
|
||||
@@ -145,6 +203,10 @@ export default {
|
||||
columns: [],
|
||||
selectedRowKeys: [],
|
||||
searchValue: '',
|
||||
logModalVisible: false,
|
||||
logTextArray: [],
|
||||
acceptByFilters: [],
|
||||
selectedCount: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -152,7 +214,7 @@ export default {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
return this.windowHeight - 140
|
||||
return this.windowHeight - 240
|
||||
},
|
||||
filterTableData() {
|
||||
const { searchValue } = this
|
||||
@@ -169,7 +231,7 @@ export default {
|
||||
return rest
|
||||
}
|
||||
return this.tableData
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentType: {
|
||||
@@ -180,6 +242,13 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
selectedRowKeys: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(selectedRowKeys) {
|
||||
this.selectedCount = selectedRowKeys.length
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
getADCCiTypes({ need_other: true }).then((res) => {
|
||||
@@ -213,18 +282,20 @@ export default {
|
||||
if ($table) {
|
||||
const nameColumn = $table.getVxetableRef().getColumnByField('accept_by')
|
||||
if (nameColumn) {
|
||||
const acceptByFilters = _.uniqBy(
|
||||
res.result
|
||||
.filter((item) => item.accept_by)
|
||||
.map((item) => ({
|
||||
value: item.accept_by,
|
||||
label: item.accept_by,
|
||||
})),
|
||||
'value'
|
||||
)
|
||||
$table.getVxetableRef().setFilter(
|
||||
nameColumn,
|
||||
_.uniqBy(
|
||||
res.result
|
||||
.filter((item) => item.accept_by)
|
||||
.map((item) => ({
|
||||
value: item.accept_by,
|
||||
label: item.accept_by,
|
||||
})),
|
||||
'value'
|
||||
)
|
||||
acceptByFilters
|
||||
)
|
||||
this.acceptByFilters = acceptByFilters
|
||||
}
|
||||
}
|
||||
this.tableData = res.result.map((item) => ({ ..._.cloneDeep(item), ...item.instance }))
|
||||
@@ -302,12 +373,33 @@ export default {
|
||||
onCancel() {},
|
||||
})
|
||||
},
|
||||
onSelectChange({ records, checked, row }) {
|
||||
onSelectChange({ records, checked }) {
|
||||
this.selectedRowKeys = records.map((item) => item.id)
|
||||
},
|
||||
handleSearch(value) {
|
||||
this.searchValue = value
|
||||
},
|
||||
|
||||
async clickLog() {
|
||||
this.logModalVisible = true
|
||||
const logRes = await getAdcExecHistories({
|
||||
type_id: this.currentType,
|
||||
page_size: 1000
|
||||
})
|
||||
let logTextArray = []
|
||||
if (logRes?.result?.length) {
|
||||
logTextArray = logRes.result.map((log) => {
|
||||
return `[${log.created_at}] ${log.stdout}`
|
||||
})
|
||||
}
|
||||
this.logTextArray = logTextArray
|
||||
this.$nextTick(() => {
|
||||
const textEl = this.$refs.logModelText
|
||||
if (textEl) {
|
||||
textEl.scrollTop = textEl.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -350,5 +442,48 @@ export default {
|
||||
.ops_popover_item_selected();
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
|
||||
.discovery-ci-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.discovery-ci-log {
|
||||
cursor: pointer;
|
||||
background-color: #F4F9FF;
|
||||
border: solid 1px @primary-color_8;
|
||||
color: @primary-color;
|
||||
font-size: 12px;
|
||||
padding: 5px 12px;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.log-modal-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.log-modal-text {
|
||||
margin-top: 14px;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
height: 312px;
|
||||
overflow: auto;
|
||||
border: solid 1px @border-color-base;
|
||||
background-color: #2f333d;
|
||||
|
||||
&-item {
|
||||
color: #c5c8c6;
|
||||
width: 100%;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -70,13 +70,16 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('cmdb.ciType.attributeAssociation')">
|
||||
<a-row>
|
||||
<a-col :span="11">
|
||||
<a-row
|
||||
v-for="item in modalAttrList"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-col :span="10">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
|
||||
allowClear
|
||||
v-decorator="['parent_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.parentAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalParentAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -87,12 +90,12 @@
|
||||
<a-col :span="2" :style="{ textAlign: 'center' }">
|
||||
=>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-col :span="9">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
|
||||
allowClear
|
||||
v-decorator="['child_attr_id', { rules: [{ required: false }] }]"
|
||||
v-model="item.childAttrId"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(modalChildAttributes)" :key="attr.id">
|
||||
{{ attr.alias || attr.name }}
|
||||
@@ -100,6 +103,20 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="removeModalAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="modal-attribute-action"
|
||||
@click="addModalAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -114,6 +131,7 @@ import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@@ -139,6 +157,7 @@ export default {
|
||||
|
||||
modalParentAttributes: [],
|
||||
modalChildAttributes: [],
|
||||
modalAttrList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -228,6 +247,13 @@ export default {
|
||||
handleCreate() {
|
||||
this.drawerTitle = this.$t('cmdb.ciType.addRelation')
|
||||
this.visible = true
|
||||
this.$set(this, 'modalAttrList', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
}
|
||||
])
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
source_ci_type_id: this.sourceCITypeId,
|
||||
@@ -249,19 +275,22 @@ export default {
|
||||
ci_type_id,
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id = undefined,
|
||||
child_attr_id = undefined,
|
||||
} = values
|
||||
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.modalAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
createRelation(source_ci_type_id, ci_type_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).then((res) => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.onClose()
|
||||
@@ -272,6 +301,37 @@ export default {
|
||||
this.sourceCITypeId = undefined
|
||||
this.targetCITypeId = undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
handleOk() {
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
@@ -284,14 +344,18 @@ export default {
|
||||
},
|
||||
handleSourceTypeChange(value) {
|
||||
this.sourceCITypeId = value
|
||||
this.form.setFieldsValue({ parent_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.parentAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalParentAttributes = res?.attributes ?? []
|
||||
})
|
||||
},
|
||||
handleTargetTypeChange(value) {
|
||||
this.targetCITypeId = value
|
||||
this.form.setFieldsValue({ child_attr_id: undefined })
|
||||
this.modalAttrList.forEach((item) => {
|
||||
item.childAttrId = undefined
|
||||
})
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.modalChildAttributes = res?.attributes ?? []
|
||||
})
|
||||
@@ -303,12 +367,30 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
|
||||
addModalAttr() {
|
||||
this.modalAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
|
||||
removeModalAttr(id) {
|
||||
if (this.modalAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.modalAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.modalAttrList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.model-relation {
|
||||
background-color: #fff;
|
||||
border-radius: @border-radius-box;
|
||||
@@ -316,4 +398,8 @@ export default {
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
}
|
||||
|
||||
.modal-attribute-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="model-relation-table">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
stripe
|
||||
@@ -7,6 +7,7 @@
|
||||
show-header-overflow
|
||||
show-overflow
|
||||
resizable
|
||||
:scroll-y="{enabled: false}"
|
||||
:height="`${windowHeight - 160}px`"
|
||||
:data="tableData"
|
||||
:sort-config="{ defaultSort: { field: 'created_at', order: 'desc' } }"
|
||||
@@ -34,7 +35,7 @@
|
||||
{{ handleConstraint(row.constraint) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :width="250" field="attributeAssociation" :edit-render="{}">
|
||||
<vxe-column :width="300" field="attributeAssociation" :edit-render="{}">
|
||||
<template #header>
|
||||
<span>
|
||||
<a-tooltip :title="$t('cmdb.ciType.attributeAssociationTip1')">
|
||||
@@ -47,37 +48,73 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #default="{row}">
|
||||
<span
|
||||
v-if="row.parent_attr_id && row.child_attr_id"
|
||||
>{{ getAttrNameById(type2attributes[row.parent_id], row.parent_attr_id) }}=>
|
||||
{{ getAttrNameById(type2attributes[row.child_id], row.child_attr_id) }}</span
|
||||
<template
|
||||
v-for="item in row.parentAndChildAttrList"
|
||||
>
|
||||
<div
|
||||
:key="item.id"
|
||||
v-if="item.parentAttrId && item.childAttrId"
|
||||
>
|
||||
{{ getAttrNameById(type2attributes[row.parent_id], item.parentAttrId) }}=>
|
||||
{{ getAttrNameById(type2attributes[row.child_id], item.childAttrId) }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #edit="{ row }">
|
||||
<div style="display:inline-flex;align-items:center;">
|
||||
<div
|
||||
v-for="item in tableAttrList"
|
||||
:key="item.id"
|
||||
class="table-attribute-row"
|
||||
>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="parent_attr_id"
|
||||
v-model="item.parentAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.parent_id])" :key="attr.id">
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(type2attributes[row.parent_id])"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
=>
|
||||
<span class="table-attribute-row-link">=></span>
|
||||
<a-select
|
||||
allowClear
|
||||
size="small"
|
||||
v-model="child_attr_id"
|
||||
v-model="item.childAttrId"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
:style="{ width: '100px' }"
|
||||
show-search
|
||||
optionFilterProp="title"
|
||||
>
|
||||
<a-select-option v-for="attr in filterAttributes(type2attributes[row.child_id])" :key="attr.id">
|
||||
<a-select-option
|
||||
v-for="attr in filterAttributes(type2attributes[row.child_id])"
|
||||
:key="attr.id"
|
||||
:value="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="removeTableAttr(item.id)"
|
||||
>
|
||||
<a-icon type="minus-circle" />
|
||||
</a>
|
||||
<a
|
||||
class="table-attribute-row-action"
|
||||
@click="addTableAttr"
|
||||
>
|
||||
<a-icon type="plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
@@ -97,6 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getCITypeRelations, deleteRelation, createRelation } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
import CMDBGrant from '../../../components/cmdbGrant'
|
||||
@@ -108,8 +146,7 @@ export default {
|
||||
tableData: [],
|
||||
relationTypeList: null,
|
||||
type2attributes: {},
|
||||
parent_attr_id: undefined,
|
||||
child_attr_id: undefined,
|
||||
tableAttrList: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -137,9 +174,29 @@ export default {
|
||||
},
|
||||
async getMainData() {
|
||||
const { relations, type2attributes } = await getCITypeRelations()
|
||||
this.tableData = relations
|
||||
this.tableData = relations.map((item) => {
|
||||
const parentAndChildAttrList = this.handleAttrList(item)
|
||||
return {
|
||||
...item,
|
||||
parentAndChildAttrList
|
||||
}
|
||||
})
|
||||
this.type2attributes = type2attributes
|
||||
},
|
||||
|
||||
handleAttrList(data) {
|
||||
const length = Math.min(data?.parent_attr_ids?.length || 0, data.child_attr_ids?.length || 0)
|
||||
const parentAndChildAttrList = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
parentAndChildAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: data?.parent_attr_ids?.[i] ?? '',
|
||||
childAttrId: data?.child_attr_ids?.[i] ?? ''
|
||||
})
|
||||
}
|
||||
return parentAndChildAttrList
|
||||
},
|
||||
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
@@ -171,21 +228,75 @@ export default {
|
||||
})
|
||||
},
|
||||
handleEditActived({ row }) {
|
||||
this.parent_attr_id = row?.parent_attr_id ?? undefined
|
||||
this.child_attr_id = row?.child_attr_id ?? undefined
|
||||
const tableAttrList = []
|
||||
|
||||
const length = Math.min(row?.parent_attr_ids?.length || 0, row.child_attr_ids?.length || 0)
|
||||
if (length) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: row?.parent_attr_ids?.[i] ?? undefined,
|
||||
childAttrId: row?.child_attr_ids?.[i] ?? undefined
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
}
|
||||
console.log('handleEditActived', tableAttrList)
|
||||
this.$set(this, 'tableAttrList', tableAttrList)
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验属性列表
|
||||
* @param {*} attrList
|
||||
*/
|
||||
handleValidateAttrList(attrList) {
|
||||
const parent_attr_ids = []
|
||||
const child_attr_ids = []
|
||||
attrList.map((attr) => {
|
||||
if (attr.parentAttrId) {
|
||||
parent_attr_ids.push(attr.parentAttrId)
|
||||
}
|
||||
if (attr.childAttrId) {
|
||||
child_attr_ids.push(attr.childAttrId)
|
||||
}
|
||||
})
|
||||
|
||||
if (parent_attr_ids.length !== child_attr_ids.length) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
return {
|
||||
validate: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validate: true,
|
||||
parent_attr_ids,
|
||||
child_attr_ids
|
||||
}
|
||||
},
|
||||
|
||||
async handleEditClose({ row }) {
|
||||
const { parent_id, child_id, constraint, relation_type_id } = row
|
||||
const { parent_attr_id = undefined, child_attr_id = undefined } = this
|
||||
if ((!parent_attr_id && child_attr_id) || (parent_attr_id && !child_attr_id)) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.attributeAssociationTip3'))
|
||||
|
||||
const {
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
validate
|
||||
} = this.handleValidateAttrList(this.tableAttrList)
|
||||
if (!validate) {
|
||||
return
|
||||
}
|
||||
|
||||
await createRelation(parent_id, child_id, {
|
||||
relation_type_id,
|
||||
constraint,
|
||||
parent_attr_id,
|
||||
child_attr_id,
|
||||
parent_attr_ids,
|
||||
child_attr_ids,
|
||||
}).finally(() => {
|
||||
this.getMainData()
|
||||
})
|
||||
@@ -198,8 +309,49 @@ export default {
|
||||
// filter password/json/is_list
|
||||
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6')
|
||||
},
|
||||
|
||||
addTableAttr() {
|
||||
this.tableAttrList.push({
|
||||
id: uuidv4(),
|
||||
parentAttrId: undefined,
|
||||
childAttrId: undefined
|
||||
})
|
||||
},
|
||||
removeTableAttr(id) {
|
||||
if (this.tableAttrList.length <= 1) {
|
||||
this.$message.error(this.$t('cmdb.ciType.attributeAssociationTip6'))
|
||||
return
|
||||
}
|
||||
const index = this.tableAttrList.findIndex((item) => item.id === id)
|
||||
if (index !== -1) {
|
||||
this.tableAttrList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style lang="less" scoped>
|
||||
.relation-table {
|
||||
/deep/ .vxe-cell {
|
||||
max-height: max-content !important;
|
||||
}
|
||||
}
|
||||
.table-attribute-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-link {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
&-action {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -162,6 +162,9 @@ export default {
|
||||
{ [this.$t('cmdb.history.deleteUniqueConstraint')]: 11 },
|
||||
{ [this.$t('cmdb.history.addRelation')]: 12 },
|
||||
{ [this.$t('cmdb.history.deleteRelation')]: 13 },
|
||||
{ [this.$t('cmdb.history.addReconciliation')]: 14 },
|
||||
{ [this.$t('cmdb.history.updateReconciliation')]: 15 },
|
||||
{ [this.$t('cmdb.history.deleteReconciliation')]: 16 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -198,6 +201,9 @@ export default {
|
||||
['11', this.$t('cmdb.history.deleteUniqueConstraint')],
|
||||
['12', this.$t('cmdb.history.addRelation')],
|
||||
['13', this.$t('cmdb.history.deleteRelation')],
|
||||
['14', this.$t('cmdb.history.addReconciliation')],
|
||||
['15', this.$t('cmdb.history.updateReconciliation')],
|
||||
['16', this.$t('cmdb.history.deleteReconciliation')],
|
||||
])
|
||||
},
|
||||
},
|
||||
@@ -312,27 +318,22 @@ export default {
|
||||
// update CIType
|
||||
case '1': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
const newVal = item.change.new[key]
|
||||
const oldVal = item.change.old[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` [ ${key} : ${newVal || '""'} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
} else {
|
||||
const str = ` [ ${key} : ${oldVal || '""'} -> ${newVal || '""'} ] `
|
||||
item.changeDescription += ` [ ${key} : ${oldVal || '""'} -> ${newVal || '""'} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
const diffs = this.deepCompare({
|
||||
obj1: item?.change?.old,
|
||||
obj2: item?.change?.new,
|
||||
ignoreKeys: ['updated_at']
|
||||
})
|
||||
for (const val of diffs) {
|
||||
const str = ` [ ${val.path} : ${val.value1} -> ${val.value2} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
|
||||
break
|
||||
}
|
||||
// delete CIType
|
||||
case '2': {
|
||||
item.changeDescription = this.$t('cmdb.history.addCIType') + ': ' + `${item.change.alias}`
|
||||
item.changeDescription = this.$t('cmdb.history.deleteCIType') + ': ' + `${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// add Attribute
|
||||
@@ -343,24 +344,15 @@ export default {
|
||||
// update Attribute
|
||||
case '4': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') {
|
||||
let newStr = item.change.new[key]
|
||||
let oldStr = item.change.old[key]
|
||||
if (key === 'choice_value') {
|
||||
newStr = newStr ? newStr.map((item) => item[0]).join(',') : ''
|
||||
oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : ''
|
||||
}
|
||||
if (Object.prototype.toString.call(newStr) === '[object Object]') {
|
||||
newStr = JSON.stringify(newStr)
|
||||
}
|
||||
if (Object.prototype.toString.call(oldStr) === '[object Object]') {
|
||||
oldStr = JSON.stringify(oldStr)
|
||||
}
|
||||
const str = `${key} : ${oldStr ? ` ${oldStr || '""'} ` : ''} -> ${newStr || '""'}`
|
||||
item.changeDescription += ` [ ${str} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
const diffs = this.deepCompare({
|
||||
obj1: item?.change?.old,
|
||||
obj2: item?.change?.new,
|
||||
ignoreKeys: ['updated_at']
|
||||
})
|
||||
for (const val of diffs) {
|
||||
const str = ` [ ${val.path} : ${val.value1} -> ${val.value2} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
|
||||
break
|
||||
@@ -372,39 +364,29 @@ export default {
|
||||
}
|
||||
// add trigger
|
||||
case '6': {
|
||||
item.changeDescription = this.$t('cmdb.history.noModifications', {
|
||||
attr_id: item.change.attr_id,
|
||||
before_days: item.change.option.before_days,
|
||||
subject: item.change.option.subject,
|
||||
body: item.change.option.body,
|
||||
notify_at: item.change.option.notify_at,
|
||||
})
|
||||
item.changeDescription = `${this.$t('cmdb.history.addTrigger')}:${item?.change?.option?.name || ''}`
|
||||
break
|
||||
}
|
||||
// update trigger
|
||||
case '7': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old.option) {
|
||||
const newVal = item.change.new.option[key]
|
||||
const oldVal = item.change.old.option[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
const str = ` [ ${key} : ${oldVal} -> ${newVal} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
const diffs = this.deepCompare({
|
||||
obj1: item?.change?.old,
|
||||
obj2: item?.change?.new,
|
||||
directDeepKeys: ['notifies'],
|
||||
ignoreKeys: ['updated_at']
|
||||
})
|
||||
for (const val of diffs) {
|
||||
const str = ` [ ${val.path} : ${val.value1} -> ${val.value2} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
|
||||
break
|
||||
}
|
||||
// delete trigger
|
||||
case '8': {
|
||||
item.changeDescription = this.$t('cmdb.history.noModifications', {
|
||||
attr_id: item.change.attr_id,
|
||||
before_days: item.change.option.before_days,
|
||||
subject: item.change.option.subject,
|
||||
body: item.change.option.body,
|
||||
notify_at: item.change.option.notify_at,
|
||||
})
|
||||
item.changeDescription = `${this.$t('cmdb.history.deleteTrigger')}:${item?.change?.option?.name || ''}`
|
||||
break
|
||||
}
|
||||
// add unique constraint
|
||||
@@ -441,8 +423,77 @@ export default {
|
||||
)} -> ${item.change.child.alias}`
|
||||
break
|
||||
}
|
||||
case '14': {
|
||||
item.changeDescription = this.$t('cmdb.history.addReconciliation') + ': ' + item.change.name || item.change.alias
|
||||
break
|
||||
}
|
||||
case '15': {
|
||||
item.changeArr = []
|
||||
const diffs = this.deepCompare({
|
||||
obj1: item?.change?.old,
|
||||
obj2: item?.change?.new,
|
||||
directDeepKeys: ['notifies'],
|
||||
ignoreKeys: ['updated_at']
|
||||
})
|
||||
for (const val of diffs) {
|
||||
const str = ` [ ${val.path} : ${val.value1} -> ${val.value2} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.updateReconciliation')
|
||||
break
|
||||
}
|
||||
case '16': {
|
||||
item.changeDescription = this.$t('cmdb.history.deleteReconciliation') + ': ' + item.change.name || item.change.alias
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deepCompare({
|
||||
obj1,
|
||||
obj2,
|
||||
directDeepKeys = [],
|
||||
ignoreKeys = [],
|
||||
}) {
|
||||
const diffs = []
|
||||
|
||||
function compare(obj1, obj2, path = '') {
|
||||
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) {
|
||||
if (obj1 !== obj2) {
|
||||
diffs.push({ path, value1: formatValue(obj1), value2: formatValue(obj2) })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const keys1 = new Set(Object.keys(obj1))
|
||||
const keys2 = new Set(Object.keys(obj2))
|
||||
const allKeys = new Set([...keys1, ...keys2])
|
||||
|
||||
allKeys.forEach(key => {
|
||||
const newPath = path ? `${path}.${key}` : key
|
||||
if (!ignoreKeys.includes(key)) {
|
||||
if (directDeepKeys.includes(key) && !_.isEqual(obj1[key], obj2[key])) {
|
||||
diffs.push({ path: newPath, value1: formatValue(obj1[key]), value2: formatValue(obj2[key]) })
|
||||
} else if (!keys1.has(key)) {
|
||||
diffs.push({ path: newPath, value1: undefined, value2: formatValue(obj2[key]) })
|
||||
} else if (!keys2.has(key)) {
|
||||
diffs.push({ path: newPath, value1: formatValue(obj1[key]), value2: undefined })
|
||||
} else {
|
||||
compare(obj1[key], obj2[key], newPath)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function formatValue(val) {
|
||||
return _.isObject(val) ? JSON.stringify(val) : val
|
||||
}
|
||||
|
||||
compare(obj1, obj2)
|
||||
return diffs
|
||||
},
|
||||
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
|
@@ -32,8 +32,11 @@
|
||||
ghost
|
||||
@click="handleClickAddGroup"
|
||||
class="ops-button-ghost"
|
||||
><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button
|
||||
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
|
||||
>
|
||||
<ops-icon type="veops-increase" />
|
||||
{{ $t('cmdb.ciType.group') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<draggable class="topo-left-content" :list="computedTopoGroups" @end="handleChangeGroups" filter=".undraggable">
|
||||
<div v-for="group in computedTopoGroups" :key="group.id || group.name">
|
||||
@@ -56,16 +59,16 @@
|
||||
<a-space>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.topo.addTopoViewInGroup') }}</template>
|
||||
<a><ops-icon type="veops-increase" @click="handleCreate(group)"/></a>
|
||||
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"><ops-icon type="veops-increase" @click="handleCreate(group)"/></a>
|
||||
</a-tooltip>
|
||||
<template v-if="group.id">
|
||||
<a-tooltip >
|
||||
<template slot="title">{{ $t('cmdb.ciType.editGroup') }}</template>
|
||||
<a><a-icon type="edit" @click="handleEditGroup(group)"/></a>
|
||||
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"><a-icon type="edit" @click="handleEditGroup(group)"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
|
||||
<a :style="{color: 'red'}"><a-icon type="delete" @click="handleDeleteGroup(group)"/></a>
|
||||
<a v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')" :style="{color: 'red'}"><a-icon type="delete" @click="handleDeleteGroup(group)"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
@@ -140,12 +143,33 @@
|
||||
<div :style="{ height: `${windowHeight - 80}px` }" ref="rightTopoView">
|
||||
<RelationGraph ref="showTopoView" :options="graphOptions2" :on-node-click="showNodeTips">
|
||||
<template #node="{node}">
|
||||
<div :style="{ lineHeight: '20px' }">
|
||||
<ops-icon type="caise-wuliji" />
|
||||
<span :style="{ marginLeft: '5px', textOverflow: 'ellipsis' }">{{ node.text }}aaa</span>
|
||||
<div
|
||||
:style="{ borderColor: node.data.btnType === 'more' ? '#A4B5E1' : nodeStyle[Math.abs(node.lot.level)] ? nodeStyle[Math.abs(node.lot.level)].backgroundColor : '#A4B5E1' }"
|
||||
class="relation-graph-node"
|
||||
>
|
||||
<template v-if="node.data.icon">
|
||||
<img
|
||||
v-if="node.data.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${node.data.icon.split('$$')[3]}`"
|
||||
class="relation-graph-node-image"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: node.data.icon.split('$$')[1] }"
|
||||
:type="node.data.icon ? node.data.icon.split('$$')[0] : ''"
|
||||
class="relation-graph-node-icon"
|
||||
/>
|
||||
</template>
|
||||
<span class="relation-graph-node-text">{{ node.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #graph-plug>
|
||||
<a-input-search
|
||||
class="relation-graph-search"
|
||||
v-model="topoViewSearchValue"
|
||||
:placeholder="$t('cmdb.topo.topoViewSearchPlaceholder')"
|
||||
@search="handleSearchTopoView"
|
||||
/>
|
||||
<div v-if="(isShowNodeTipsPanel && currentNodeValues && currentNodeAttributes.length) || errorMessageShow" :style="nodeTipsPosition" class="node-tips">
|
||||
<a-descriptions
|
||||
v-if="currentNodeValues"
|
||||
@@ -236,12 +260,12 @@
|
||||
</SeeksRelationGraph>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item :style="{ display: 'none' }" :label="$t('cmdb.topo.aggregationCount')" prop="aggregation_count" :help="$t('cmdb.topo.aggreationCountTip')">
|
||||
<a-form-item :label="$t('cmdb.topo.aggregationCount')" prop="aggregation_count" :help="$t('cmdb.topo.aggreationCountTip')">
|
||||
<a-input-number
|
||||
:style="{ width: '100%' }"
|
||||
:min="0"
|
||||
v-decorator="['aggregation_count']"
|
||||
>
|
||||
<a @click="handleOpenCmdb" slot="suffix"><a-icon type="menu"/></a>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<div :class="{ 'chart-left-preview': true, 'chart-left-preview-empty': !isShowPreview }">
|
||||
@@ -253,9 +277,27 @@
|
||||
>
|
||||
<template v-if="isShowPreview">
|
||||
<RelationGraph ref="previewTopoView" :options="graphOptionsPrivew">
|
||||
<div slot="node" slot-scope="{ node }" :style="{ lineHeight: '20px' }">
|
||||
<span :style="{ marginLeft: '5px' }">{{ node.text }}</span>
|
||||
</div>
|
||||
<template #node="{node}">
|
||||
<div
|
||||
:style="{ borderColor: nodeStyle[node.lot.level] ? nodeStyle[node.lot.level].backgroundColor : '#7F97FA' }"
|
||||
class="relation-graph-node"
|
||||
>
|
||||
<template v-if="node.data.icon">
|
||||
<img
|
||||
v-if="node.data.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${node.data.icon.split('$$')[3]}`"
|
||||
class="relation-graph-node-image"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: node.data.icon.split('$$')[1] }"
|
||||
:type="node.data.icon ? node.data.icon.split('$$')[0] : ''"
|
||||
class="relation-graph-node-icon"
|
||||
/>
|
||||
</template>
|
||||
<span class="relation-graph-node-text">{{ node.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</RelationGraph>
|
||||
</template>
|
||||
</div>
|
||||
@@ -290,6 +332,7 @@ import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getTopoGroups, postTopoGroup, putTopoGroupByGId, putTopoGroupsOrder, deleteTopoGroup, getTopoView, addTopoView, updateTopoView, deleteTopoView, getRelationsByTypeId, previewTopoView, showTopoView } from '@/modules/cmdb/api/topology'
|
||||
import CMDBExprDrawer from '@/components/CMDBExprDrawer'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
const currentTopoKey = 'ops_cmdb_topo_currentId'
|
||||
export default {
|
||||
@@ -326,6 +369,7 @@ export default {
|
||||
const graphOptions2 = {
|
||||
// ...defaultOptions,
|
||||
backgrounImageNoRepeat: true,
|
||||
ovUseNodeSlot: true,
|
||||
placeOtherGroup: true,
|
||||
moveToCenterWhenRefresh: true,
|
||||
zoomToFitWhenRefresh: true,
|
||||
@@ -339,10 +383,11 @@ export default {
|
||||
max_per_width: 200,
|
||||
min_per_height: 40,
|
||||
max_per_height: undefined,
|
||||
defaultLineColor: '#CACDD9',
|
||||
defaultNodeColor: '#29AAE1',
|
||||
defaultNodeFontColor: '#ffffff',
|
||||
defaultNodeBorderColor: '#b1c9ff',
|
||||
backgroundColor: '#FFFFFF',
|
||||
// defaultLineColor: '#CACDD9',
|
||||
// defaultNodeColor: '#29AAE1',
|
||||
// defaultNodeFontColor: '#ffffff',
|
||||
// defaultNodeBorderColor: '#b1c9ff',
|
||||
defaultExpandHolderPosition: 'right',
|
||||
defaultJunctionPoint: 'lr',
|
||||
layouts: [
|
||||
@@ -350,7 +395,7 @@ export default {
|
||||
layoutName: 'tree',
|
||||
from: 'left',
|
||||
layoutClassName: 'seeks-layout-center',
|
||||
defaultExpandHolderPosition: 'hide',
|
||||
defaultExpandHolderPosition: 'right',
|
||||
defaultJunctionPoint: 'border',
|
||||
},
|
||||
],
|
||||
@@ -411,6 +456,29 @@ export default {
|
||||
|
||||
errorMessageShow: false,
|
||||
errorMessage: '',
|
||||
nodeStyle: {
|
||||
'0': {
|
||||
backgroundColor: '#2F54EB'
|
||||
},
|
||||
'1': {
|
||||
backgroundColor: '#29AAE1'
|
||||
},
|
||||
'2': {
|
||||
backgroundColor: '#7F97FA'
|
||||
},
|
||||
'3': {
|
||||
backgroundColor: '##75C5CA'
|
||||
},
|
||||
'4': {
|
||||
backgroundColor: '#A699F6'
|
||||
},
|
||||
'5': {
|
||||
backgroundColor: '#A4B5E1'
|
||||
}
|
||||
}, // 拓扑图节点分级别样式
|
||||
topoViewJsonData: {}, // 拓扑图 JSON 数据
|
||||
topoViewOption: {}, // 拓扑图配置数据 子节点分页
|
||||
topoViewSearchValue: '', // 拓扑图搜索
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
@@ -485,6 +553,30 @@ export default {
|
||||
: {}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$i18n.locale': {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.changeTopoViewToolbarLang(newVal)
|
||||
},
|
||||
},
|
||||
isShowPreview: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.changeTopoViewToolbarLang(this.$i18n.locale)
|
||||
}
|
||||
},
|
||||
},
|
||||
drawerVisible: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.changeTopoViewToolbarLang(this.$i18n.locale)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeNodeTips(e) {
|
||||
e.preventDefault()
|
||||
@@ -578,7 +670,7 @@ export default {
|
||||
payload.view_ids = g.views.map(i => i.id)
|
||||
}
|
||||
if (groupId) {
|
||||
putTopoGroupByGId(groupId, { view_ids: g.views.map((i) => i.id) })
|
||||
putTopoGroupByGId(groupId, { view_ids: payload.view_ids })
|
||||
.then(() => {
|
||||
this.$message.success(that.$t('saveSuccess'))
|
||||
})
|
||||
@@ -729,12 +821,21 @@ export default {
|
||||
disableDefaultClickEffect: true,
|
||||
})
|
||||
})
|
||||
const type2meta = res?.type2meta
|
||||
res.nodes.forEach(item => {
|
||||
const icon = type2meta?.[item?.type_id] || ''
|
||||
nodes.push({
|
||||
id: `${item.id}`,
|
||||
text: item.name,
|
||||
nodeShape: 1,
|
||||
borderWidth: -1,
|
||||
color: 'transparent',
|
||||
styleClass: {
|
||||
padding: '0px'
|
||||
},
|
||||
data: {
|
||||
icon
|
||||
},
|
||||
disableDefaultClickEffect: true,
|
||||
})
|
||||
})
|
||||
@@ -752,11 +853,15 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
showTopoView(viewId) {
|
||||
async showTopoView(viewId) {
|
||||
if (viewId === 'null' || !viewId) {
|
||||
return
|
||||
}
|
||||
showTopoView(viewId).then(res => {
|
||||
const topoViewRes = await getTopoView(viewId)
|
||||
if (topoViewRes?.option) {
|
||||
this.topoViewOption = topoViewRes.option
|
||||
}
|
||||
showTopoView(viewId).then(async res => {
|
||||
const nodes = []
|
||||
const links = []
|
||||
this.currentNodes = res.nodes
|
||||
@@ -768,11 +873,21 @@ export default {
|
||||
disableDefaultClickEffect: false,
|
||||
})
|
||||
})
|
||||
const type2meta = res?.type2meta
|
||||
res.nodes.forEach(item => {
|
||||
const icon = type2meta?.[item?.type_id] || ''
|
||||
nodes.push({
|
||||
id: `${item.id}`,
|
||||
text: item.name,
|
||||
data: {},
|
||||
color: 'transparent',
|
||||
styleClass: {
|
||||
padding: '0px'
|
||||
},
|
||||
data: {
|
||||
icon
|
||||
},
|
||||
isHide: false,
|
||||
opacity: 1,
|
||||
})
|
||||
})
|
||||
const _graphJsonData = {
|
||||
@@ -783,11 +898,132 @@ export default {
|
||||
this.$message.error(this.$t('cmdb.topo.noData'))
|
||||
return
|
||||
}
|
||||
// this.$nextTick(() => {
|
||||
this.$refs.showTopoView.setJsonData(_graphJsonData)
|
||||
// })
|
||||
this.$refs.showTopoView.setJsonData(_.cloneDeep(_graphJsonData), async () => {
|
||||
this.topoViewSearchValue = ''
|
||||
|
||||
// map 结构存储 节点
|
||||
const nodeMap = _graphJsonData.nodes.reduce((map, node) => {
|
||||
map.set(node.id, node)
|
||||
return map
|
||||
}, new Map())
|
||||
_graphJsonData.nodes = nodeMap
|
||||
|
||||
if (this?.topoViewOption?.aggregation_count) {
|
||||
const instance = this.$refs.showTopoView.getInstance()
|
||||
const nodes = instance.getNodes()
|
||||
const rootNodes = nodes.filter((node) => node.lot.level === 0)
|
||||
rootNodes.forEach((node) => {
|
||||
this.initMoreNodesData(node, _graphJsonData)
|
||||
})
|
||||
|
||||
this.$refs.showTopoView.setJsonData(_.cloneDeep({
|
||||
nodes: _graphJsonData.nodes.values(),
|
||||
links: _graphJsonData.links
|
||||
}))
|
||||
}
|
||||
this.topoViewJsonData = _graphJsonData
|
||||
this.changeTopoViewToolbarLang(this.$i18n.locale)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化子节点分页数据
|
||||
*/
|
||||
initMoreNodesData(node, jsonData) {
|
||||
const childs = node.lot.childs
|
||||
// 没有子节点 终止遍历
|
||||
if (!childs?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// 子节点分页数量
|
||||
const aggregation_count = this?.topoViewOption?.aggregation_count || 1
|
||||
// 展示节点数量
|
||||
let showNodeCount = 0
|
||||
|
||||
childs.forEach((childNode, index) => {
|
||||
if (childNode?.data?.btnType !== 'more') {
|
||||
if (showNodeCount >= aggregation_count) {
|
||||
const originNode = jsonData?.nodes?.get(childNode.id)
|
||||
if (originNode) {
|
||||
originNode.isHide = true
|
||||
}
|
||||
} else if (!childNode.isHide) {
|
||||
showNodeCount++
|
||||
}
|
||||
this.initMoreNodesData(childNode, jsonData)
|
||||
}
|
||||
})
|
||||
|
||||
if (childs.length - showNodeCount > 0) {
|
||||
const id = uuidv4()
|
||||
jsonData.nodes.set(id, {
|
||||
id,
|
||||
text: `展示更多(${childs.length - showNodeCount})`,
|
||||
data: {
|
||||
btnType: 'more'
|
||||
},
|
||||
color: 'transparent',
|
||||
styleClass: {
|
||||
padding: '0px'
|
||||
},
|
||||
})
|
||||
|
||||
jsonData.links.push({
|
||||
from: node.id,
|
||||
to: id,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async clickMoreBtn(node) {
|
||||
const childs = node?.lot?.parent?.lot?.childs
|
||||
if (childs?.length) {
|
||||
const topoViewJsonData = this.topoViewJsonData
|
||||
let moreBtnNode = null
|
||||
let showNodeCount = 0
|
||||
let toggleNodeCount = 0
|
||||
const aggregation_count = this?.topoViewOption?.aggregation_count || 1
|
||||
|
||||
childs.forEach((child) => {
|
||||
if (!child.isHide) {
|
||||
showNodeCount++
|
||||
}
|
||||
|
||||
if (toggleNodeCount < aggregation_count && child.isHide) {
|
||||
const childNode = topoViewJsonData?.nodes?.get(child.id)
|
||||
if (childNode) {
|
||||
childNode.isHide = false
|
||||
toggleNodeCount++
|
||||
showNodeCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (child.data.btnType === 'more') {
|
||||
moreBtnNode = topoViewJsonData?.nodes?.get(child.id)
|
||||
}
|
||||
})
|
||||
if (moreBtnNode) {
|
||||
if (showNodeCount === childs.length) {
|
||||
moreBtnNode.isHide = true
|
||||
} else {
|
||||
moreBtnNode.text = `展示更多(${childs.length - showNodeCount})`
|
||||
}
|
||||
}
|
||||
|
||||
const instance = this.$refs.showTopoView.getInstance()
|
||||
instance.setJsonData(
|
||||
{
|
||||
links: topoViewJsonData.links,
|
||||
nodes: topoViewJsonData.nodes.values()
|
||||
},
|
||||
false
|
||||
)
|
||||
this.topoViewJsonData = topoViewJsonData
|
||||
}
|
||||
},
|
||||
|
||||
handleOpenCmdb() {
|
||||
this.$refs.cmdbDrawer.open()
|
||||
},
|
||||
@@ -921,9 +1157,16 @@ export default {
|
||||
}
|
||||
},
|
||||
async showNodeTips(nodeObject, $event) {
|
||||
console.log('node click')
|
||||
console.log('node click', nodeObject)
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
|
||||
const btnType = nodeObject?.data?.btnType
|
||||
if (btnType === 'more') {
|
||||
this.clickMoreBtn(nodeObject)
|
||||
return
|
||||
}
|
||||
|
||||
const _base_position = this.$refs.showTopoView.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.rightTopoView.getBoundingClientRect()
|
||||
if (this.currentNode !== nodeObject) {
|
||||
this.currentNodeValues = null
|
||||
@@ -934,25 +1177,16 @@ export default {
|
||||
const [ attributes ] = await Promise.all([getSubscribeAttributes(rawNode.type_id)])
|
||||
this.currentNodeAttributes = attributes?.attributes || []
|
||||
if (!this.currentNodeAttributes.length) {
|
||||
this.errorMessage = this.$t('cmdb.topo.noPreferenceAttributes')
|
||||
this.errorMessageShow = true
|
||||
this.currentNodeValues = null
|
||||
this.isShowNodeTipsPanel = false
|
||||
this.handleNullNodeTips(this.$t('cmdb.topo.noPreferenceAttributes'))
|
||||
}
|
||||
await searchCI({ q: `_id:${rawNode.id}` }, false).then(res => {
|
||||
if (!res.result.length) {
|
||||
this.errorMessage = this.$t('cmdb.topo.noInstancePerm')
|
||||
this.errorMessageShow = true
|
||||
this.currentNodeValues = null
|
||||
this.isShowNodeTipsPanel = false
|
||||
this.handleNullNodeTips(this.$t('cmdb.topo.noInstancePerm'))
|
||||
} else {
|
||||
this.currentNodeValues = res.result[0]
|
||||
}
|
||||
}).catch(error => {
|
||||
this.errorMessage = ((error.response || {}).data || {}).message
|
||||
this.errorMessageShow = true
|
||||
this.currentNodeValues = null
|
||||
this.isShowNodeTipsPanel = false
|
||||
this.handleNullNodeTips(((error.response || {}).data || {}).message)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -971,9 +1205,74 @@ export default {
|
||||
this.isShowNodeTipsPanel = true
|
||||
console.log(this.nodeTipsPosition)
|
||||
},
|
||||
|
||||
handleNullNodeTips(errorMessage) {
|
||||
this.errorMessage = errorMessage
|
||||
this.errorMessageShow = true
|
||||
this.currentNodeValues = null
|
||||
this.isShowNodeTipsPanel = false
|
||||
this.currentNode = {}
|
||||
},
|
||||
|
||||
hideNodeTips(nodeObject, $event) {
|
||||
this.isShowNodeTipsPanel = false
|
||||
},
|
||||
|
||||
handleSearchTopoView(v) {
|
||||
const topoViewJsonData = this.topoViewJsonData
|
||||
topoViewJsonData.nodes.keys().forEach((key) => {
|
||||
const node = topoViewJsonData?.nodes?.get(key)
|
||||
if (node?.data?.btnType !== 'more') {
|
||||
node.opacity = node?.text?.indexOf(v) !== -1 ? 1 : 0.1
|
||||
}
|
||||
})
|
||||
const instance = this.$refs.showTopoView.getInstance()
|
||||
instance.setJsonData(
|
||||
{
|
||||
links: topoViewJsonData.links,
|
||||
nodes: topoViewJsonData.nodes.values()
|
||||
},
|
||||
false
|
||||
)
|
||||
this.topoViewJsonData = topoViewJsonData
|
||||
},
|
||||
|
||||
changeTopoViewToolbarLang(lang) {
|
||||
setTimeout(() => {
|
||||
const toolbarElements = document.getElementsByClassName('rel-toolbar')
|
||||
const zhlangMap = {
|
||||
'全屏/退出全屏': 'Full Screen/Exit Full Screen',
|
||||
'放大': 'zoom in',
|
||||
'缩小': 'zoom out',
|
||||
'刷新': 'refresh ',
|
||||
'下载图片': 'download image'
|
||||
}
|
||||
const enlangMap = {
|
||||
'Full Screen/Exit Full Screen': '全屏/退出全屏',
|
||||
'zoom in': '放大',
|
||||
'zoom out': '缩小',
|
||||
'refresh': '刷新 ',
|
||||
'download image': '下载图片'
|
||||
}
|
||||
|
||||
const toolbarElementArray = Array.from(toolbarElements ?? [])
|
||||
toolbarElementArray.forEach((toolbarElement) => {
|
||||
const childArray = Array.from(toolbarElement?.children || [])
|
||||
|
||||
if (childArray?.length) {
|
||||
childArray.forEach((node) => {
|
||||
const oldTitle = node?.getAttribute('title')
|
||||
if (oldTitle) {
|
||||
const newTitle = lang === 'en' ? zhlangMap[oldTitle] : enlangMap[oldTitle]
|
||||
if (newTitle) {
|
||||
node.setAttribute('title', newTitle)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1098,6 +1397,14 @@ export default {
|
||||
top: 40%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.relation-graph-search {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
.topo-left,
|
||||
.topo-right {
|
||||
@@ -1133,6 +1440,54 @@ export default {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.relation-graph-node {
|
||||
padding: 6px 3px;
|
||||
border-radius: 2px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
background-color: transparent;
|
||||
position: relative !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&-text {
|
||||
color: #000000;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-left: 6px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
&-image {
|
||||
max-height: 20px;
|
||||
max-width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .relation-graph {
|
||||
background-color: #FFFFFF;
|
||||
|
||||
.rel-node {
|
||||
padding: 0px;
|
||||
height: auto !important;
|
||||
}
|
||||
.rel-node-checked {
|
||||
box-shadow: none;
|
||||
}
|
||||
.c-expanded {
|
||||
background-color: rgb(64, 158, 255) !important;
|
||||
}
|
||||
.c-collapsed {
|
||||
background-color: rgb(64, 158, 255) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
@@ -11,6 +11,8 @@
|
||||
@primary-color_7: #f7f8fa;
|
||||
@primary-color_8: #b1c9ff;
|
||||
|
||||
@link-color: @primary-color;
|
||||
|
||||
// Neutral color
|
||||
@text-color_1: #1d2129;
|
||||
@text-color_2: #4e5969;
|
||||
|
@@ -1,5 +1,3 @@
|
||||
version: '2.19'
|
||||
|
||||
services:
|
||||
cmdb-db:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.3
|
||||
@@ -43,7 +41,7 @@ services:
|
||||
- redis
|
||||
|
||||
cmdb-api:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.5
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.4.6
|
||||
container_name: cmdb-api
|
||||
env_file:
|
||||
- .env
|
||||
@@ -78,12 +76,18 @@ services:
|
||||
new:
|
||||
aliases:
|
||||
- cmdb-api
|
||||
healthcheck:
|
||||
timeout: 3s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
test: "ps aux|grep -v grep|grep -v '1 root'|grep gunicorn || exit 1"
|
||||
|
||||
cmdb-ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.5
|
||||
image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-ui:2.4.6
|
||||
container_name: cmdb-ui
|
||||
depends_on:
|
||||
- cmdb-api
|
||||
cmdb-api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
CMDB_API_HOST: cmdb-api:5000
|
||||
|
25
docker/Dockerfile-API
Normal file
25
docker/Dockerfile-API
Normal file
@@ -0,0 +1,25 @@
|
||||
# ================================= API ================================
|
||||
FROM python:3.8-alpine AS cmdb-api
|
||||
|
||||
LABEL description="Python3.8,cmdb"
|
||||
|
||||
COPY cmdb-api /data/apps/cmdb
|
||||
|
||||
WORKDIR /data/apps/cmdb
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
|
||||
RUN apk add --no-cache tzdata gcc musl-dev libffi-dev openldap-dev python3-dev jpeg-dev zlib-dev build-base
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt \
|
||||
&& cp ./settings.example.py settings.py \
|
||||
&& sed -i "s#{user}:{password}@127.0.0.1:3306/{db}#cmdb:123456@mysql:3306/cmdb#g" settings.py \
|
||||
&& sed -i "s#redis://127.0.0.1#redis://redis#g" settings.py \
|
||||
&& sed -i "s#CACHE_REDIS_HOST = '127.0.0.1'#CACHE_REDIS_HOST = 'redis'#g" settings.py
|
||||
|
||||
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
|
||||
RUN chmod +x /wait
|
||||
|
||||
CMD ["bash", "-c", "flask run"]
|
16
docker/Dockerfile-UI
Normal file
16
docker/Dockerfile-UI
Normal file
@@ -0,0 +1,16 @@
|
||||
# ================================= UI ================================
|
||||
FROM node:14.17.3-alpine AS builder
|
||||
|
||||
LABEL description="cmdb-ui"
|
||||
|
||||
COPY cmdb-ui /data/apps/cmdb-ui
|
||||
|
||||
WORKDIR /data/apps/cmdb-ui
|
||||
|
||||
RUN sed -i "s#http://127.0.0.1:5000##g" .env && yarn install --ignore-engines && yarn build
|
||||
|
||||
FROM nginx:alpine AS cmdb-ui
|
||||
|
||||
RUN mkdir /etc/nginx/html && rm -f /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY --from=builder /data/apps/cmdb-ui/dist /etc/nginx/html/
|
@@ -1,48 +0,0 @@
|
||||
# ================================= UI ================================
|
||||
FROM node:16.0.0-alpine AS builder
|
||||
|
||||
LABEL description="cmdb-ui"
|
||||
|
||||
COPY ../cmdb-ui /data/apps/cmdb-ui
|
||||
|
||||
WORKDIR /data/apps/cmdb-ui
|
||||
|
||||
RUN sed -i "s#http://127.0.0.1:5000##g" .env && yarn install && yarn build
|
||||
|
||||
|
||||
FROM nginx:alpine AS cmdb-ui
|
||||
|
||||
RUN mkdir /etc/nginx/html && rm -f /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY --from=builder /data/apps/cmdb-ui/dist /etc/nginx/html/
|
||||
|
||||
|
||||
# ================================= API ================================
|
||||
FROM python:3.8-alpine AS cmdb-api
|
||||
|
||||
LABEL description="Python3.8,cmdb"
|
||||
|
||||
COPY ../cmdb-api /data/apps/cmdb
|
||||
|
||||
WORKDIR /data/apps/cmdb
|
||||
|
||||
RUN apk add --no-cache tzdata gcc musl-dev libffi-dev openldap-dev python3-dev jpeg-dev zlib-dev build-base
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt \
|
||||
&& cp ./settings.example.py settings.py \
|
||||
&& sed -i "s#{user}:{password}@127.0.0.1:3306/{db}#cmdb:123456@mysql:3306/cmdb#g" settings.py \
|
||||
&& sed -i "s#redis://127.0.0.1#redis://redis#g" settings.py \
|
||||
&& sed -i "s#CACHE_REDIS_HOST = '127.0.0.1'#CACHE_REDIS_HOST = 'redis'#g" settings.py
|
||||
|
||||
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
|
||||
RUN chmod +x /wait
|
||||
|
||||
CMD ["bash", "-c", "flask run"]
|
||||
|
||||
|
||||
# ================================= Search ================================
|
||||
FROM docker.elastic.co/elasticsearch/elasticsearch:7.4.2 AS cmdb-search
|
||||
|
||||
RUN yes | ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
|
Reference in New Issue
Block a user