mirror of
https://github.com/veops/cmdb.git
synced 2025-09-03 03:06:56 +08:00
Compare commits
87 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 | ||
|
65ef58dea9 | ||
|
0a2e7aa99f | ||
|
8875e75883 | ||
|
2f03639c57 | ||
|
49bc5d94a9 | ||
|
39354e1293 | ||
|
d3714f3ecf | ||
|
729a616282 | ||
|
2d3a290aa3 | ||
|
9e885a5b12 | ||
|
f5822d7cba | ||
|
21ea553e74 | ||
|
e63038d1b6 | ||
|
d56806f511 | ||
|
7ac7fdc08e | ||
|
ba11707146 | ||
|
d49dc8a067 | ||
|
6bfb34fe2a | ||
|
2c7ed8c32d | ||
|
5b275af54e | ||
|
dde7ec6246 | ||
|
9181817e96 | ||
|
46b54bb7f2 | ||
|
fe63310c4e | ||
|
27c733aa2c | ||
|
2a8e9e684e | ||
|
095190a785 | ||
|
ef25c94b5d | ||
|
06ae1bcf13 | ||
|
9ead4e7d8d | ||
|
994a28dd25 | ||
|
74b587e46c | ||
|
091cd882bd | ||
|
73093db467 | ||
|
66e268ce68 | ||
|
a41d1a5e97 | ||
|
b4b728fe28 | ||
|
d16462d8b7 | ||
|
de7d98c0b4 | ||
|
51332c7236 | ||
|
bf1076fe4a | ||
|
3454a98cfb | ||
|
506dcbb40e | ||
|
5ac4517187 |
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
||||
MYSQL_ROOT_PASSWORD='123456'
|
||||
MYSQL_HOST='mysql'
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER='cmdb'
|
||||
MYSQL_DATABASE='cmdb'
|
||||
MYSQL_PASSWORD='123456'
|
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
|
@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
|
||||
environs = "==4.2.0"
|
||||
marshmallow = "==2.20.2"
|
||||
# async tasks
|
||||
celery = ">=5.3.1"
|
||||
celery = "==5.3.1"
|
||||
celery_once = "==3.0.1"
|
||||
more-itertools = "==5.0.0"
|
||||
kombu = ">=5.3.1"
|
||||
|
@@ -128,7 +128,7 @@ def cmdb_init_acl():
|
||||
perms = [PermEnum.READ]
|
||||
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
|
||||
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
|
||||
elif resource_type == ResourceTypeEnum.RELATION_VIEW:
|
||||
elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
|
||||
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
|
||||
|
||||
ResourceTypeCRUD.add(app_id, resource_type, '', perms)
|
||||
@@ -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
|
||||
@@ -326,7 +331,7 @@ def cmdb_inner_secrets_init(address):
|
||||
"""
|
||||
init inner secrets for password feature
|
||||
"""
|
||||
res, ok = KeyManage(backend=InnerKVManger).init()
|
||||
res, ok = KeyManage(backend=InnerKVManger()).init()
|
||||
if not ok:
|
||||
if res.get("status") == "failed":
|
||||
KeyManage.print_response(res)
|
||||
@@ -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:
|
||||
@@ -264,9 +264,11 @@ class CIManager(object):
|
||||
for attr_id in constraint.attr_ids:
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id,
|
||||
to_dict=False,
|
||||
value=ci_dict.get(id2name[attr_id]) or None)])
|
||||
values = value_table.get_by(attr_id=attr_id,
|
||||
value=ci_dict.get(id2name[attr_id]) or None,
|
||||
only_query=True).join(
|
||||
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
|
||||
_ci_ids = set([i.ci_id for i in values])
|
||||
if ci_ids is None:
|
||||
ci_ids = _ci_ids
|
||||
else:
|
||||
@@ -381,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)
|
||||
@@ -419,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)
|
||||
@@ -429,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
|
||||
@@ -437,14 +440,17 @@ class CIManager(object):
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
|
||||
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
|
||||
|
||||
raw_dict = copy.deepcopy(ci_dict)
|
||||
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
@@ -459,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)
|
||||
@@ -489,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
|
||||
|
||||
@@ -497,19 +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
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
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)
|
||||
|
||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||
if ref_ci_dict:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
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)
|
||||
|
||||
@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)
|
||||
|
||||
@@ -724,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:
|
||||
@@ -749,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]
|
||||
@@ -765,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'))
|
||||
@@ -825,6 +843,149 @@ class CIManager(object):
|
||||
|
||||
return data.get('v')
|
||||
|
||||
def baseline(self, ci_ids, before_date):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
|
||||
if not ci_list:
|
||||
return dict()
|
||||
|
||||
ci2changed = dict()
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
ci2changed.setdefault(change['ci_id'], {})
|
||||
item = (change['old'],
|
||||
change['new'],
|
||||
change.get('is_list'),
|
||||
change.get('value_type'),
|
||||
change['operate_type'])
|
||||
if change.get('is_list'):
|
||||
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
|
||||
else:
|
||||
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
|
||||
|
||||
type2show_name = {}
|
||||
result = []
|
||||
for ci in ci_list:
|
||||
list_attr2item = {}
|
||||
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
|
||||
if not alias_name:
|
||||
continue
|
||||
if alias_name == ci.get('unique_alias'):
|
||||
continue
|
||||
|
||||
if ci.get('_type') not in type2show_name:
|
||||
ci_type = CITypeCache.get(ci.get('_type'))
|
||||
show_id = ci_type.show_id or ci_type.unique_id
|
||||
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
|
||||
|
||||
if isinstance(v, list):
|
||||
for old, new, is_list, value_type, operate_type in v:
|
||||
if alias_name not in list_attr2item:
|
||||
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
is_list=is_list,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=copy.deepcopy(ci.get(alias_name)),
|
||||
to=ci.get(alias_name) or [])
|
||||
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD:
|
||||
list_attr2item[alias_name]['to'].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
|
||||
list_attr2item[alias_name]['to'].append(old)
|
||||
continue
|
||||
|
||||
old, value_type = v[0], v[3]
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
if isinstance(old, (datetime.datetime, datetime.date)):
|
||||
old = str(old)
|
||||
if ci.get(alias_name) != old:
|
||||
item = dict(instance=ci.get(type2show_name[ci['_type']]),
|
||||
attr_name=alias_name,
|
||||
value_type=value_type,
|
||||
ci_type=ci.get('ci_type'),
|
||||
unique_alias=ci.get('unique_alias'),
|
||||
unique_value=ci.get(ci['unique_alias']),
|
||||
cur=ci.get(alias_name),
|
||||
to=old)
|
||||
result.append(item)
|
||||
|
||||
for alias_name, item in list_attr2item.items():
|
||||
if sorted(item['cur'] or []) != sorted(item['to'] or []):
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
def baseline_cis(self, ci_ids, before_date, fl=None):
|
||||
"""
|
||||
return CI changes
|
||||
:param ci_ids:
|
||||
:param before_date:
|
||||
:param fl:
|
||||
:return:
|
||||
"""
|
||||
ci_list = self.get_cis_by_ids(ci_ids, fields=fl)
|
||||
if not ci_list:
|
||||
return []
|
||||
|
||||
id2ci = {ci['_id']: ci for ci in ci_list}
|
||||
changed = AttributeHistoryManger.get_records_for_attributes(
|
||||
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
|
||||
for records in changed:
|
||||
for change in records[1]:
|
||||
if change['is_computed'] or change['is_password']:
|
||||
continue
|
||||
|
||||
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
continue
|
||||
|
||||
if change['is_list']:
|
||||
old, new, value_type, operate_type, ci_id, attr_name = (
|
||||
change['old'], change['new'], change['value_type'], change['operate_type'],
|
||||
change['ci_id'], change['attr_name'])
|
||||
old = ValueTypeMap.deserialize[value_type](old) if old else old
|
||||
new = ValueTypeMap.deserialize[value_type](new) if new else new
|
||||
if operate_type == OperateType.ADD and new in (id2ci[ci_id][attr_name] or []):
|
||||
id2ci[ci_id][attr_name].remove(new)
|
||||
elif operate_type == OperateType.DELETE and old not in id2ci[ci_id][attr_name]:
|
||||
id2ci[ci_id][attr_name].append(old)
|
||||
else:
|
||||
id2ci[change['ci_id']][change['attr_name']] = change['old']
|
||||
|
||||
return list(id2ci.values())
|
||||
|
||||
def rollback(self, ci_id, before_date):
|
||||
baseline_ci = self.baseline([ci_id], before_date)
|
||||
|
||||
payload = dict()
|
||||
for item in baseline_ci:
|
||||
payload[item.get('attr_name')] = item.get('to')
|
||||
|
||||
if payload:
|
||||
payload['ci_type'] = baseline_ci[0]['ci_type']
|
||||
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
|
||||
|
||||
self.update(ci_id, **payload)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
@@ -935,6 +1096,18 @@ class CIRelationManager(object):
|
||||
|
||||
return ci_ids, level2ids
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, ci_ids):
|
||||
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
|
||||
CI, CI.id == CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
|
||||
result = {}
|
||||
for ci in cis:
|
||||
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.commit()
|
||||
@@ -985,7 +1158,7 @@ class CIRelationManager(object):
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||
|
||||
if current_app.config.get('USE_ACL') and valid:
|
||||
if current_app.config.get('USE_ACL') and valid and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
@@ -1019,7 +1192,7 @@ class CIRelationManager(object):
|
||||
def delete(cr_id):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
@@ -1053,6 +1226,21 @@ class CIRelationManager(object):
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def delete_3(cls, first_ci_id, second_ci_id):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
if cr is not None:
|
||||
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
cls.delete(cr.id)
|
||||
|
||||
return cr
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
|
||||
"""
|
||||
@@ -1092,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):
|
||||
|
@@ -5,7 +5,6 @@ import copy
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
from werkzeug.exceptions import BadRequest
|
||||
@@ -28,12 +27,15 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import kwargs_required
|
||||
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.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
|
||||
@@ -53,6 +55,7 @@ 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):
|
||||
@@ -130,7 +133,9 @@ class CITypeManager(object):
|
||||
def add(cls, **kwargs):
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
app_cli = CMDBApp()
|
||||
validate_permission(app_cli.op.Model_Configuration, app_cli.resource_type_name,
|
||||
app_cli.op.create_CIType, app_cli.app_name)
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
@@ -224,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 []):
|
||||
@@ -247,10 +255,26 @@ 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.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.soft_delete(commit=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
@@ -414,9 +438,6 @@ class CITypeGroupManager(object):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
if name is not None and name != existed.name:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
existed.update(name=name)
|
||||
|
||||
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
|
||||
@@ -675,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()
|
||||
|
||||
@@ -746,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
|
||||
@@ -784,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
|
||||
|
||||
@@ -818,6 +851,72 @@ class CITypeRelationManager(object):
|
||||
|
||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||
|
||||
@staticmethod
|
||||
def get_relations_by_type_id(type_id):
|
||||
nodes, edges = [], []
|
||||
node_ids, edge_tuples = set(), set()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return nodes, edges
|
||||
|
||||
ci_type_dict = ci_type.to_dict()
|
||||
ci_type_dict.setdefault('level', [0])
|
||||
nodes.append(ci_type_dict)
|
||||
node_ids.add(ci_type.id)
|
||||
|
||||
def _find(_id, lv):
|
||||
lv += 1
|
||||
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
|
||||
if i.child_id not in node_ids:
|
||||
node_ids.add(i.child_id)
|
||||
node = i.child.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_find(i.child_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_find(i.child_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
def _reverse_find(_id, lv):
|
||||
lv -= 1
|
||||
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
|
||||
if i.parent_id not in node_ids:
|
||||
node_ids.add(i.parent_id)
|
||||
node = i.parent.to_dict()
|
||||
node.setdefault('level', []).append(lv)
|
||||
nodes.append(node)
|
||||
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
|
||||
_reverse_find(i.parent_id, lv)
|
||||
continue
|
||||
elif (i.parent_id, i.child_id) not in edge_tuples:
|
||||
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
|
||||
edge_tuples.add((i.parent_id, i.child_id))
|
||||
_reverse_find(i.parent_id, lv)
|
||||
|
||||
for _node in nodes:
|
||||
if _node['id'] == i.child_id and lv not in _node['level']:
|
||||
_node['level'].append(lv)
|
||||
|
||||
level = 0
|
||||
_reverse_find(ci_type.id, level)
|
||||
level = 0
|
||||
_find(ci_type.id, level)
|
||||
|
||||
return nodes, edges
|
||||
|
||||
@staticmethod
|
||||
def _get(parent_id, child_id):
|
||||
return CITypeRelation.get_by(parent_id=parent_id,
|
||||
@@ -831,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)
|
||||
|
||||
@@ -846,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"):
|
||||
@@ -874,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))
|
||||
@@ -1171,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,6 +55,9 @@ class CITypeOperateType(BaseEnum):
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
ADD_RECONCILIATION = "14" # 新增数据合规
|
||||
UPDATE_RECONCILIATION = "15" # 修改数据合规
|
||||
DELETE_RECONCILIATION = "16" # 删除数据合规
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
@@ -70,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
PAGE = "page" # read
|
||||
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
@@ -98,6 +102,12 @@ class AttributeDefaultValueEnum(BaseEnum):
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
class ExecuteStatusEnum(BaseEnum):
|
||||
COMPLETED = '0'
|
||||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
@@ -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
|
||||
@@ -26,7 +27,7 @@ from api.models.cmdb import OperationRecord
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
ci_id=None, attr_id=None, ci_ids=None, more=False):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
@@ -48,6 +49,9 @@ class AttributeHistoryManger(object):
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if ci_ids and isinstance(ci_ids, list):
|
||||
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
@@ -62,6 +66,12 @@ class AttributeHistoryManger(object):
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
if more:
|
||||
attr_hist['is_list'] = attr_hist['attr'].is_list
|
||||
attr_hist['is_computed'] = attr_hist['attr'].is_computed
|
||||
attr_hist['is_password'] = attr_hist['attr'].is_password
|
||||
attr_hist['default'] = attr_hist['attr'].default
|
||||
attr_hist['value_type'] = attr_hist['attr'].value_type
|
||||
attr_hist.pop("attr")
|
||||
|
||||
if record_id not in res:
|
||||
@@ -161,6 +171,7 @@ class AttributeHistoryManger(object):
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
value_type=attr.value_type,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
@@ -271,7 +282,7 @@ class CITypeHistoryManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
@@ -284,6 +295,7 @@ class CITypeHistoryManager(object):
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
rc_id=rc_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
@@ -295,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)
|
||||
|
@@ -25,6 +25,8 @@ from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceCITypeOrder
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
@@ -43,22 +45,46 @@ class PreferenceManager(object):
|
||||
def get_types(instance=False, tree=False):
|
||||
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
|
||||
|
||||
type2group = {}
|
||||
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
|
||||
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
|
||||
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
|
||||
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
|
||||
group_types = []
|
||||
other_types = []
|
||||
group2idx = {}
|
||||
type_ids = set()
|
||||
for ci_type in types:
|
||||
type_id = ci_type.type_id
|
||||
type_ids.add(type_id)
|
||||
type_dict = CITypeCache.get(type_id).to_dict()
|
||||
if type_id not in type2group:
|
||||
other_types.append(type_dict)
|
||||
else:
|
||||
group = type2group[type_id]
|
||||
if group['id'] not in group2idx:
|
||||
group_types.append(type2group[type_id])
|
||||
group2idx[group['id']] = len(group_types) - 1
|
||||
group_types[group2idx[group['id']]].setdefault('ci_types', []).append(type_dict)
|
||||
if other_types:
|
||||
group_types.append(dict(ci_types=other_types))
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
|
||||
ci_type_order) if i.is_tree}.get(x.type_id, 1))
|
||||
|
||||
type_ids = [i.type_id for i in types + tree_types]
|
||||
if types and tree_types:
|
||||
type_ids = set(type_ids)
|
||||
tree_types = [CITypeCache.get(_type.type_id).to_dict() for _type in tree_types]
|
||||
for _type in tree_types:
|
||||
type_ids.add(_type['id'])
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
|
||||
|
||||
@staticmethod
|
||||
def get_types2(instance=False, tree=False):
|
||||
|
@@ -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(
|
||||
@@ -78,6 +79,8 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
|
||||
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
|
||||
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
|
||||
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
|
||||
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
|
||||
|
||||
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
|
||||
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
|
||||
@@ -138,3 +141,12 @@ class ErrFormat(CommonErrFormat):
|
||||
|
||||
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
|
||||
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
|
||||
|
||||
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
|
||||
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
|
||||
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
|
||||
|
||||
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
|
||||
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
|
||||
# 因为该分组下定义了拓扑视图,不能删除
|
||||
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||
|
@@ -17,10 +17,12 @@ def search(query=None,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None,
|
||||
use_id_filter=False):
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter)
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
|
||||
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
|
||||
|
||||
return s
|
||||
|
@@ -47,7 +47,8 @@ class Search(object):
|
||||
excludes=None,
|
||||
parent_node_perm_passed=False,
|
||||
use_id_filter=False,
|
||||
use_ci_filter=True):
|
||||
use_ci_filter=True,
|
||||
only_ids=False):
|
||||
self.orig_query = query
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
@@ -64,10 +65,12 @@ class Search(object):
|
||||
self.parent_node_perm_passed = parent_node_perm_passed
|
||||
self.use_id_filter = use_id_filter
|
||||
self.use_ci_filter = use_ci_filter
|
||||
self.only_ids = only_ids
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
@@ -590,6 +593,8 @@ class Search(object):
|
||||
def search(self):
|
||||
numfound, ci_ids = self._query_build_raw()
|
||||
ci_ids = list(map(str, ci_ids))
|
||||
if self.only_ids:
|
||||
return ci_ids
|
||||
|
||||
_fl = self._fl_build()
|
||||
|
||||
|
@@ -69,7 +69,7 @@ class Search(object):
|
||||
if _l < int(level) and c == ConstraintEnum.Many2Many:
|
||||
self.has_m2m = True
|
||||
|
||||
self.type2filter_perms = None
|
||||
self.type2filter_perms = {}
|
||||
|
||||
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
|
||||
@@ -79,7 +79,7 @@ class Search(object):
|
||||
key = []
|
||||
_tmp = []
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||
if len(self.descendant_ids or []) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
|
||||
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
@@ -151,9 +151,9 @@ class Search(object):
|
||||
|
||||
return True
|
||||
|
||||
def search(self):
|
||||
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1
|
||||
parent_node_perm_passed = self._has_read_perm_from_parent_nodes()
|
||||
def search(self, only_ids=False):
|
||||
use_ci_filter = len(self.descendant_ids or []) == self.level[0] - 1
|
||||
parent_node_perm_passed = not self.is_app_admin and self._has_read_perm_from_parent_nodes()
|
||||
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||
@@ -197,7 +197,8 @@ class Search(object):
|
||||
sort=self.sort,
|
||||
ci_ids=merge_ids,
|
||||
parent_node_perm_passed=parent_node_perm_passed,
|
||||
use_ci_filter=use_ci_filter).search()
|
||||
use_ci_filter=use_ci_filter,
|
||||
only_ids=only_ids).search()
|
||||
|
||||
def _get_ci_filter(self, filter_perms, ci_filters=None):
|
||||
ci_filters = ci_filters or []
|
||||
|
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
251
cmdb-api/api/lib/cmdb/topology.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search as ci_search
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import TopologyView
|
||||
from api.models.cmdb import TopologyViewGroup
|
||||
|
||||
|
||||
class TopologyViewManager(object):
|
||||
group_cls = TopologyViewGroup
|
||||
cls = TopologyView
|
||||
|
||||
@classmethod
|
||||
def get_name_by_id(cls, _id):
|
||||
res = cls.cls.get_by_id(_id)
|
||||
return res and res.name
|
||||
|
||||
def get_view_by_id(self, _id):
|
||||
res = self.cls.get_by_id(_id)
|
||||
|
||||
return res and res.to_dict() or {}
|
||||
|
||||
@classmethod
|
||||
def add_group(cls, name, order):
|
||||
if order is None:
|
||||
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
|
||||
400, ErrFormat.topology_group_exists.format(name))
|
||||
|
||||
return cls.group_cls.create(name=name, order=order)
|
||||
|
||||
def update_group(self, group_id, name, view_ids):
|
||||
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
|
||||
if name is not None and name != existed.name:
|
||||
existed.update(name=name)
|
||||
|
||||
for idx, view_id in enumerate(view_ids):
|
||||
view = self.cls.get_by_id(view_id)
|
||||
if view is not None:
|
||||
view.update(group_id=group_id, order=idx)
|
||||
|
||||
return existed.to_dict()
|
||||
|
||||
@classmethod
|
||||
def delete_group(cls, _id):
|
||||
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
if cls.cls.get_by(group_id=_id, first=True):
|
||||
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@classmethod
|
||||
def group_order(cls, group_ids):
|
||||
for idx, group_id in enumerate(group_ids):
|
||||
group = cls.group_cls.get_by_id(group_id)
|
||||
group.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, group_id, option, order=None, **kwargs):
|
||||
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
|
||||
if order is None:
|
||||
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
|
||||
cls.cls.order.desc()).first()
|
||||
cur_max_order = cur_max_order and cur_max_order.order or 0
|
||||
order = cur_max_order + 1
|
||||
|
||||
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL'):
|
||||
try:
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
ACLManager().grant_resource_to_role(name,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
existed_name = existed.name
|
||||
|
||||
inst = existed.update(filter_none=False, **kwargs).to_dict()
|
||||
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
|
||||
try:
|
||||
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
existed.soft_delete()
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
|
||||
|
||||
@classmethod
|
||||
def group_inner_order(cls, _ids):
|
||||
for idx, _id in enumerate(_ids):
|
||||
topology = cls.cls.get_by_id(_id)
|
||||
topology.update(order=idx + 1)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)])
|
||||
|
||||
groups = cls.group_cls.get_by(to_dict=True)
|
||||
groups = sorted(groups, key=lambda x: x['order'])
|
||||
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
|
||||
|
||||
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
|
||||
other_group = dict(views=[])
|
||||
for view in topo_views:
|
||||
if resources is not None and view['name'] not in resources:
|
||||
continue
|
||||
|
||||
if view['group_id']:
|
||||
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
|
||||
else:
|
||||
other_group['views'].append(view)
|
||||
|
||||
if other_group['views']:
|
||||
groups.append(other_group)
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def relation_from_ci_type(type_id):
|
||||
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
|
||||
|
||||
return dict(nodes=nodes, edges=edges)
|
||||
|
||||
def topology_view(self, view_id=None, preview=None):
|
||||
if view_id is not None:
|
||||
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
central_node_type, central_node_instances, path = (view.central_node_type,
|
||||
view.central_node_instances, view.path)
|
||||
else:
|
||||
central_node_type = preview.get('central_node_type')
|
||||
central_node_instances = preview.get('central_node_instances')
|
||||
path = preview.get('path')
|
||||
|
||||
nodes, links = [], []
|
||||
_type = CITypeCache.get(central_node_type)
|
||||
if not _type:
|
||||
return dict(nodes=nodes, links=links)
|
||||
type2meta = {_type.id: _type.icon}
|
||||
root_ids = []
|
||||
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
|
||||
|
||||
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
|
||||
central_node_instances)
|
||||
s = ci_search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.info(e)
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
root_ids.append(i['_id'])
|
||||
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
|
||||
if not root_ids:
|
||||
return dict(nodes=nodes, links=links)
|
||||
|
||||
prefix = REDIS_PREFIX_CI_RELATION
|
||||
key = list(map(str, root_ids))
|
||||
id2node = {}
|
||||
for level in sorted([i for i in path.keys() if int(i) > 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
new_key = []
|
||||
for idx, from_id in enumerate(key):
|
||||
for to_id, type_id in res[idx]:
|
||||
if type_id in type_ids:
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[to_id] = {'id': to_id, 'type_id': type_id}
|
||||
new_key.append(to_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
key = new_key
|
||||
|
||||
ci_ids = list(map(int, root_ids))
|
||||
for level in sorted([i for i in path.keys() if int(i) < 0]):
|
||||
type_ids = {int(i) for i in path[level]}
|
||||
res = CIRelationManager.get_parent_ids(ci_ids)
|
||||
_ci_ids = []
|
||||
for to_id in res:
|
||||
for from_id, type_id in res[to_id]:
|
||||
if type_id in type_ids:
|
||||
from_id, to_id = str(from_id), str(to_id)
|
||||
links.append({'from': from_id, 'to': to_id})
|
||||
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
|
||||
_ci_ids.append(from_id)
|
||||
if type_id not in type2meta:
|
||||
type2meta[type_id] = CITypeCache.get(type_id).icon
|
||||
|
||||
ci_ids = _ci_ids
|
||||
|
||||
fl = set()
|
||||
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
|
||||
type2show = {}
|
||||
for type_id in type_ids:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type:
|
||||
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||
if attr:
|
||||
fl.add(attr.name)
|
||||
type2show[type_id] = attr.name
|
||||
|
||||
if id2node:
|
||||
s = ci_search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
|
||||
use_id_filter=False, use_ci_filter=False, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError:
|
||||
return dict(nodes=nodes, links=links)
|
||||
for i in response:
|
||||
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
|
||||
nodes.extend(id2node.values())
|
||||
|
||||
return dict(nodes=nodes, links=links, type2meta=type2meta)
|
@@ -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:
|
||||
@@ -274,41 +275,66 @@ class AttributeValueManager(object):
|
||||
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
|
||||
existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
|
||||
i.value or i.value == 0 else i.value) for i in existed_attrs]
|
||||
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
# Comparison array starts from which position changes
|
||||
min_len = min(len(value), len(existed_values))
|
||||
index = 0
|
||||
while index < min_len:
|
||||
if value[index] != existed_values[index]:
|
||||
break
|
||||
index += 1
|
||||
|
||||
# Delete first and then add to ensure id sorting
|
||||
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, v, 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)
|
||||
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
|
||||
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
|
||||
existed_value or existed_value == 0 else existed_value)
|
||||
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:
|
||||
if existed_value != value and existed_attr:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
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):
|
||||
|
@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
|
||||
|
||||
def validate_app(app_id):
|
||||
app = AppCache.get(app_id)
|
||||
return app.id if app else None
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self, app_name='acl', uid=None):
|
||||
self.log = current_app.logger
|
||||
@@ -133,7 +138,8 @@ class ACLManager(object):
|
||||
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
|
||||
return res
|
||||
|
||||
def grant_resource(self, rid, resource_id, perms):
|
||||
@staticmethod
|
||||
def grant_resource(rid, resource_id, perms):
|
||||
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
|
||||
|
||||
@staticmethod
|
||||
@@ -141,3 +147,7 @@ class ACLManager(object):
|
||||
rt = AppCRUD.add(**payload)
|
||||
|
||||
return rt.to_dict()
|
||||
|
||||
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
|
||||
app_id = validate_app(self.app_name)
|
||||
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)
|
||||
|
38
cmdb-api/api/lib/common_setting/decorator.py
Normal file
38
cmdb-api/api/lib/common_setting/decorator.py
Normal file
@@ -0,0 +1,38 @@
|
||||
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):
|
||||
def decorator_perms_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_required(*args, **kwargs):
|
||||
acl = ACLManager(app_name)
|
||||
has_perms = False
|
||||
try:
|
||||
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
|
||||
except Exception as e:
|
||||
# resource_type not exist, continue check role
|
||||
if role_name:
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
|
||||
abort(403, ErrFormat.role_required.format(role_name))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
|
||||
|
||||
if not has_perms:
|
||||
if role_name:
|
||||
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))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_required
|
||||
|
||||
return decorator_perms_role_required
|
@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
|
||||
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
|
||||
|
||||
company_wide = _l("Company wide") # 全公司
|
||||
|
||||
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"
|
||||
|
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
64
cmdb-api/api/lib/common_setting/role_perm_base.py
Normal file
@@ -0,0 +1,64 @@
|
||||
class OperationPermission(object):
|
||||
|
||||
def __init__(self, resource_perms):
|
||||
for _r in resource_perms:
|
||||
setattr(self, _r['page'], _r['page'])
|
||||
for _p in _r['perms']:
|
||||
setattr(self, _p, _p)
|
||||
|
||||
|
||||
class BaseApp(object):
|
||||
resource_type_name = 'OperationPermission'
|
||||
all_resource_perms = []
|
||||
|
||||
def __init__(self):
|
||||
self.admin_name = None
|
||||
self.roles = []
|
||||
self.app_name = 'acl'
|
||||
self.require_create_resource_type = self.resource_type_name
|
||||
self.extra_create_resource_type_list = []
|
||||
|
||||
self.op = None
|
||||
|
||||
@staticmethod
|
||||
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
|
||||
return dict(
|
||||
role_name=role_name,
|
||||
role_type=role_type,
|
||||
acl_rid=acl_rid,
|
||||
description=description,
|
||||
resource_perms=resource_perms,
|
||||
)
|
||||
|
||||
|
||||
class CMDBApp(BaseApp):
|
||||
all_resource_perms = [
|
||||
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
|
||||
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
|
||||
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
|
||||
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
|
||||
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
|
||||
{"page": "Model_Configuration", "page_cn": "模型配置",
|
||||
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
|
||||
"delete_CIType_group", "download_CIType"]},
|
||||
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
|
||||
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
|
||||
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
|
||||
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
|
||||
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
|
||||
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
|
||||
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]},
|
||||
{"page": "TopologyView", "page_cn": "拓扑视图",
|
||||
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
|
||||
"create_topology_view"],
|
||||
},
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.admin_name = 'cmdb_admin'
|
||||
self.app_name = 'cmdb'
|
||||
|
||||
self.op = OperationPermission(self.all_resource_perms)
|
@@ -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):
|
||||
|
@@ -153,11 +153,11 @@ class ACLManager(object):
|
||||
if resource:
|
||||
return ResourceCRUD.delete(resource.id, rebuild=rebuild)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None):
|
||||
def has_permission(self, resource_name, resource_type, perm, resource_id=None, rid=None):
|
||||
if is_app_admin(self.app_id):
|
||||
return True
|
||||
|
||||
role = self._get_role(current_user.username)
|
||||
role = self._get_role(current_user.username) if rid is None else RoleCache.get(rid)
|
||||
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import msgpack
|
||||
import redis_lock
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.models.acl import App
|
||||
@@ -157,9 +158,10 @@ class RoleRelationCache(object):
|
||||
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
|
||||
|
||||
@classmethod
|
||||
def get_parent_ids(cls, rid, app_id):
|
||||
def get_parent_ids(cls, rid, app_id, force=False):
|
||||
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
|
||||
if not parent_ids:
|
||||
if not parent_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
|
||||
@@ -167,9 +169,10 @@ class RoleRelationCache(object):
|
||||
return parent_ids
|
||||
|
||||
@classmethod
|
||||
def get_child_ids(cls, rid, app_id):
|
||||
def get_child_ids(cls, rid, app_id, force=False):
|
||||
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
|
||||
if not child_ids:
|
||||
if not child_ids or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
|
||||
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
|
||||
@@ -177,14 +180,16 @@ class RoleRelationCache(object):
|
||||
return child_ids
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls, rid, app_id):
|
||||
def get_resources(cls, rid, app_id, force=False):
|
||||
"""
|
||||
:param rid:
|
||||
:param app_id:
|
||||
:param force:
|
||||
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
|
||||
"""
|
||||
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
|
||||
if not resources:
|
||||
if not resources or force:
|
||||
db.session.commit()
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
resources = RoleCRUD.get_resources(rid, app_id)
|
||||
if resources['id2perms'] or resources['group2perms']:
|
||||
@@ -193,9 +198,10 @@ class RoleRelationCache(object):
|
||||
return resources or {}
|
||||
|
||||
@classmethod
|
||||
def get_resources2(cls, rid, app_id):
|
||||
def get_resources2(cls, rid, app_id, force=False):
|
||||
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
if not r_g:
|
||||
if not r_g or force:
|
||||
db.session.commit()
|
||||
res = cls.get_resources(rid, app_id)
|
||||
id2perms = res['id2perms']
|
||||
group2perms = res['group2perms']
|
||||
@@ -224,22 +230,28 @@ class RoleRelationCache(object):
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild(cls, rid, app_id):
|
||||
cls.clean(rid, app_id)
|
||||
|
||||
cls.get_parent_ids(rid, app_id)
|
||||
cls.get_child_ids(rid, app_id)
|
||||
resources = cls.get_resources(rid, app_id)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, app_id)
|
||||
if app_id is None:
|
||||
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, app_id)
|
||||
cls.get_resources2(rid, app_id)
|
||||
app_ids = [app_id]
|
||||
|
||||
for _app_id in app_ids:
|
||||
cls.clean(rid, _app_id)
|
||||
|
||||
cls.get_parent_ids(rid, _app_id, force=True)
|
||||
cls.get_child_ids(rid, _app_id, force=True)
|
||||
resources = cls.get_resources(rid, _app_id, force=True)
|
||||
if resources.get('id2perms') or resources.get('group2perms'):
|
||||
HasResourceRoleCache.add(rid, _app_id)
|
||||
else:
|
||||
HasResourceRoleCache.remove(rid, _app_id)
|
||||
cls.get_resources2(rid, _app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
@flush_db
|
||||
def rebuild2(cls, rid, app_id):
|
||||
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
|
||||
cls.get_resources2(rid, app_id)
|
||||
cls.get_resources2(rid, app_id, force=True)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, rid, app_id):
|
||||
|
@@ -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:
|
||||
@@ -274,12 +290,14 @@ class PermissionCRUD(object):
|
||||
perm2resource.setdefault(_perm, []).append(resource_id)
|
||||
for _perm in perm2resource:
|
||||
perm = PermissionCache.get(_perm, resource_type_id)
|
||||
existeds = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in existeds:
|
||||
if perm is None:
|
||||
continue
|
||||
exists = RolePermission.get_by(rid=rid,
|
||||
app_id=app_id,
|
||||
perm_id=perm.id,
|
||||
__func_in___key_resource_id=perm2resource[_perm],
|
||||
to_dict=False)
|
||||
for existed in exists:
|
||||
existed.deleted = True
|
||||
existed.deleted_at = datetime.datetime.now()
|
||||
db.session.add(existed)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
|
||||
existed_ids = [i.id for i in existed]
|
||||
current_ids = []
|
||||
|
||||
rebuild_rids = set()
|
||||
for i in existed:
|
||||
if i.name not in perms:
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
|
||||
rp.soft_delete(commit=False)
|
||||
rebuild_rids.add((rp.app_id, rp.rid))
|
||||
else:
|
||||
current_ids.append(i.id)
|
||||
db.session.commit()
|
||||
for _app_id, _rid in rebuild_rids:
|
||||
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
|
||||
|
||||
for i in perms:
|
||||
if i not in existed_names:
|
||||
@@ -309,9 +315,12 @@ class ResourceCRUD(object):
|
||||
return resource
|
||||
|
||||
@staticmethod
|
||||
def delete(_id, rebuild=True):
|
||||
def delete(_id, rebuild=True, app_id=None):
|
||||
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
if app_id is not None and resource.app_id != app_id:
|
||||
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
|
||||
|
||||
origin = resource.to_dict()
|
||||
resource.soft_delete()
|
||||
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
|
||||
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
parent = RoleCache.get(i.parent_id)
|
||||
if parent:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
db.session.commit()
|
||||
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
result = []
|
||||
for child_id in child_ids:
|
||||
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
|
||||
if existed:
|
||||
continue
|
||||
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
if parent_id in cls.recursive_child_ids(child_id, app_id):
|
||||
return abort(400, ErrFormat.inheritance_dead_loop)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
|
||||
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
|
||||
RoleRelationCache.clean(parent_id, app_id)
|
||||
RoleRelationCache.clean(child_id, app_id)
|
||||
|
||||
if app_id is None:
|
||||
for app in AppCRUD.get_all():
|
||||
if app.name != "acl":
|
||||
RoleRelationCache.clean(child_id, app.id)
|
||||
|
||||
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
|
||||
AuditScope.role_relation, role.id, {}, {},
|
||||
@@ -372,16 +379,16 @@ class RoleCRUD(object):
|
||||
resource_type_id = resource_type and resource_type.id
|
||||
|
||||
result = dict(resources=dict(), groups=dict())
|
||||
s = time.time()
|
||||
# s = time.time()
|
||||
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
|
||||
current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
# current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
|
||||
for parent_id in parent_ids:
|
||||
|
||||
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
|
||||
current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('middle1: {0}'.format(time.time() - s))
|
||||
_merge(result['resources'], _resources)
|
||||
current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
current_app.logger.info(len(_groups))
|
||||
# current_app.logger.info('middle2: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info(len(_groups))
|
||||
if not group_flat:
|
||||
_merge(result['groups'], _groups)
|
||||
else:
|
||||
@@ -392,7 +399,7 @@ class RoleCRUD(object):
|
||||
item.setdefault('permissions', [])
|
||||
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
|
||||
result['resources'][item['id']] = item
|
||||
current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
# current_app.logger.info('End: {0}'.format(time.time() - s))
|
||||
|
||||
result['resources'] = list(result['resources'].values())
|
||||
result['groups'] = list(result['groups'].values())
|
||||
|
@@ -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!") # 只有创建人或者管理员才有权限!
|
||||
|
@@ -3,8 +3,8 @@ import os
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from Cryptodome.Protocol.SecretSharing import Shamir
|
||||
from colorama import Back, Fore, Style, init as colorama_init
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
@@ -30,6 +30,7 @@ seal_status = True
|
||||
secrets_encrypt_key = ""
|
||||
secrets_root_key = ""
|
||||
|
||||
|
||||
def string_to_bytes(value):
|
||||
if not value:
|
||||
return ""
|
||||
@@ -78,7 +79,7 @@ class KeyManage:
|
||||
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
|
||||
|
||||
self.backend = backend
|
||||
threading.Thread(target=self.watch_root_key, args=(app,)).start()
|
||||
threading.Thread(target=self.watch_root_key, args=(app,), daemon=True).start()
|
||||
|
||||
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
|
||||
if not self.trigger:
|
||||
@@ -412,7 +413,7 @@ class KeyManage:
|
||||
class InnerCrypt:
|
||||
def __init__(self):
|
||||
self.encrypt_key = b64decode(secrets_encrypt_key)
|
||||
#self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
# self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
@@ -490,4 +491,4 @@ if __name__ == "__main__":
|
||||
t_ciphertext, status1 = c.encrypt(t_plaintext)
|
||||
print("Ciphertext:", t_ciphertext)
|
||||
decrypted_plaintext, status2 = c.decrypt(t_ciphertext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
||||
print("Decrypted plaintext:", decrypted_plaintext)
|
||||
|
@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
|
||||
|
||||
params = webhook.get('parameters') or None
|
||||
if isinstance(params, dict):
|
||||
params = json.loads(Template(json.dumps(params)).render(payload))
|
||||
params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
|
||||
|
||||
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
||||
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
|
||||
data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
|
||||
auth = _wrap_auth(**webhook.get('authorization', {}))
|
||||
|
||||
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
||||
|
@@ -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}
|
||||
|
||||
@@ -209,6 +213,26 @@ class CITriggerHistory(Model):
|
||||
webhook = db.Column(db.Text)
|
||||
|
||||
|
||||
class TopologyViewGroup(Model):
|
||||
__tablename__ = 'c_topology_view_groups'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
|
||||
|
||||
class TopologyView(Model):
|
||||
__tablename__ = 'c_topology_views'
|
||||
|
||||
name = db.Column(db.String(64), index=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
|
||||
category = db.Column(db.String(32))
|
||||
central_node_type = db.Column(db.Integer)
|
||||
central_node_instances = db.Column(db.Text)
|
||||
path = db.Column(db.JSON)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
option = db.Column(db.JSON)
|
||||
|
||||
|
||||
class CITypeUniqueConstraint(Model):
|
||||
__tablename__ = "c_c_t_u_c"
|
||||
|
||||
@@ -432,6 +456,7 @@ class CITypeHistory(Model):
|
||||
|
||||
attr_id = db.Column(db.Integer)
|
||||
trigger_id = db.Column(db.Integer)
|
||||
rc_id = db.Column(db.Integer)
|
||||
unique_constraint_id = db.Column(db.Integer)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
@@ -541,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"
|
||||
|
||||
@@ -570,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"
|
||||
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import rd
|
||||
from api.lib.decorator import flush_db
|
||||
from api.lib.decorator import reconnect_db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -25,14 +26,14 @@ from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
||||
|
||||
@celery.task(name="acl.role_rebuild",
|
||||
queue=ACL_QUEUE,)
|
||||
@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
|
||||
@flush_db
|
||||
@reconnect_db
|
||||
def role_rebuild(rids, app_id):
|
||||
rids = rids if isinstance(rids, list) else [rids]
|
||||
for rid in rids:
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
||||
|
@@ -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))
|
||||
|
||||
|
||||
@@ -113,18 +123,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
|
||||
first=True, to_dict=False)
|
||||
if str(child_id) not in children:
|
||||
children[str(child_id)] = cr.second_ci.type_id
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
@@ -191,16 +200,15 @@ def ci_relation_add(parent_dict, child_id, uid):
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
if ancestor_ids is None:
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
if str(child_id) in children:
|
||||
children.pop(str(child_id))
|
||||
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
|
||||
|
||||
else:
|
||||
if ancestor_ids is not None:
|
||||
key = "{},{}".format(ancestor_ids, parent_id)
|
||||
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
|
||||
grandson = json.loads(grandson) if grandson is not None else {}
|
||||
@@ -251,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-03-29 10:42+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,212 +238,248 @@ 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:82
|
||||
msgid "Duplicated reconciliation rule"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
msgid "Reconciliation rule {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:85
|
||||
msgid "Operation record {} does not exist"
|
||||
msgstr "操作记录 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:83
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
msgid "Unique identifier cannot be deleted"
|
||||
msgstr "不能删除唯一标识"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:84
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
msgid "Cannot delete default sorted attributes"
|
||||
msgstr "不能删除默认排序的属性"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:86
|
||||
#: api/lib/cmdb/resp_format.py:89
|
||||
msgid "No node selected"
|
||||
msgstr "没有选择节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:87
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
msgid "This search option does not exist!"
|
||||
msgstr "该搜索选项不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:88
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
msgid "This search option has a duplicate name!"
|
||||
msgstr "该搜索选项命名重复!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:90
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
msgid "Relationship type {} already exists"
|
||||
msgstr "关系类型 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:91
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
msgid "Relationship type {} does not exist"
|
||||
msgstr "关系类型 {} 不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:93
|
||||
#: api/lib/cmdb/resp_format.py:96
|
||||
msgid "Invalid attribute value: {}"
|
||||
msgstr "无效的属性值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:94
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
msgid "{} Invalid value: {}"
|
||||
msgstr "{} 无效的值: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:95
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
msgid "{} is not in the predefined values"
|
||||
msgstr "{} 不在预定义值里"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:97
|
||||
#: api/lib/cmdb/resp_format.py:100
|
||||
msgid "The value of attribute {} must be unique, {} already exists"
|
||||
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:98
|
||||
#: api/lib/cmdb/resp_format.py:101
|
||||
msgid "Attribute {} value must exist"
|
||||
msgstr "属性 {} 值必须存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:99
|
||||
#: 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:101
|
||||
#: api/lib/cmdb/resp_format.py:104
|
||||
msgid "Unknown error when adding or modifying attribute value: {}"
|
||||
msgstr "新增或者修改属性值未知错误: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:103
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
msgid "Duplicate custom name"
|
||||
msgstr "订制名重复"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:105
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
msgid "Number of models exceeds limit: {}"
|
||||
msgstr "模型数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:106
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
msgid "The number of CIs exceeds the limit: {}"
|
||||
msgstr "CI数超过限制: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:108
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
msgid "Auto-discovery rule: {} already exists!"
|
||||
msgstr "自动发现规则: {} 已经存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:109
|
||||
#: api/lib/cmdb/resp_format.py:112
|
||||
msgid "Auto-discovery rule: {} does not exist!"
|
||||
msgstr "自动发现规则: {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:111
|
||||
#: 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:113
|
||||
#: 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:114
|
||||
#: 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:115
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
msgid "Attribute does not include unique identifier: {}"
|
||||
msgstr "属性字段没有包括唯一标识: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:116
|
||||
#: api/lib/cmdb/resp_format.py:119
|
||||
msgid "The auto-discovery instance does not exist!"
|
||||
msgstr "自动发现的实例不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:117
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
msgid "The model is not associated with this auto-discovery!"
|
||||
msgstr "模型并未关联该自动发现!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:118
|
||||
#: api/lib/cmdb/resp_format.py:121
|
||||
msgid "Only the creator can modify the Secret!"
|
||||
msgstr "只有创建人才能修改Secret!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:120
|
||||
#: 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:122
|
||||
#: 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:124
|
||||
#: 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:125
|
||||
#: api/lib/cmdb/resp_format.py:128
|
||||
msgid "The attributes method must return a list"
|
||||
msgstr "attributes方法必须返回的是list"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:127
|
||||
#: 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:129
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
msgid "Only administrators can define execution targets as: all nodes!"
|
||||
msgstr "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:130
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
msgid "Execute targets permission check failed: {}"
|
||||
msgstr "执行机器权限检查不通过: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:132
|
||||
#: api/lib/cmdb/resp_format.py:135
|
||||
msgid "CI filter authorization must be named!"
|
||||
msgstr "CI过滤授权 必须命名!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:133
|
||||
#: 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:136
|
||||
#: api/lib/cmdb/resp_format.py:139
|
||||
msgid "You do not have permission to operate attribute {}!"
|
||||
msgstr "您没有属性 {} 的操作权限!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:137
|
||||
#: 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:139
|
||||
#: api/lib/cmdb/resp_format.py:142
|
||||
msgid "Failed to save password: {}"
|
||||
msgstr "保存密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:140
|
||||
#: api/lib/cmdb/resp_format.py:143
|
||||
msgid "Failed to get password: {}"
|
||||
msgstr "获取密码失败: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:145
|
||||
msgid "Scheduling time format error"
|
||||
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:146
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:147
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:149
|
||||
msgid "Topology view {} already exists"
|
||||
msgstr "拓扑视图 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:150
|
||||
msgid "Topology group {} already exists"
|
||||
msgstr "拓扑视图分组 {} 已经存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:152
|
||||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:8
|
||||
msgid "Company info already existed"
|
||||
msgstr "公司信息已存在,无法创建!"
|
||||
@@ -700,6 +736,10 @@ msgstr "LDAP测试用户名必填"
|
||||
msgid "Company wide"
|
||||
msgstr "全公司"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:84
|
||||
msgid "No permission to access resource {}, perm {} "
|
||||
msgstr "您没有资源: {} 的 {} 权限"
|
||||
|
||||
#: api/lib/perm/acl/resp_format.py:9
|
||||
msgid "login successful"
|
||||
msgstr "登录成功"
|
||||
|
@@ -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))
|
||||
|
@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
@@ -254,3 +255,23 @@ class CIPasswordView(APIView):
|
||||
|
||||
def post(self, ci_id, attr_id):
|
||||
return self.get(ci_id, attr_id)
|
||||
|
||||
|
||||
class CIBaselineView(APIView):
|
||||
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
|
||||
|
||||
@args_required("before_date")
|
||||
def get(self):
|
||||
ci_ids = handle_arg_list(request.values.get('ci_ids'))
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
|
||||
|
||||
@args_required("before_date")
|
||||
def post(self, ci_id):
|
||||
if 'rollback' in request.url:
|
||||
before_date = request.values.get('before_date')
|
||||
|
||||
return self.jsonify(**CIManager().rollback(ci_id, before_date))
|
||||
|
||||
return self.get(ci_id)
|
||||
|
@@ -7,7 +7,6 @@ from io import BytesIO
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeTemplateManager
|
||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
|
||||
@@ -116,7 +119,6 @@ class CITypeInheritanceView(APIView):
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups",
|
||||
"/ci_types/groups/config",
|
||||
"/ci_types/groups/order",
|
||||
"/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
@@ -125,7 +127,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(CITypeGroupManager.get(need_other, config_required))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.create_CIType_group, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def post(self):
|
||||
@@ -136,15 +139,6 @@ class CITypeGroupView(APIView):
|
||||
|
||||
@args_validate(CITypeGroupManager.cls)
|
||||
def put(self, gid=None):
|
||||
if "/order" in request.url:
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
|
||||
type_ids = request.values.get('type_ids')
|
||||
|
||||
@@ -152,7 +146,8 @@ class CITypeGroupView(APIView):
|
||||
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.delete_CIType_group, app_cli.admin_name)
|
||||
def delete(self, gid):
|
||||
type_ids = request.values.get("type_ids")
|
||||
CITypeGroupManager.delete(gid, type_ids)
|
||||
@@ -160,6 +155,18 @@ class CITypeGroupView(APIView):
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeGroupOrderView(APIView):
|
||||
url_prefix = "/ci_types/groups/order"
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.update_CIType_group, app_cli.admin_name)
|
||||
def put(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
CITypeGroupManager.order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@@ -352,14 +359,16 @@ class CITypeAttributeGroupView(APIView):
|
||||
class CITypeTemplateView(APIView):
|
||||
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self, type_id=None): # export
|
||||
if type_id is not None:
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
|
||||
|
||||
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
tpt = request.values.get('ci_type_template') or {}
|
||||
|
||||
@@ -379,7 +388,8 @@ class CITypeCanDefineComputed(APIView):
|
||||
class CITypeTemplateFileView(APIView):
|
||||
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def get(self): # export
|
||||
tpt_json = CITypeTemplateManager.export_template()
|
||||
tpt_json = dict(ci_type_template=tpt_json)
|
||||
@@ -394,7 +404,8 @@ class CITypeTemplateFileView(APIView):
|
||||
mimetype='application/json',
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
|
||||
app_cli.op.download_CIType, app_cli.admin_name)
|
||||
def post(self): # import
|
||||
f = request.files.get('file')
|
||||
|
||||
|
@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
@@ -41,7 +45,8 @@ class GetParentsView(APIView):
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
res, type2attributes = CITypeRelationManager.get()
|
||||
|
||||
@@ -52,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)
|
||||
|
||||
@@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
|
||||
|
@@ -3,14 +3,16 @@
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.lib.cmdb.custom_dashboard import SystemConfigManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(res)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
|
||||
|
||||
return self.jsonify(id2options=request.values.get('id2options'))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
CustomDashboardManager.delete(_id)
|
||||
|
||||
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
|
||||
class SystemConfigApiView(APIView):
|
||||
url_prefix = ("/system_config",)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name", value_required=True)
|
||||
def get(self):
|
||||
return self.jsonify(SystemConfigManager.get(request.values['name']))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_validate(SystemConfigManager.cls)
|
||||
@args_required("name", value_required=True)
|
||||
@args_required("option", value_required=True)
|
||||
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
|
||||
def put(self, _id=None):
|
||||
return self.post()
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
CustomDashboardManager.delete(request.values['name'])
|
||||
|
@@ -5,28 +5,29 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
from flask import session
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = ("/history/records/attribute", "/history/records/relation")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
|
||||
|
||||
|
||||
class CITriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||
url_prefix = ("/history/ci_triggers/<int:ci_id>",)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||
def get(self, ci_id=None):
|
||||
if ci_id is not None:
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
def get(self, ci_id):
|
||||
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(result)
|
||||
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
class CIsTriggerHistoryView(APIView):
|
||||
url_prefix = ("/history/ci_triggers",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
trigger_id = request.values.get("trigger_id")
|
||||
operate_type = request.values.get("operate_type")
|
||||
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
|
||||
class CITypeHistoryView(APIView):
|
||||
url_prefix = "/history/ci_types"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
type_id = request.values.get("type_id")
|
||||
username = request.values.get("username")
|
||||
|
@@ -8,20 +8,22 @@ from flask import request
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = ("/preference/ci_types", "/preference/ci_types2")
|
||||
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_required("cr_ids")
|
||||
@args_validate(PreferenceManager.pref_rel_cls)
|
||||
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def put(self, _id):
|
||||
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
|
||||
|
||||
return self.jsonify(views=views, id2type=id2type, name2id=name2id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
|
@@ -4,14 +4,16 @@
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def post(self):
|
||||
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("name")
|
||||
@args_validate(RelationTypeManager.cls)
|
||||
def put(self, rel_id):
|
||||
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
|
||||
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
|
||||
|
178
cmdb-api/api/views/cmdb/topology.py
Normal file
178
cmdb-api/api/views/cmdb/topology.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.topology import TopologyViewManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class TopologyGroupView(APIView):
|
||||
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
|
||||
|
||||
@args_required('name')
|
||||
@args_validate(TopologyViewManager.group_cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.get('name')
|
||||
order = request.values.get('order')
|
||||
|
||||
group = TopologyViewManager.add_group(name, order)
|
||||
|
||||
return self.jsonify(group.to_dict())
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def put(self, group_id):
|
||||
name = request.values.get('name')
|
||||
view_ids = request.values.get('view_ids')
|
||||
group = TopologyViewManager().update_group(group_id, name, view_ids)
|
||||
|
||||
return self.jsonify(**group)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.delete_topology_group, app_cli.admin_name)
|
||||
def delete(self, group_id):
|
||||
TopologyViewManager.delete_group(group_id)
|
||||
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
|
||||
class TopologyGroupOrderView(APIView):
|
||||
url_prefix = ('/topology_views/groups/order',)
|
||||
|
||||
@args_required('group_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.update_topology_group, app_cli.admin_name)
|
||||
def post(self):
|
||||
group_ids = request.values.get('group_ids')
|
||||
|
||||
TopologyViewManager.group_order(group_ids)
|
||||
|
||||
return self.jsonify(group_ids=group_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyView(APIView):
|
||||
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
|
||||
|
||||
def get(self, type_id=None, _id=None):
|
||||
if type_id is not None:
|
||||
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
|
||||
|
||||
if _id is not None:
|
||||
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
|
||||
|
||||
return self.jsonify(TopologyViewManager.get_all())
|
||||
|
||||
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
name = request.values.pop('name')
|
||||
group_id = request.values.pop('group_id', None)
|
||||
option = request.values.pop('option', None)
|
||||
order = request.values.pop('order', None)
|
||||
|
||||
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@args_validate(TopologyViewManager.cls)
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
|
||||
def put(self, _id):
|
||||
topo_view = TopologyViewManager.update(_id, **request.values)
|
||||
|
||||
return self.jsonify(topo_view)
|
||||
|
||||
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
|
||||
def delete(self, _id):
|
||||
TopologyViewManager.delete(_id)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyOrderView(APIView):
|
||||
url_prefix = ('/topology_views/order',)
|
||||
|
||||
@args_required('view_ids')
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
|
||||
app_cli.op.create_topology_view, app_cli.admin_name)
|
||||
def post(self):
|
||||
view_ids = request.values.get('view_ids')
|
||||
|
||||
TopologyViewManager.group_inner_order(view_ids)
|
||||
|
||||
return self.jsonify(view_ids=view_ids)
|
||||
|
||||
def put(self):
|
||||
return self.post()
|
||||
|
||||
|
||||
class TopologyViewPreview(APIView):
|
||||
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
|
||||
|
||||
def get(self, _id=None):
|
||||
if _id is not None:
|
||||
acl = ACLManager('cmdb')
|
||||
resource_name = TopologyViewManager.get_name_by_id(_id)
|
||||
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
|
||||
not is_app_admin('cmdb')):
|
||||
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
|
||||
|
||||
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
|
||||
else:
|
||||
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
|
||||
|
||||
def post(self, _id=None):
|
||||
return self.get(_id)
|
||||
|
||||
|
||||
class TopologyViewGrantView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
|
||||
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class TopologyViewRevokeView(APIView):
|
||||
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
|
||||
|
||||
@args_required('perms')
|
||||
def post(self, view_id, rid):
|
||||
perms = request.values.pop('perms', None)
|
||||
|
||||
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
|
||||
acl = ACLManager('cmdb')
|
||||
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
|
||||
PermEnum.GRANT) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
|
||||
|
||||
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
|
||||
|
||||
return self.jsonify(code=200)
|
@@ -1,7 +1,7 @@
|
||||
-i https://mirrors.aliyun.com/pypi/simple
|
||||
alembic==1.7.7
|
||||
bs4==0.0.1
|
||||
celery>=5.3.1
|
||||
celery==5.3.1
|
||||
celery-once==3.0.1
|
||||
click==8.1.3
|
||||
elasticsearch==7.17.9
|
||||
@@ -31,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
|
||||
@@ -53,4 +54,4 @@ shamir~=17.12.0
|
||||
pycryptodomex>=3.19.0
|
||||
colorama>=0.4.6
|
||||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
||||
python-magic==0.4.27
|
||||
|
@@ -20,10 +20,16 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
|
||||
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
|
||||
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
|
||||
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
|
||||
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
|
||||
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
|
||||
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
'user': SQLALCHEMY_DATABASE_URI
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
@@ -39,7 +39,7 @@
|
||||
"md5": "^2.2.1",
|
||||
"moment": "^2.24.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"relation-graph": "^1.1.0",
|
||||
"relation-graph": "^2.1.42",
|
||||
"snabbdom": "^3.5.1",
|
||||
"sortablejs": "1.9.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
@@ -59,7 +59,7 @@
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vuedraggable": "^2.23.0",
|
||||
"vuex": "^3.1.1",
|
||||
"vxe-table": "3.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=1713335725699') format('woff2'),
|
||||
url('iconfont.woff?t=1713335725699') format('woff'),
|
||||
url('iconfont.ttf?t=1713335725699') 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,402 @@
|
||||
-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";
|
||||
}
|
||||
|
||||
.monitor-host_analysis:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
|
||||
.a-Group427319324:before {
|
||||
content: "\e929";
|
||||
}
|
||||
|
||||
.monitor-native:before {
|
||||
content: "\e928";
|
||||
}
|
||||
|
||||
.veops-filter2:before {
|
||||
content: "\e927";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-cmdb-data_companies:before {
|
||||
content: "\e926";
|
||||
}
|
||||
|
||||
.monitor-threshold_value:before {
|
||||
content: "\e921";
|
||||
}
|
||||
|
||||
.monitor-disposition:before {
|
||||
content: "\e922";
|
||||
}
|
||||
|
||||
.monitor-automatic_discovery:before {
|
||||
content: "\e923";
|
||||
}
|
||||
|
||||
.monitor-grouping_list:before {
|
||||
content: "\e924";
|
||||
}
|
||||
|
||||
.monitor-node_list:before {
|
||||
content: "\e925";
|
||||
}
|
||||
|
||||
.monitor-general_view:before {
|
||||
content: "\e920";
|
||||
}
|
||||
|
||||
.monitor-network_topology:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
|
||||
.monitor-node_management:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
||||
.monitor-alarm_policy:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
|
||||
.monitor-alarm:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
|
||||
.monitor-healing:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
.monitor-data_acquisition:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.monitor-analysis:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
|
||||
.monitor-index:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.monitor-user_defined:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.monitor-database:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.monitor-common:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.veops-edit:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.veops-empower:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.veops-share:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.veops-export:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.a-veops-import1:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.monitor-ip:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.monitor-director:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.monitor-host:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.a-cmdb-log1:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.monitor-add:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.monitor-down:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.monitor-up:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.itsm-unfold:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.itsm-stretch:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison2:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.monitor-data_comaparison1:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.a-monitor-online1:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-application-selected:before {
|
||||
content: "\e919";
|
||||
}
|
||||
|
||||
.ops-setting-application:before {
|
||||
content: "\e918";
|
||||
}
|
||||
|
||||
.ops-setting-basic:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-basic-selected:before {
|
||||
content: "\e917";
|
||||
}
|
||||
|
||||
.ops-setting-security:before {
|
||||
content: "\e915";
|
||||
}
|
||||
|
||||
.ops-setting-theme:before {
|
||||
content: "\e916";
|
||||
}
|
||||
|
||||
.veops-show:before {
|
||||
content: "\e914";
|
||||
}
|
||||
@@ -21,7 +417,7 @@
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.a-itsm-workload1:before {
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
@@ -269,10 +665,6 @@
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.ops-setting-auth-selected:before {
|
||||
content: "\e8d4";
|
||||
}
|
||||
|
||||
.itsm-knowledge2:before {
|
||||
content: "\e8d2";
|
||||
}
|
||||
@@ -501,10 +893,6 @@
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.ops-setting-duty-selected:before {
|
||||
content: "\e89b";
|
||||
}
|
||||
|
||||
.datainsight-sequential:before {
|
||||
content: "\e899";
|
||||
}
|
||||
@@ -669,38 +1057,6 @@
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketsetting-selected:before {
|
||||
content: "\e860";
|
||||
}
|
||||
|
||||
.ops-itsm-reports-selected:before {
|
||||
content: "\e861";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog-selected:before {
|
||||
content: "\e862";
|
||||
}
|
||||
|
||||
.ops-itsm-ticketmanage-selected:before {
|
||||
content: "\e863";
|
||||
}
|
||||
|
||||
.ops-itsm-knowledge-selected:before {
|
||||
content: "\e864";
|
||||
}
|
||||
|
||||
.ops-itsm-workstation-selected:before {
|
||||
content: "\e865";
|
||||
}
|
||||
|
||||
.ops-itsm-servicedesk-selected:before {
|
||||
content: "\e866";
|
||||
}
|
||||
|
||||
.ops-itsm-planticket-selected:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.ops-itsm-servicecatalog:before {
|
||||
content: "\e868";
|
||||
}
|
||||
@@ -1073,26 +1429,10 @@
|
||||
content: "\e816";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch-selected:before {
|
||||
content: "\e803";
|
||||
}
|
||||
|
||||
.ops-cmdb-batch:before {
|
||||
content: "\e80a";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc-selected:before {
|
||||
content: "\e7f7";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource-selected:before {
|
||||
content: "\e7f8";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference-selected:before {
|
||||
content: "\e7f9";
|
||||
}
|
||||
|
||||
.ops-cmdb-preference:before {
|
||||
content: "\e7fa";
|
||||
}
|
||||
@@ -1101,22 +1441,10 @@
|
||||
content: "\e7fb";
|
||||
}
|
||||
|
||||
.ops-cmdb-tree-selected:before {
|
||||
content: "\e7fc";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation-selected:before {
|
||||
content: "\e7fd";
|
||||
}
|
||||
|
||||
.ops-cmdb-adc:before {
|
||||
content: "\e7fe";
|
||||
}
|
||||
|
||||
.ops-cmdb-search-selected:before {
|
||||
content: "\e7ff";
|
||||
}
|
||||
|
||||
.ops-cmdb-relation:before {
|
||||
content: "\e800";
|
||||
}
|
||||
@@ -1125,14 +1453,6 @@
|
||||
content: "\e801";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype-selected:before {
|
||||
content: "\e802";
|
||||
}
|
||||
|
||||
.ops-cmdb-dashboard-selected:before {
|
||||
content: "\e804";
|
||||
}
|
||||
|
||||
.ops-cmdb-citype:before {
|
||||
content: "\e805";
|
||||
}
|
||||
@@ -1141,10 +1461,6 @@
|
||||
content: "\e806";
|
||||
}
|
||||
|
||||
.ops-cmdb-screen-selected:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.ops-cmdb-resource:before {
|
||||
content: "\e808";
|
||||
}
|
||||
@@ -1493,14 +1809,6 @@
|
||||
content: "\e7a6";
|
||||
}
|
||||
|
||||
.ops-setting-role-selected:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.ops-setting-group-selected:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.ops-setting-role:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
@@ -1941,18 +2249,10 @@
|
||||
content: "\e738";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected-copy:before {
|
||||
content: "\e889";
|
||||
}
|
||||
|
||||
.ops-setting-notice:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.ops-setting-notice-selected:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.ops-setting-notice-email-selected:before {
|
||||
content: "\e731";
|
||||
}
|
||||
@@ -1977,10 +2277,6 @@
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure-selected:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.ops-setting-companyStructure:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
@@ -1989,10 +2285,6 @@
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.ops-setting-companyInfo-selected:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.ops-email:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
@@ -3033,14 +3325,6 @@
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.ops-dag-dashboard-selected:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.ops-dag-applet-selected:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.ops-dag-applet:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -3049,62 +3333,26 @@
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.ops-dag-terminal-selected:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ops-dag-cron:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.ops-dag-cron-selected:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ops-dag-history:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.ops-dag-history-selected:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.ops-dag-dags-selected:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.ops-dag-dagreview-selected:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.ops-dag-panel:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.ops-dag-panel-selected:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.ops-dag-variables:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.ops-dag-variables-selected:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.ops-dag-appletadmin-selected:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.ops-dag-dags:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@
|
||||
:bodyStyle="{ padding: '24px 12px' }"
|
||||
:placement="placement"
|
||||
>
|
||||
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
|
||||
<ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,14 @@ export default {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'resourceSearch'
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -73,7 +73,7 @@ export default {
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
background: @primary-color;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
|
@@ -298,14 +298,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -182,8 +182,8 @@ export default {
|
||||
<tag {...{ props, attrs }}>
|
||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||
<span>
|
||||
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot &&
|
||||
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
|
||||
{isShowDot && !menu.meta.disabled &&
|
||||
<a-popover
|
||||
overlayClassName="custom-menu-extra-submenu"
|
||||
placement="rightTop"
|
||||
|
@@ -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: '' }
|
||||
]
|
||||
}
|
||||
|
@@ -161,7 +161,7 @@ export default {
|
||||
cursor: pointer;
|
||||
&-selected,
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export default {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000000;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
@@ -185,14 +185,14 @@ export default {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ export default {
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
background-color: @primary-color;
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
|
@@ -81,7 +81,7 @@ export default {
|
||||
if (route.name === 'cmdb') {
|
||||
const preference = await getPreference()
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
|
||||
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
|
||||
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
|
||||
} else {
|
||||
this.$router.push('/cmdb/dashboard')
|
||||
|
@@ -97,8 +97,8 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
.color {
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
color: @primary-color;
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.custom-user {
|
||||
.custom-user-item {
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
.locale {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -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: '货币金额',
|
||||
|
@@ -2,11 +2,12 @@ import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function searchCI(params) {
|
||||
export function searchCI(params, isShowMessage = true) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/s`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
params: params,
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
})
|
||||
}
|
||||
|
@@ -54,3 +54,36 @@ export function getCiTriggersByCiId(ci_id, params) {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCiRelatedTickets(params) {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/get_tickets_by`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function judgeItsmInstalled() {
|
||||
return axios({
|
||||
url: `/itsm/v1/process_ticket/itsm_existed`,
|
||||
method: 'GET',
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIsBaseline(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/baseline`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function CIBaselineRollback(ciId, params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci/${ciId}/baseline/rollback`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
110
cmdb-ui/src/modules/cmdb/api/topology.js
Normal file
110
cmdb-ui/src/modules/cmdb/api/topology.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function getTopoGroups() {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function postTopoGroup(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupByGId(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putTopoGroupsOrder(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/order`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoGroup(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/groups/${gid}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addTopoView(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTopoView(_id, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationsByTypeId(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/relations/ci_types/${_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function previewTopoView(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/preview`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export function showTopoView(_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/topology_views/${_id}/view`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function grantTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/grant`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function revokeTopologyView(viewId, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/topology_views/${viewId}/roles/${rid}/revoke`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/itsm_uninstalled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
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>
|
@@ -230,7 +230,7 @@ export default {
|
||||
background-color: #f9fbff;
|
||||
border-bottom: none;
|
||||
.ant-transfer-list-header-title {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -258,7 +258,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
border-radius: 4px;
|
||||
width: 12px;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #cacdd9;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
@@ -291,8 +291,8 @@ export default {
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 130}px` }">
|
||||
<div class="cmdb-grant" :style="{ }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
@@ -57,6 +57,20 @@
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('TopologyView')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
|
||||
<TopologyViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
:viewId="CITypeId"
|
||||
grantType="TopologyView"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grantTopologyView"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
@@ -72,11 +86,12 @@ import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import TopologyViewGrant from './topologyViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, TopologyViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
@@ -291,6 +306,20 @@ export default {
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'TopologyView') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.addedRids = rids
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
|
@@ -75,10 +75,10 @@ export default {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
// background-color: @primary-color;
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
border-left-color: @primary-color;
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
|
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="topology-view-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:scroll-y="{enabled: true}"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTopologyView, revokeTopologyView } from '@/modules/cmdb/api/topology.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
export default {
|
||||
name: 'TopologyViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
viewId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['read', 'update', 'delete', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTopologyView(this.viewId, row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.topology-view-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
@@ -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>
|
@@ -49,6 +49,8 @@
|
||||
import _ from 'lodash'
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||
|
||||
export default {
|
||||
name: 'NoticeContent',
|
||||
components: { Editor, Toolbar },
|
||||
@@ -76,6 +78,10 @@ export default {
|
||||
if (editor == null) return
|
||||
editor.destroy() // When the component is destroyed, destroy the editor in time
|
||||
},
|
||||
beforeCreate() {
|
||||
const locale = this.$i18n.locale === 'zh' ? 'zh-CN' : 'en'
|
||||
i18nChangeLanguage(locale)
|
||||
},
|
||||
methods: {
|
||||
onCreated(editor) {
|
||||
this.editor = Object.seal(editor) // Be sure to use Object.seal(), otherwise an error will be reported
|
||||
@@ -175,8 +181,8 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +194,7 @@ export default {
|
||||
|
||||
.notice-content {
|
||||
.w-e-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
}
|
||||
.w-e-text-placeholder {
|
||||
line-height: 1.5;
|
||||
|
@@ -1,314 +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()
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
@@ -253,7 +253,7 @@ export default {
|
||||
}
|
||||
.cmdb-subscribe-drawer-tree-header {
|
||||
border-radius: 4px;
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
@@ -264,7 +264,7 @@ export default {
|
||||
> span {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
border-left: 2px solid #custom_colors[color_1];
|
||||
border-left: 2px solid @primary-color;
|
||||
padding: 3px 12px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
@@ -272,7 +272,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ export default {
|
||||
<style lang="less">
|
||||
.cmdb-subscribe-drawer {
|
||||
.ant-tabs-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
background-color: @primary-color_5;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span>
|
||||
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
|
||||
<a-button @click="add" type="primary" size="small" icon="plus">
|
||||
{{ $t('add') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,19 +13,25 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/dashboard',
|
||||
name: 'cmdb_dashboard',
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false },
|
||||
meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard', keepAlive: false },
|
||||
component: () => import('../views/dashboard/index_v2.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/topoviews',
|
||||
name: 'cmdb_topology_views',
|
||||
meta: { title: 'cmdb.menu.topologyView', appName: 'cmdb', icon: 'ops-topology_view', selectedIcon: 'ops-topology_view', keepAlive: false },
|
||||
component: () => import('../views/topology_view/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled1',
|
||||
name: 'cmdb_disabled1',
|
||||
meta: { title: 'cmdb.menu.views', disabled: true },
|
||||
meta: { title: 'cmdb.menu.resources', disabled: true },
|
||||
},
|
||||
{
|
||||
path: '/cmdb/resourceviews',
|
||||
name: 'cmdb_resource_views',
|
||||
component: RouteView,
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true },
|
||||
meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource', keepAlive: true },
|
||||
hideChildrenInMenu: false,
|
||||
children: []
|
||||
},
|
||||
@@ -33,7 +39,7 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/tree_views',
|
||||
component: () => import('../views/tree_views'),
|
||||
name: 'cmdb_tree_views',
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree', keepAlive: false },
|
||||
hideChildrenInMenu: true,
|
||||
children: [
|
||||
{
|
||||
@@ -47,13 +53,13 @@ const genCmdbRoutes = async () => {
|
||||
{
|
||||
path: '/cmdb/resourcesearch',
|
||||
name: 'cmdb_resource_search',
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false },
|
||||
component: () => import('../views/resource_search/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
name: 'cmdb_auto_discovery_ci',
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
|
||||
meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc', keepAlive: false },
|
||||
component: () => import('../views/discoveryCI/index.vue')
|
||||
},
|
||||
{
|
||||
@@ -72,19 +78,19 @@ const genCmdbRoutes = async () => {
|
||||
path: '/cmdb/preference',
|
||||
component: () => import('../views/preference/index'),
|
||||
name: 'cmdb_preference',
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false }
|
||||
meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/batch',
|
||||
component: () => import('../views/batch'),
|
||||
name: 'cmdb_batch',
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false }
|
||||
meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/ci_types',
|
||||
name: 'ci_type',
|
||||
component: () => import('../views/ci_types/index'),
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/disabled3',
|
||||
@@ -143,21 +149,30 @@ const genCmdbRoutes = async () => {
|
||||
}
|
||||
// Dynamically add subscription items and business relationships
|
||||
const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
|
||||
|
||||
preference.forEach(item => {
|
||||
routes.children[2].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
|
||||
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
|
||||
const resourceViewsIndex = routes.children.findIndex(item => item.name === 'cmdb_resource_views')
|
||||
preference.group_types.forEach(group => {
|
||||
if (preference.group_types.length > 1) {
|
||||
routes.children[resourceViewsIndex].children.push({
|
||||
path: `/cmdb/instances/types/group${group.id}`,
|
||||
name: `cmdb_instances_group_${group.id}`,
|
||||
meta: { title: group.name || 'other', disabled: true, style: 'margin-left: 12px' },
|
||||
})
|
||||
}
|
||||
group.ci_types.forEach(item => {
|
||||
routes.children[resourceViewsIndex].children.push({
|
||||
path: `/cmdb/instances/types/${item.id}`,
|
||||
component: () => import(`../views/ci/index`),
|
||||
name: `cmdb_${item.id}`,
|
||||
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
|
||||
// hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
|
||||
})
|
||||
})
|
||||
})
|
||||
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
|
||||
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
|
||||
if (lastTypeId && preference.type_ids.some(item => item === Number(lastTypeId))) {
|
||||
routes.redirect = `/cmdb/instances/types/${lastTypeId}`
|
||||
} else if (routes.children[2]?.children?.length > 0) {
|
||||
routes.redirect = routes.children[2].children.find(item => !item.hidden)?.path
|
||||
} else if (routes.children[resourceViewsIndex]?.children?.length > 0) {
|
||||
routes.redirect = routes.children[resourceViewsIndex].children.find(item => !item.hidden && !item.meta.disabled)?.path
|
||||
} else {
|
||||
routes.redirect = '/cmdb/dashboard'
|
||||
}
|
||||
@@ -166,10 +181,10 @@ const genCmdbRoutes = async () => {
|
||||
path: `/cmdb/relationviews/${item[1]}`,
|
||||
name: `cmdb_relation_views_${item[1]}`,
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation-selected', keepAlive: false, name: item[0] },
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
|
||||
}
|
||||
})
|
||||
routes.children.splice(2, 0, ...relationViews)
|
||||
routes.children.splice(resourceViewsIndex, 0, ...relationViews)
|
||||
return routes
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div class="ci-detail-header">{{ this.type.alias }}</div>
|
||||
<div class="ci-detail-page">
|
||||
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
|
||||
<ci-detail-tab ref="ciDetailTab" :typeId="typeId" :attributeHistoryTableHeight="windowHeight - 250" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,6 +23,11 @@ export default {
|
||||
attributes: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
@@ -55,7 +60,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.ci-detail-header {
|
||||
border-left: 3px solid @primary-color;
|
||||
padding-left: 10px;
|
||||
|
@@ -68,6 +68,8 @@
|
||||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span @click="batchRollback">{{ $t('cmdb.ci.rollback') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||||
</div>
|
||||
</SearchForm>
|
||||
@@ -165,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"
|
||||
@@ -293,6 +299,7 @@
|
||||
<create-instance-form ref="create" @reload="reloadData" @submit="batchUpdate" />
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||||
<ci-rollback-form ref="ciRollbackForm" @batchRollbackAsync="batchRollbackAsync($event)" :ciIds="selectedRowKeys" />
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
<CMDBGrant ref="cmdbGrant" resourceTypeName="CIType" app_id="cmdb" />
|
||||
</a-spin>
|
||||
@@ -325,6 +332,8 @@ import MetadataDrawer from './modules/MetadataDrawer.vue'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||||
import CiRollbackForm from './modules/ciRollbackForm.vue'
|
||||
import { CIBaselineRollback } from '../../api/history'
|
||||
|
||||
export default {
|
||||
name: 'InstanceList',
|
||||
@@ -340,6 +349,7 @@ export default {
|
||||
MetadataDrawer,
|
||||
CMDBGrant,
|
||||
OpsMoveIcon,
|
||||
CiRollbackForm,
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
@@ -429,6 +439,12 @@ export default {
|
||||
// window.onkeypress = (e) => {
|
||||
// this.handleKeyPress(e)
|
||||
// }
|
||||
this.$nextTick(() => {
|
||||
const loadingNode = document.getElementsByClassName('ant-drawer-mask')
|
||||
if (loadingNode?.style) {
|
||||
loadingNode.style.zIndex = 8
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.columnDrop()
|
||||
}, 1000)
|
||||
@@ -661,7 +677,7 @@ export default {
|
||||
message: this.$t('warning'),
|
||||
description: errorMsg,
|
||||
duration: 0,
|
||||
style: { whiteSpace: 'break-spaces' },
|
||||
style: { whiteSpace: 'break-spaces', overflow: 'auto', maxHeight: this.windowHeight - 80 + 'px' },
|
||||
})
|
||||
errorNum += 1
|
||||
})
|
||||
@@ -744,6 +760,55 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
batchRollback() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciRollbackForm.onOpen(true)
|
||||
})
|
||||
},
|
||||
async batchRollbackAsync(params) {
|
||||
const mask = document.querySelector('.ant-drawer-mask')
|
||||
const oldValue = mask.style.zIndex
|
||||
mask.style.zIndex = 2
|
||||
let successNum = 0
|
||||
let errorNum = 0
|
||||
this.loading = true
|
||||
this.loadTip = this.$t('cmdb.ci.rollbackingTips')
|
||||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||||
for (let i = 0; i < floor; i++) {
|
||||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||||
const promises = itemList.map((x) => CIBaselineRollback(x, params))
|
||||
await Promise.allSettled(promises)
|
||||
.then((res) => {
|
||||
res.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
successNum += 1
|
||||
} else {
|
||||
errorNum += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadTip = this.$t('cmdb.ci.batchRollbacking', {
|
||||
total: this.selectedRowKeys.length,
|
||||
successNum: successNum,
|
||||
errorNum: errorNum,
|
||||
})
|
||||
})
|
||||
}
|
||||
this.loading = false
|
||||
this.loadTip = ''
|
||||
mask.style.zIndex = oldValue
|
||||
this.selectedRowKeys = []
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||
this.$nextTick(() => {
|
||||
if (this.currentPage === 1) {
|
||||
this.loadTableData()
|
||||
} else {
|
||||
this.currentPage = 1
|
||||
}
|
||||
})
|
||||
},
|
||||
async refreshAfterEditAttrs() {
|
||||
await this.loadPreferenceAttrList()
|
||||
await this.loadTableData()
|
||||
|
@@ -107,6 +107,7 @@
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
:valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
|
||||
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
@@ -370,7 +371,7 @@ export default {
|
||||
return 'select%%multiple'
|
||||
}
|
||||
return 'select'
|
||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||
} else if ((_find.value_type === '0' || _find.value_type === '1') && !_find.is_list) {
|
||||
return 'input_number'
|
||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||
return _find.value_type
|
||||
|
@@ -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'),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
|
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
width="90%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
style="transform: translateX(0px)!important"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
|
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div :style="{ height: '100%' }" v-if="itsmInstalled">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
resizable
|
||||
border
|
||||
size="small"
|
||||
class="ops-unstripe-table"
|
||||
:span-method="mergeRowMethod"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { height: 'auto' } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<template #empty>
|
||||
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</template>
|
||||
<vxe-column field="ticket.ticket_id" min-width="80" :title="$t('cmdb.history.ticketId')"> </vxe-column>
|
||||
<vxe-column field="ticket.created_at" width="160" :title="$t('cmdb.history.ticketStartTime')"> </vxe-column>
|
||||
<vxe-column field="ticket.creator_name" min-width="80" :title="$t('cmdb.history.ticketCreator')"> </vxe-column>
|
||||
<vxe-column field="ticket.title" min-width="150" :title="$t('cmdb.history.ticketTitle')">
|
||||
<template slot-scope="{ row }">
|
||||
<a target="_blank" :href="row.ticket.url">{{ row.ticket.title }}</a>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_finish_time" width="160" :title="$t('cmdb.history.ticketFinishTime')">
|
||||
</vxe-column>
|
||||
<vxe-column field="ticket.node_name" min-width="100" :title="$t('cmdb.history.ticketNodeName')"> </vxe-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
min-width="100"
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 2, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
min-width="100"
|
||||
:title="$t('cmdb.attribute')"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="old" min-width="100" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" min-width="100" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right' }" v-if="!ci_id">
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:page-size-options="pageSizeOptions"
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.totalResult"
|
||||
:show-total="(total, range) => $t('cmdb.history.totalItems', { total: total })"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '200px',
|
||||
}"
|
||||
:style="{ paddingTop: '10%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/modules/cmdb/assets/itsm_uninstalled.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.history.itsmUninstalled') }} </span>
|
||||
<a-button href="https://veops.cn/apply" target="_blank" type="primary">
|
||||
{{ $t('cmdb.history.applyItsm') }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCiRelatedTickets } from '../../../api/history'
|
||||
export default {
|
||||
name: 'RelatedItsmTable',
|
||||
props: {
|
||||
ci_id: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
ciHistory: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
itsmInstalled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tablePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
totalResult: 0,
|
||||
},
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
1: this.$t('delete'),
|
||||
2: this.$t('update'),
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTableData()
|
||||
},
|
||||
methods: {
|
||||
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
|
||||
const params = { page: currentPage, page_size: pageSize, next_todo_ids: [] }
|
||||
if (this.ci_id) {
|
||||
const tableData = []
|
||||
this.ciHistory.forEach((item) => {
|
||||
if (item.ticket_id) {
|
||||
params.next_todo_ids.push(item.ticket_id)
|
||||
tableData.push(item)
|
||||
}
|
||||
})
|
||||
if (params.next_todo_ids.length) {
|
||||
getCiRelatedTickets(params)
|
||||
.then((res) => {
|
||||
const ticketId2Detail = {}
|
||||
res.forEach((item) => {
|
||||
ticketId2Detail[item.next_todo_id] = item
|
||||
})
|
||||
this.tableData = tableData.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
ticket: ticketId2Detail[item.ticket_id],
|
||||
}
|
||||
})
|
||||
this.updateAttrFilter()
|
||||
})
|
||||
.catch((e) => {})
|
||||
}
|
||||
} else {
|
||||
}
|
||||
},
|
||||
updateAttrFilter() {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (attrColumn) {
|
||||
$table.setFilter(
|
||||
attrColumn,
|
||||
this.attrList().map((attr) => {
|
||||
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = [
|
||||
'ticket.ticket_id',
|
||||
'ticket.created_at',
|
||||
'ticket.creator_name',
|
||||
'ticket.title',
|
||||
'ticket.node_finish_time',
|
||||
'ticket.node_name',
|
||||
]
|
||||
const cellValue1 = row.ticket.ticket_id
|
||||
const cellValue2 = row.ticket.node_name
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow.ticket.ticket_id === cellValue1 && prevRow.ticket.node_name === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow.ticket.ticket_id === cellValue1 && nextRow.ticket.node_name === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.updateTableData(currentPage, pageSize)
|
||||
},
|
||||
filterOperateMethod({ value, row, column }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -182,7 +182,7 @@ export default {
|
||||
const edges = []
|
||||
this.parentCITypes.forEach((parent) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
|
||||
if (this.firstCIs[parent.name]) {
|
||||
if (this.firstCIs[parent.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
@@ -225,7 +225,7 @@ export default {
|
||||
})
|
||||
this.childCITypes.forEach((child) => {
|
||||
const _findCiType = ci_types_list.find((item) => item.id === child.id)
|
||||
if (this.secondCIs[child.name]) {
|
||||
if (this.secondCIs[child.name] && _findCiType) {
|
||||
const unique_id = _findCiType.show_id || _findCiType.unique_id
|
||||
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
|
||||
const unique_name = _findUnique?.name
|
||||
@@ -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}`,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user