mirror of
https://github.com/veops/cmdb.git
synced 2025-09-09 23:47:43 +08:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1f48b18e14 | ||
|
b73822bf26 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,7 +39,6 @@ pip-log.txt
|
||||
nosetests.xml
|
||||
.pytest_cache
|
||||
cmdb-api/test-output
|
||||
cmdb-api/api/uploaded_files
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@@ -3,7 +3,7 @@ FROM node:16.0.0-alpine AS builder
|
||||
|
||||
LABEL description="cmdb-ui"
|
||||
|
||||
COPY ../cmdb-ui /data/apps/cmdb-ui
|
||||
COPY cmdb-ui /data/apps/cmdb-ui
|
||||
|
||||
WORKDIR /data/apps/cmdb-ui
|
||||
|
||||
@@ -22,7 +22,7 @@ FROM python:3.8-alpine AS cmdb-api
|
||||
|
||||
LABEL description="Python3.8,cmdb"
|
||||
|
||||
COPY ../cmdb-api /data/apps/cmdb
|
||||
COPY cmdb-api /data/apps/cmdb
|
||||
|
||||
WORKDIR /data/apps/cmdb
|
||||
|
47
Makefile
47
Makefile
@@ -1,52 +1,37 @@
|
||||
MYSQL_ROOT_PASSWORD ?= root
|
||||
MYSQL_PORT ?= 3306
|
||||
REDIS_PORT ?= 6379
|
||||
.PHONY: env clean api ui worker
|
||||
|
||||
default: help
|
||||
help: ## display this help
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo " env create a development environment using pipenv"
|
||||
@echo " deps install dependencies using pip"
|
||||
@echo " clean remove unwanted files like .pyc's"
|
||||
@echo " lint check style with flake8"
|
||||
@echo " api start api server"
|
||||
@echo " ui start ui server"
|
||||
@echo " worker start async tasks worker"
|
||||
|
||||
env: ## create a development environment using pipenv
|
||||
env:
|
||||
sudo easy_install pip && \
|
||||
pip install pipenv -i https://pypi.douban.com/simple && \
|
||||
npm install yarn && \
|
||||
make deps
|
||||
.PHONY: env
|
||||
|
||||
docker-mysql: ## deploy MySQL use docker
|
||||
@docker run --name mysql -p ${MYSQL_PORT}:3306 -e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} -d mysql:latest
|
||||
.PHONY: docker-mysql
|
||||
|
||||
docker-redis: ## deploy Redis use docker
|
||||
@docker run --name redis -p ${REDIS_PORT}:6379 -d redis:latest
|
||||
.PHONY: docker-redis
|
||||
|
||||
deps: ## install dependencies using pip
|
||||
cd cmdb-api && \
|
||||
deps:
|
||||
pipenv install --dev && \
|
||||
pipenv run flask db-setup && \
|
||||
pipenv run flask cmdb-init-cache && \
|
||||
cd .. && \
|
||||
cd cmdb-ui && yarn install && cd ..
|
||||
.PHONY: deps
|
||||
|
||||
api: ## start api server
|
||||
api:
|
||||
cd cmdb-api && pipenv run flask run -h 0.0.0.0
|
||||
.PHONY: api
|
||||
|
||||
worker: ## start async tasks worker
|
||||
worker:
|
||||
cd cmdb-api && pipenv run celery -A celery_worker.celery worker -E -Q one_cmdb_async --concurrency=1 -D && pipenv run celery -A celery_worker.celery worker -E -Q acl_async --concurrency=1 -D
|
||||
.PHONY: worker
|
||||
|
||||
ui: ## start ui server
|
||||
ui:
|
||||
cd cmdb-ui && yarn run serve
|
||||
.PHONY: ui
|
||||
|
||||
clean: ## remove unwanted files like .pyc's
|
||||
clean:
|
||||
pipenv run flask clean
|
||||
.PHONY: clean
|
||||
|
||||
lint: ## check style with flake8
|
||||
lint:
|
||||
flake8 --exclude=env .
|
||||
.PHONY: lint
|
||||
|
@@ -4,8 +4,8 @@
|
||||
[](https://github.com/sendya/ant-design-pro-vue)
|
||||
[](https://github.com/pallets/flask)
|
||||
|
||||
[English](docs/README_en.md) / [中文](README.md)
|
||||
- 产品文档:https://veops.cn/docs/
|
||||
[English](README_en.md) / [中文](README.md)
|
||||
|
||||
- 在线体验: <a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
@@ -1,13 +1,13 @@
|
||||

|
||||

|
||||
|
||||
[](https://github.com/veops/cmdb/blob/master/LICENSE)
|
||||
[](https://github.com/sendya/ant-design-pro-vue)
|
||||
[](https://github.com/pallets/flask)
|
||||
|
||||
[English](README_en.md) / [中文](../README.md)
|
||||
[English](README_en.md) / [中文](README.md)
|
||||
|
||||
## DEMO ONLINE
|
||||
- Product document:https://veops.cn/docs/
|
||||
|
||||
- Preview online: <a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
|
||||
- username: demo
|
||||
- password: 123456
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
### Technical Architecture
|
||||
|
||||
<img src=images/view.jpg />
|
||||
<img src=docs/images/view.jpg />
|
||||
|
||||
### Document
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
### System Overview
|
||||
|
||||
- Service Tree
|
||||

|
||||

|
||||
|
||||
[View more screenshots](screenshot.md)
|
||||
[View more screenshots](docs/screenshot.md)
|
||||
|
||||
### More Features
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
- password: 123456
|
||||
|
||||
|
||||
### [Local Setup](local_en.md)
|
||||
### [Local Setup](docs/local_en.md)
|
||||
|
||||
### [Installation with Makefile](makefile_en.md)
|
||||
### [Installation with Makefile](docs/makefile_en.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -89,4 +89,4 @@
|
||||
|
||||
_**Welcome to pay attention to our public account, click to contact us, join WeChat, QQ operation and maintenance group, and get more product and industry related information**_
|
||||
|
||||

|
||||

|
@@ -6,14 +6,13 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
from inspect import getmembers
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
from flask import Flask
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask import jsonify, make_response
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.cli import click
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
|
||||
import api.views.entry
|
||||
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||
@@ -22,6 +21,7 @@ from api.models.acl import User
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
PROJECT_ROOT = os.path.join(HERE, os.pardir)
|
||||
API_PACKAGE = "api"
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
@@ -174,8 +174,9 @@ def register_commands(app):
|
||||
for root, _, files in os.walk(os.path.join(HERE, "commands")):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
if root not in sys.path:
|
||||
sys.path.insert(1, root)
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("commands"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
command = __import__(os.path.splitext(filename)[0])
|
||||
func_list = [o[0] for o in getmembers(command) if isinstance(o[1], click.core.Command)]
|
||||
for func_name in func_list:
|
||||
|
@@ -9,7 +9,6 @@ import time
|
||||
import click
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from flask_login import login_user
|
||||
|
||||
import api.lib.cmdb.ci
|
||||
from api.extensions import db
|
||||
@@ -25,7 +24,6 @@ from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resource import ResourceCRUD
|
||||
from api.lib.perm.acl.resource import ResourceTypeCRUD
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
@@ -209,8 +207,6 @@ def cmdb_counter():
|
||||
"""
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
while True:
|
||||
try:
|
||||
db.session.remove()
|
||||
|
@@ -161,55 +161,6 @@ class InitDepartment(object):
|
||||
info = f"update department acl_rid: {acl_rid}"
|
||||
current_app.logger.info(info)
|
||||
|
||||
def init_backend_resource(self):
|
||||
acl = self.check_app('backend')
|
||||
resources_types = acl.get_all_resources_types()
|
||||
|
||||
results = list(filter(lambda t: t['name'] == '操作权限', resources_types['groups']))
|
||||
if len(results) == 0:
|
||||
payload = dict(
|
||||
app_id=acl.app_name,
|
||||
name='操作权限',
|
||||
description='',
|
||||
perms=['read', 'grant', 'delete', 'update']
|
||||
)
|
||||
resource_type = acl.create_resources_type(payload)
|
||||
else:
|
||||
resource_type = results[0]
|
||||
|
||||
for name in ['公司信息']:
|
||||
payload = dict(
|
||||
type_id=resource_type['id'],
|
||||
app_id=acl.app_name,
|
||||
name=name,
|
||||
)
|
||||
try:
|
||||
acl.create_resource(payload)
|
||||
except Exception as e:
|
||||
if '已经存在' in str(e):
|
||||
pass
|
||||
else:
|
||||
raise Exception(e)
|
||||
|
||||
def check_app(self, app_name):
|
||||
acl = ACLManager(app_name)
|
||||
payload = dict(
|
||||
name=app_name,
|
||||
description=app_name
|
||||
)
|
||||
try:
|
||||
app = acl.validate_app()
|
||||
if app:
|
||||
return acl
|
||||
|
||||
acl.create_app(payload)
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
if '不存在' in str(e):
|
||||
acl.create_app(payload)
|
||||
return acl
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
@@ -226,7 +177,5 @@ def init_department():
|
||||
"""
|
||||
Department initialization
|
||||
"""
|
||||
cli = InitDepartment()
|
||||
cli.init_wide_company()
|
||||
cli.create_acl_role_with_department()
|
||||
cli.init_backend_resource()
|
||||
InitDepartment().init()
|
||||
InitDepartment().create_acl_role_with_department()
|
@@ -8,14 +8,9 @@ from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BUILTIN_KEYWORDS
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
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.const import RoleEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
@@ -45,7 +40,7 @@ class AttributeManager(object):
|
||||
ret_key = choice_web_hook.get('ret_key')
|
||||
headers = choice_web_hook.get('headers') or {}
|
||||
payload = choice_web_hook.get('payload') or {}
|
||||
method = (choice_web_hook.get('method') or 'GET').lower()
|
||||
method = choice_web_hook.get('method', 'GET').lower()
|
||||
|
||||
try:
|
||||
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
||||
@@ -60,17 +55,15 @@ class AttributeManager(object):
|
||||
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error("get choice values failed: {}".format(e))
|
||||
current_app.logger.error(str(e))
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
|
||||
if choice_web_hook:
|
||||
if choice_web_hook_parse:
|
||||
if isinstance(choice_web_hook, dict):
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
else:
|
||||
return []
|
||||
if choice_web_hook and isinstance(choice_web_hook, dict) and choice_web_hook_parse:
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
elif choice_web_hook and not choice_web_hook_parse:
|
||||
return []
|
||||
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||
@@ -80,34 +73,34 @@ class AttributeManager(object):
|
||||
@staticmethod
|
||||
def add_choice_values(_id, value_type, choice_values):
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
if choice_table is None:
|
||||
return
|
||||
|
||||
choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
||||
db.session.flush()
|
||||
choice_values = choice_values
|
||||
for v, option in choice_values:
|
||||
choice_table.create(attr_id=_id, value=v, option=option, commit=False)
|
||||
table = choice_table(attr_id=_id, value=v, option=option)
|
||||
|
||||
db.session.add(table)
|
||||
|
||||
try:
|
||||
db.session.flush()
|
||||
except Exception as e:
|
||||
current_app.logger.warning("add choice values failed: {}".format(e))
|
||||
except:
|
||||
return abort(400, ErrFormat.invalid_choice_values)
|
||||
|
||||
@staticmethod
|
||||
def _del_choice_values(_id, value_type):
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
|
||||
choice_table and choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
||||
db.session.flush()
|
||||
|
||||
@classmethod
|
||||
def search_attributes(cls, name=None, alias=None, page=1, page_size=None):
|
||||
"""
|
||||
:param name:
|
||||
:param alias:
|
||||
:param page:
|
||||
:param page_size:
|
||||
:param name:
|
||||
:param alias:
|
||||
:param page:
|
||||
:param page_size:
|
||||
:return: attribute, if name is None, then return all attributes
|
||||
"""
|
||||
if name is not None:
|
||||
@@ -121,8 +114,8 @@ class AttributeManager(object):
|
||||
attrs = attrs[(page - 1) * page_size:][:page_size]
|
||||
res = list()
|
||||
for attr in attrs:
|
||||
attr["is_choice"] and attr.update(
|
||||
dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
attr['is_choice'] and attr.pop('choice_web_hook', None)
|
||||
|
||||
res.append(attr)
|
||||
@@ -131,31 +124,30 @@ class AttributeManager(object):
|
||||
|
||||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key, choice_web_hook_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"], choice_web_hook_parse=choice_web_hook_parse)
|
||||
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])), choice_web_hook_parse=choice_web_hook_parse)
|
||||
return attr
|
||||
|
||||
@staticmethod
|
||||
@@ -163,19 +155,6 @@ class AttributeManager(object):
|
||||
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))
|
||||
|
||||
@classmethod
|
||||
def calc_computed_attribute(cls, attr_id):
|
||||
"""
|
||||
calculate computed attribute for all ci
|
||||
:param attr_id:
|
||||
:return:
|
||||
"""
|
||||
cls.can_create_computed_attribute()
|
||||
|
||||
from api.tasks.cmdb import calc_computed_attribute
|
||||
|
||||
calc_computed_attribute.apply_async(args=(attr_id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
@@ -184,9 +163,8 @@ class AttributeManager(object):
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
|
||||
name = kwargs.pop("name")
|
||||
if name in BUILTIN_KEYWORDS:
|
||||
if name in {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}:
|
||||
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
|
||||
|
||||
alias = kwargs.pop("alias", "")
|
||||
alias = name if not alias else alias
|
||||
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
||||
@@ -234,11 +212,6 @@ class AttributeManager(object):
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
def _clean_ci_type_attributes_cache(attr_id):
|
||||
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
|
||||
CITypeAttributesCache.clean(i.type_id)
|
||||
|
||||
@staticmethod
|
||||
def _change_index(attr, old, new):
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
@@ -249,11 +222,11 @@ class AttributeManager(object):
|
||||
new_table = TableMap(attr=attr, is_index=new).table
|
||||
|
||||
ci_ids = []
|
||||
for i in old_table.get_by(attr_id=attr.id, to_dict=False):
|
||||
for i in db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id):
|
||||
new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True)
|
||||
ci_ids.append(i.ci_id)
|
||||
|
||||
old_table.get_by(attr_id=attr.id, only_query=True).delete()
|
||||
db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id).delete()
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
@@ -318,7 +291,7 @@ class AttributeManager(object):
|
||||
|
||||
if is_choice and choice_value:
|
||||
self.add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
elif existed2['is_choice']:
|
||||
elif is_choice:
|
||||
self._del_choice_values(attr.id, attr.value_type)
|
||||
|
||||
try:
|
||||
@@ -337,8 +310,6 @@ class AttributeManager(object):
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
self._clean_ci_type_attributes_cache(_id)
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
@@ -352,25 +323,24 @@ class AttributeManager(object):
|
||||
ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True)
|
||||
if ref is not None:
|
||||
ci_type = CITypeCache.get(ref.type_id)
|
||||
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type and ci_type.alias or ref.type_id))
|
||||
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type.alias))
|
||||
|
||||
if attr.uid != current_user.uid and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.cannot_delete_attribute)
|
||||
|
||||
if attr.is_choice:
|
||||
choice_table = ValueTypeMap.choice.get(attr.value_type)
|
||||
choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
|
||||
attr.soft_delete()
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict
|
||||
db.session.flush()
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
attr.soft_delete()
|
||||
|
||||
for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False):
|
||||
i.soft_delete(commit=False)
|
||||
i.soft_delete()
|
||||
|
||||
for i in CITypeAttributeGroupItem.get_by(attr_id=_id, to_dict=False):
|
||||
i.soft_delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
i.soft_delete()
|
||||
|
||||
return name
|
||||
|
@@ -240,10 +240,9 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
for i in response:
|
||||
if (current_user.username not in (i.get('rd_duty') or []) and
|
||||
current_user.username not in (i.get('op_duty') or []) and
|
||||
current_user.nickname not in (i.get('rd_duty') or []) and
|
||||
current_user.nickname not in (i.get('op_duty') or [])):
|
||||
if current_user.username not in (i.get('rd_duty') or []) and current_user.username not in \
|
||||
(i.get('op_duty') or []) and current_user.nickname not in (i.get('rd_duty') or []) and \
|
||||
current_user.nickname not in (i.get('op_duty') or []):
|
||||
return abort(403, ErrFormat.adt_target_expr_no_permission.format(
|
||||
i.get("{}_name".format(i.get('ci_type')))))
|
||||
except SearchError as e:
|
||||
@@ -454,12 +453,10 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
|
||||
for r_adt in relation_adts:
|
||||
if not r_adt.relation or ci_id is None:
|
||||
continue
|
||||
for ad_key in r_adt.relation:
|
||||
if not adc.instance.get(ad_key):
|
||||
continue
|
||||
cmdb_key = r_adt.relation[ad_key]
|
||||
if r_adt.relation and ci_id is not None:
|
||||
ad_key, cmdb_key = None, {}
|
||||
for ad_key in r_adt.relation:
|
||||
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)
|
||||
|
@@ -2,11 +2,14 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import requests
|
||||
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 CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import RelationType
|
||||
@@ -31,7 +34,6 @@ class AttributeCache(object):
|
||||
attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
|
||||
if attr is not None:
|
||||
cls.set(attr)
|
||||
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
@@ -65,7 +67,6 @@ class CITypeCache(object):
|
||||
ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
@@ -97,7 +98,6 @@ class RelationTypeCache(object):
|
||||
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
@@ -133,15 +133,12 @@ class CITypeAttributesCache(object):
|
||||
attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
|
||||
if attrs is not None:
|
||||
cls.set(key, attrs)
|
||||
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
@@ -158,16 +155,13 @@ class CITypeAttributesCache(object):
|
||||
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
|
||||
if attrs is not None:
|
||||
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
|
||||
cls.set2(key, attrs)
|
||||
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
@@ -207,13 +201,13 @@ class CITypeAttributeCache(object):
|
||||
|
||||
@classmethod
|
||||
def get(cls, type_id, attr_id):
|
||||
|
||||
attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id))
|
||||
attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id))
|
||||
attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
||||
|
||||
if attr is not None:
|
||||
cls.set(type_id, attr_id, attr)
|
||||
|
||||
if not attr:
|
||||
attr = CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
||||
if attr is not None:
|
||||
cls.set(type_id, attr_id, attr)
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
@@ -247,72 +241,53 @@ class CMDBCounterCache(object):
|
||||
result = {}
|
||||
for custom in customs:
|
||||
if custom['category'] == 0:
|
||||
res = cls.sum_counter(custom)
|
||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
||||
elif custom['category'] == 1:
|
||||
res = cls.attribute_counter(custom)
|
||||
else:
|
||||
res = cls.relation_counter(custom.get('type_id'),
|
||||
custom.get('level'),
|
||||
custom.get('options', {}).get('filter', ''),
|
||||
custom.get('options', {}).get('type_ids', ''))
|
||||
|
||||
if res:
|
||||
result[custom['id']] = res
|
||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
||||
elif custom['category'] == 2:
|
||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
||||
|
||||
cls.set(result)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update(cls, custom, flush=True):
|
||||
def update(cls, custom):
|
||||
result = cache.get(cls.KEY) or {}
|
||||
if not result:
|
||||
result = cls.reset()
|
||||
|
||||
if custom['category'] == 0:
|
||||
res = cls.sum_counter(custom)
|
||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
||||
elif custom['category'] == 1:
|
||||
res = cls.attribute_counter(custom)
|
||||
else:
|
||||
res = cls.relation_counter(custom.get('type_id'),
|
||||
custom.get('level'),
|
||||
custom.get('options', {}).get('filter', ''),
|
||||
custom.get('options', {}).get('type_ids', ''))
|
||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
||||
elif custom['category'] == 2:
|
||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
||||
|
||||
if res and flush:
|
||||
result[custom['id']] = res
|
||||
cls.set(result)
|
||||
|
||||
return res
|
||||
cls.set(result)
|
||||
|
||||
@staticmethod
|
||||
def relation_counter(type_id, level, other_filer, type_ids):
|
||||
from api.lib.cmdb.search.ci_relation.search import Search as RelSearch
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
def summary_counter(type_id):
|
||||
return db.session.query(CI.id).filter(CI.deleted.is_(False)).filter(CI.type_id == type_id).count()
|
||||
|
||||
query = "_type:{}".format(type_id)
|
||||
s = search(query, count=1000000)
|
||||
try:
|
||||
type_names, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
@staticmethod
|
||||
def relation_counter(type_id, level):
|
||||
|
||||
uri = current_app.config.get('CMDB_API')
|
||||
|
||||
type_names = requests.get("{}/ci/s?q=_type:{}&count=10000".format(uri, type_id)).json().get('result')
|
||||
type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names]
|
||||
|
||||
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||
try:
|
||||
stats = s.statistics(type_ids)
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
url = "{}/ci_relations/statistics?root_ids={}&level={}".format(
|
||||
uri, ','.join([i[0] for i in type_id_names]), level)
|
||||
stats = requests.get(url).json()
|
||||
|
||||
id2name = dict(type_id_names)
|
||||
type_ids = set()
|
||||
for i in (stats.get('detail') or []):
|
||||
for j in stats['detail'][i]:
|
||||
type_ids.add(j)
|
||||
|
||||
for type_id in type_ids:
|
||||
_type = CITypeCache.get(type_id)
|
||||
id2name[type_id] = _type and _type.alias
|
||||
@@ -332,94 +307,9 @@ class CMDBCounterCache(object):
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def attribute_counter(custom):
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
custom.setdefault('options', {})
|
||||
type_id = custom.get('type_id')
|
||||
attr_id = custom.get('attr_id')
|
||||
type_ids = custom['options'].get('type_ids') or (type_id and [type_id])
|
||||
attr_ids = list(map(str, custom['options'].get('attr_ids') or (attr_id and [attr_id])))
|
||||
other_filter = custom['options'].get('filter')
|
||||
other_filter = "({})".format(other_filter) if other_filter else ''
|
||||
|
||||
if custom['options'].get('ret') == 'cis':
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
s = search(query, fl=attr_ids, ret_key='alias', count=100)
|
||||
try:
|
||||
cis, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
|
||||
return cis
|
||||
|
||||
result = dict()
|
||||
# level = 1
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
s = search(query, fl=attr_ids, facet=[attr_ids[0]], count=1)
|
||||
try:
|
||||
_, _, _, _, _, facet = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[i[0]] = i[1]
|
||||
if len(attr_ids) == 1:
|
||||
return result
|
||||
|
||||
# level = 2
|
||||
for v in result:
|
||||
query = "_type:({}),{},{}:{}".format(";".join(map(str, type_ids)), other_filter, attr_ids[0], v)
|
||||
s = search(query, fl=attr_ids, facet=[attr_ids[1]], count=1)
|
||||
try:
|
||||
_, _, _, _, _, facet = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
result[v] = dict()
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[v][i[0]] = i[1]
|
||||
|
||||
if len(attr_ids) == 2:
|
||||
return result
|
||||
|
||||
# level = 3
|
||||
for v1 in result:
|
||||
if not isinstance(result[v1], dict):
|
||||
continue
|
||||
for v2 in result[v1]:
|
||||
query = "_type:({}),{},{}:{},{}:{}".format(";".join(map(str, type_ids)), other_filter,
|
||||
attr_ids[0], v1, attr_ids[1], v2)
|
||||
s = search(query, fl=attr_ids, facet=[attr_ids[2]], count=1)
|
||||
try:
|
||||
_, _, _, _, _, facet = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
result[v1][v2] = dict()
|
||||
for i in (list(facet.values()) or [[]])[0]:
|
||||
result[v1][v2][i[0]] = i[1]
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def sum_counter(custom):
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
custom.setdefault('options', {})
|
||||
type_id = custom.get('type_id')
|
||||
type_ids = custom['options'].get('type_ids') or (type_id and [type_id])
|
||||
other_filter = custom['options'].get('filter') or ''
|
||||
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
s = search(query, count=1)
|
||||
try:
|
||||
_, _, _, _, numfound, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error(e)
|
||||
return
|
||||
|
||||
return numfound
|
||||
def attribute_counter(type_id, attr_id):
|
||||
uri = current_app.config.get('CMDB_API')
|
||||
url = "{}/ci/s?q=_type:{}&fl={}&facet={}".format(uri, type_id, attr_id, attr_id)
|
||||
res = requests.get(url).json()
|
||||
if res.get('facet'):
|
||||
return dict([i[:2] for i in list(res.get('facet').values())[0]])
|
||||
|
@@ -40,14 +40,12 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.tasks.cmdb import ci_delete
|
||||
from api.tasks.cmdb import ci_relation_add
|
||||
from api.tasks.cmdb import ci_relation_cache
|
||||
from api.tasks.cmdb import ci_relation_delete
|
||||
|
||||
@@ -68,13 +66,11 @@ class CIManager(object):
|
||||
@staticmethod
|
||||
def get_type_name(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
return CITypeCache.get(ci.type_id).name
|
||||
|
||||
@staticmethod
|
||||
def get_type(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
return CITypeCache.get(ci.type_id)
|
||||
|
||||
@staticmethod
|
||||
@@ -96,7 +92,9 @@ class CIManager(object):
|
||||
|
||||
res = dict()
|
||||
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
@@ -163,11 +161,14 @@ class CIManager(object):
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
valid and cls.valid_ci_only_read(ci)
|
||||
if valid:
|
||||
cls.valid_ci_only_read(ci)
|
||||
|
||||
res = dict()
|
||||
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
@@ -246,7 +247,7 @@ class CIManager(object):
|
||||
for i in unique_constraints:
|
||||
attr_ids.extend(i.attr_ids)
|
||||
|
||||
attrs = [AttributeCache.get(i) for i in set(attr_ids)]
|
||||
attrs = [AttributeCache.get(i) for i in list(set(attr_ids))]
|
||||
id2name = {i.id: i.name for i in attrs if i}
|
||||
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
|
||||
if not_existed_fields and ci_id is not None:
|
||||
@@ -291,7 +292,7 @@ class CIManager(object):
|
||||
_is_admin=False,
|
||||
**ci_dict):
|
||||
"""
|
||||
add ci
|
||||
|
||||
:param ci_type_name:
|
||||
:param exist_policy: replace or reject or need
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
@@ -306,7 +307,9 @@ class CIManager(object):
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||
|
||||
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||
unique_value = ci_dict.get(unique_key.name)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.alias)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.id)
|
||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
||||
@@ -329,6 +332,10 @@ class CIManager(object):
|
||||
if exist_policy == ExistPolicy.NEED:
|
||||
return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value)))
|
||||
|
||||
from api.lib.cmdb.const import L_CI
|
||||
if L_CI and len(CI.get_by(type_id=ci_type.id)) > L_CI * 2:
|
||||
return abort(400, ErrFormat.limit_ci.format(L_CI * 2))
|
||||
|
||||
limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {}
|
||||
|
||||
if existed is None: # set default
|
||||
@@ -359,18 +366,13 @@ class CIManager(object):
|
||||
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
ref_ci_dict = dict()
|
||||
for k in ci_dict:
|
||||
if k.startswith("$") and "." in k:
|
||||
ref_ci_dict[k] = ci_dict[k]
|
||||
continue
|
||||
|
||||
if k not in ci_type_attrs_name and (
|
||||
k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
|
||||
if k not in ci_type_attrs_name and k not in ci_type_attrs_alias and \
|
||||
_no_attribute_policy == ExistPolicy.REJECT:
|
||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs):
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and \
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
@@ -389,9 +391,6 @@ class CIManager(object):
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
if ref_ci_dict: # add relations
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
||||
@@ -434,10 +433,6 @@ class CIManager(object):
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
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)
|
||||
|
||||
@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)))
|
||||
@@ -460,22 +455,17 @@ class CIManager(object):
|
||||
for attr_name in attr_names:
|
||||
value_table = TableMap(attr_name=attr_name).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
item.delete()
|
||||
|
||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
item.delete()
|
||||
|
||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
item.delete(commit=False)
|
||||
item.delete()
|
||||
|
||||
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
|
||||
ad_ci and ad_ci.update(is_accept=False, accept_by=None, accept_time=None, filter_none=False, commit=False)
|
||||
|
||||
ci.delete(commit=False) # TODO: soft delete
|
||||
|
||||
db.session.commit()
|
||||
ci.delete() # TODO: soft delete
|
||||
|
||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||
|
||||
@@ -490,8 +480,11 @@ class CIManager(object):
|
||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr=unique_key).table
|
||||
|
||||
v = (value_table.get_by(attr_id=unique_key.id, value=unique_value, to_dict=False, first=True) or
|
||||
abort(404, ErrFormat.not_found))
|
||||
v = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True) \
|
||||
or abort(404, ErrFormat.not_found)
|
||||
|
||||
ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id)))
|
||||
|
||||
@@ -537,7 +530,6 @@ class CIManager(object):
|
||||
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
||||
heartbeat_dict.get(i.get("_id"))) for i in res
|
||||
if i.get("private_ip")]
|
||||
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
@@ -657,7 +649,6 @@ class CIManager(object):
|
||||
return res
|
||||
|
||||
current_app.logger.warning("cache not hit...............")
|
||||
|
||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
|
||||
|
||||
|
||||
@@ -683,7 +674,6 @@ class CIRelationManager(object):
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
|
||||
res[ci_type.name] = children
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -755,28 +745,17 @@ class CIRelationManager(object):
|
||||
return ci_ids
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||
db.session.remove()
|
||||
def _check_constraint(first_ci_id, second_ci_id, type_relation):
|
||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||
return
|
||||
|
||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
relation_type_id=type_relation.relation_type_id, to_dict=False)
|
||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id,
|
||||
relation_type_id=type_relation.relation_type_id, to_dict=False)
|
||||
if type_relation.constraint == ConstraintEnum.One2One:
|
||||
for i in first_existed:
|
||||
if i.second_ci.type_id == second_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
if type_relation.constraint == ConstraintEnum.One2One and (first_existed or second_existed):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
||||
|
||||
for i in second_existed:
|
||||
if i.first_ci.type_id == first_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
||||
|
||||
if type_relation.constraint == ConstraintEnum.One2Many:
|
||||
for i in second_existed:
|
||||
if i.first_ci.type_id == first_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
if type_relation.constraint == ConstraintEnum.One2Many and second_existed:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
@@ -815,17 +794,15 @@ class CIRelationManager(object):
|
||||
else:
|
||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||
|
||||
with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
|
||||
cls._check_constraint(first_ci_id, second_ci_id, type_relation)
|
||||
|
||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||
|
||||
if more is not None:
|
||||
existed.upadte(more=more)
|
||||
@@ -873,12 +850,12 @@ class CIRelationManager(object):
|
||||
:param children:
|
||||
:return:
|
||||
"""
|
||||
if isinstance(parents, list):
|
||||
if parents is not None and isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(parent_id, ci_id)
|
||||
|
||||
if isinstance(children, list):
|
||||
if children is not None and isinstance(children, list):
|
||||
for child_id in children:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
@@ -892,7 +869,7 @@ class CIRelationManager(object):
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(parents, list):
|
||||
if parents is not None and isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id)
|
||||
|
@@ -1,13 +1,11 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from toposort import toposort_flatten
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -18,9 +16,7 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
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.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
@@ -31,10 +27,7 @@ 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.models.cmdb import Attribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIFilterPerms
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeAttributeGroup
|
||||
@@ -44,9 +37,7 @@ from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
@@ -64,7 +55,6 @@ class CITypeManager(object):
|
||||
@staticmethod
|
||||
def get_name_by_id(type_id):
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
|
||||
return ci_type and ci_type.name
|
||||
|
||||
@staticmethod
|
||||
@@ -76,7 +66,7 @@ class CITypeManager(object):
|
||||
@staticmethod
|
||||
def get_ci_types(type_name=None):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin():
|
||||
resources = set([i.get('name') for i in ACLManager().get_resources("CIType")])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
@@ -115,8 +105,11 @@ class CITypeManager(object):
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
from api.lib.cmdb.const import L_TYPE
|
||||
if L_TYPE and len(CIType.get_by()) > L_TYPE * 2:
|
||||
return abort(400, ErrFormat.limit_ci_type.format(L_TYPE * 2))
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||
unique_key = kwargs.pop("unique_key", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
|
||||
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
||||
@@ -186,7 +179,6 @@ class CITypeManager(object):
|
||||
def set_enabled(cls, type_id, enabled=True):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
ci_type.update(enabled=enabled)
|
||||
|
||||
return type_id
|
||||
|
||||
@classmethod
|
||||
@@ -206,21 +198,19 @@ class CITypeManager(object):
|
||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
||||
|
||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
item.soft_delete()
|
||||
|
||||
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
item.soft_delete()
|
||||
|
||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
|
||||
AutoDiscoveryCIType, CIFilterPerms]:
|
||||
for item in table.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
for item in PreferenceTreeView.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
for item in PreferenceShowAttributes.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
|
||||
db.session.commit()
|
||||
for item in CITypeGroupItem.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
|
||||
ci_type.soft_delete()
|
||||
|
||||
@@ -271,17 +261,16 @@ class CITypeGroupManager(object):
|
||||
@staticmethod
|
||||
def add(name):
|
||||
CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name))
|
||||
|
||||
return CITypeGroup.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(gid, name, type_ids):
|
||||
"""
|
||||
update part
|
||||
:param gid:
|
||||
:param name:
|
||||
:param type_ids:
|
||||
:return:
|
||||
:param gid:
|
||||
:param name:
|
||||
:param type_ids:
|
||||
:return:
|
||||
"""
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||
@@ -338,17 +327,6 @@ class CITypeAttributeManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attr_name(ci_type_name, key):
|
||||
ci_type = CITypeCache.get(ci_type_name)
|
||||
if ci_type is None:
|
||||
return
|
||||
|
||||
for i in CITypeAttributesCache.get(ci_type.id):
|
||||
attr = AttributeCache.get(i.attr_id)
|
||||
if attr and (attr.name == key or attr.alias == key):
|
||||
return attr.name
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
@@ -369,19 +347,8 @@ class CITypeAttributeManager(object):
|
||||
attr_dict.pop('choice_web_hook', None)
|
||||
|
||||
result.append(attr_dict)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_common_attributes(type_ids):
|
||||
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
|
||||
attr2types = {}
|
||||
for i in result:
|
||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
||||
|
||||
return [AttributeCache.get(attr_id).to_dict() for attr_id in attr2types
|
||||
if len(attr2types[attr_id]) == len(type_ids)]
|
||||
|
||||
@staticmethod
|
||||
def _check(type_id, attr_ids):
|
||||
ci_type = CITypeManager.check_is_existed(type_id)
|
||||
@@ -398,10 +365,10 @@ class CITypeAttributeManager(object):
|
||||
def add(cls, type_id, attr_ids=None, **kwargs):
|
||||
"""
|
||||
add attributes to CIType
|
||||
:param type_id:
|
||||
:param type_id:
|
||||
:param attr_ids: list
|
||||
:param kwargs:
|
||||
:return:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
attr_ids = list(set(attr_ids))
|
||||
|
||||
@@ -428,9 +395,9 @@ class CITypeAttributeManager(object):
|
||||
def update(cls, type_id, attributes):
|
||||
"""
|
||||
update attributes to CIType
|
||||
:param type_id:
|
||||
:param type_id:
|
||||
:param attributes: list
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
cls._check(type_id, [i.get('attr_id') for i in attributes])
|
||||
|
||||
@@ -458,9 +425,9 @@ class CITypeAttributeManager(object):
|
||||
def delete(cls, type_id, attr_ids=None):
|
||||
"""
|
||||
delete attributes from CIType
|
||||
:param type_id:
|
||||
:param type_id:
|
||||
:param attr_ids: list
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
from api.tasks.cmdb import ci_cache
|
||||
|
||||
@@ -567,7 +534,6 @@ class CITypeRelationManager(object):
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@classmethod
|
||||
@@ -576,22 +542,6 @@ class CITypeRelationManager(object):
|
||||
|
||||
return [cls._wrap_relation_type_dict(child.child_id, child) for child in children]
|
||||
|
||||
@classmethod
|
||||
def recursive_level2children(cls, parent_id):
|
||||
result = dict()
|
||||
|
||||
def get_children(_id, level):
|
||||
children = CITypeRelation.get_by(parent_id=_id, to_dict=False)
|
||||
result[level + 1] = [i.child.to_dict() for i in children]
|
||||
|
||||
for i in children:
|
||||
if i.child_id != _id:
|
||||
get_children(i.child_id, level + 1)
|
||||
|
||||
get_children(parent_id, 0)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_parents(cls, child_id):
|
||||
parents = CITypeRelation.get_by(child_id=child_id, to_dict=False)
|
||||
@@ -614,17 +564,6 @@ class CITypeRelationManager(object):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
rels = {}
|
||||
for i in CITypeRelation.get_by(to_dict=False):
|
||||
rels.setdefault(i.child_id, set()).add(i.parent_id)
|
||||
rels.setdefault(c.id, set()).add(p.id)
|
||||
|
||||
try:
|
||||
toposort_flatten(rels)
|
||||
except toposort.CircularDependencyError as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.circular_dependency_error)
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id,
|
||||
@@ -653,8 +592,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
ctr = (CITypeRelation.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id))))
|
||||
ctr = CITypeRelation.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id)))
|
||||
ctr.soft_delete()
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id,
|
||||
@@ -708,7 +647,6 @@ class CITypeAttributeGroupManager(object):
|
||||
:param name:
|
||||
:param group_order: group order
|
||||
:param attr_order:
|
||||
:param is_update:
|
||||
:return:
|
||||
"""
|
||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False)
|
||||
@@ -749,8 +687,8 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(group_id):
|
||||
group = (CITypeAttributeGroup.get_by_id(group_id) or
|
||||
abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id))))
|
||||
group = CITypeAttributeGroup.get_by_id(group_id) \
|
||||
or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id)))
|
||||
group.soft_delete()
|
||||
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
|
||||
@@ -862,12 +800,6 @@ class CITypeTemplateManager(object):
|
||||
for added_id in set(id2obj_dicts.keys()) - set(existed_ids):
|
||||
if cls == CIType:
|
||||
CITypeManager.add(**id2obj_dicts[added_id])
|
||||
elif cls == CITypeRelation:
|
||||
CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
|
||||
id2obj_dicts[added_id].get('child_id'),
|
||||
id2obj_dicts[added_id].get('relation_type_id'),
|
||||
id2obj_dicts[added_id].get('constraint'),
|
||||
)
|
||||
else:
|
||||
cls.create(flush=True, **id2obj_dicts[added_id])
|
||||
|
||||
@@ -1025,8 +957,8 @@ class CITypeTemplateManager(object):
|
||||
rule['uid'] = current_user.uid
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD.add(**rule)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
except:
|
||||
pass
|
||||
|
||||
def import_template(self, tpt):
|
||||
import time
|
||||
@@ -1185,8 +1117,8 @@ class CITypeTriggerManager(object):
|
||||
|
||||
@staticmethod
|
||||
def update(_id, notify):
|
||||
existed = (CITypeTrigger.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||
existed = CITypeTrigger.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
||||
|
||||
existed2 = existed.to_dict()
|
||||
new = existed.update(notify=notify)
|
||||
@@ -1200,8 +1132,8 @@ class CITypeTriggerManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
existed = (CITypeTrigger.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||
existed = CITypeTrigger.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@@ -1224,16 +1156,16 @@ class CITypeTriggerManager(object):
|
||||
|
||||
result = []
|
||||
for v in values:
|
||||
if (isinstance(v.value, (datetime.date, datetime.datetime)) and
|
||||
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d")):
|
||||
if isinstance(v.value, (datetime.date, datetime.datetime)) and \
|
||||
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d"):
|
||||
result.append(v)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def trigger_notify(trigger, ci):
|
||||
if (trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
|
||||
not trigger.notify.get('notify_at')):
|
||||
if trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or \
|
||||
not trigger.notify.get('notify_at'):
|
||||
from api.tasks.cmdb import trigger_notify
|
||||
|
||||
trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE)
|
||||
|
@@ -99,7 +99,5 @@ CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||
|
||||
L_TYPE = None
|
||||
L_CI = None
|
||||
|
@@ -14,14 +14,6 @@ class CustomDashboardManager(object):
|
||||
def get():
|
||||
return sorted(CustomDashboard.get_by(to_dict=True), key=lambda x: (x["category"], x['order']))
|
||||
|
||||
@staticmethod
|
||||
def preview(**kwargs):
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
res = CMDBCounterCache.update(kwargs, flush=False)
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def add(**kwargs):
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
@@ -31,9 +23,9 @@ class CustomDashboardManager(object):
|
||||
|
||||
new = CustomDashboard.create(**kwargs)
|
||||
|
||||
res = CMDBCounterCache.update(new.to_dict())
|
||||
CMDBCounterCache.update(new.to_dict())
|
||||
|
||||
return new, res
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def update(_id, **kwargs):
|
||||
@@ -43,9 +35,9 @@ class CustomDashboardManager(object):
|
||||
|
||||
new = existed.update(**kwargs)
|
||||
|
||||
res = CMDBCounterCache.update(new.to_dict())
|
||||
CMDBCounterCache.update(new.to_dict())
|
||||
|
||||
return new, res
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def batch_update(id2options):
|
||||
|
@@ -176,8 +176,8 @@ class AttributeHistoryManger(object):
|
||||
def get_record_detail(record_id):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
record = (OperationRecord.get_by_id(record_id) or
|
||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id))))
|
||||
record = OperationRecord.get_by_id(record_id) or \
|
||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id)))
|
||||
|
||||
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
||||
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
@@ -37,12 +37,10 @@ class PreferenceManager(object):
|
||||
def get_types(instance=False, tree=False):
|
||||
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 []
|
||||
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(PreferenceShowAttributes.type_id).all() \
|
||||
if instance else []
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
type_ids = set([i.type_id for i in types + tree_types])
|
||||
|
||||
type_ids = list(set([i.type_id for i in types + tree_types]))
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@staticmethod
|
||||
|
@@ -42,7 +42,7 @@ FACET_QUERY1 = """
|
||||
|
||||
FACET_QUERY = """
|
||||
SELECT {0}.value,
|
||||
count(distinct({0}.ci_id))
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
||||
WHERE {0}.attr_id={2:d}
|
||||
|
@@ -24,21 +24,21 @@ class RelationTypeManager(object):
|
||||
|
||||
@staticmethod
|
||||
def add(name):
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and abort(
|
||||
400, ErrFormat.relation_type_exists.format(name))
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and \
|
||||
abort(400, ErrFormat.relation_type_exists.format(name))
|
||||
|
||||
return RelationType.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(rel_id, name):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(
|
||||
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
return existed.update(name=name)
|
||||
|
||||
@staticmethod
|
||||
def delete(rel_id):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(
|
||||
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
existed.soft_delete()
|
||||
|
@@ -245,8 +245,10 @@ class Search(object):
|
||||
new_table = _v_query_sql
|
||||
|
||||
if self.only_type_query or not self.type_id_list:
|
||||
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \
|
||||
"FROM ({0}) AS C " \
|
||||
"ORDER BY C.value {2} " \
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count)
|
||||
|
||||
elif self.type_id_list:
|
||||
self.query_sql = """SELECT C.ci_id
|
||||
|
@@ -297,8 +297,8 @@ class Search(object):
|
||||
if not attr:
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
||||
|
||||
sort_by = ("{0}.keyword".format(field)
|
||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field)
|
||||
sort_by = "{0}.keyword".format(field) \
|
||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field
|
||||
sorts.append({sort_by: {"order": sort_type}})
|
||||
|
||||
self.query.update(dict(sort=sorts))
|
||||
|
@@ -7,6 +7,7 @@ import json
|
||||
import re
|
||||
|
||||
import six
|
||||
from markupsafe import escape
|
||||
|
||||
import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
@@ -32,8 +33,8 @@ class ValueTypeMap(object):
|
||||
deserialize = {
|
||||
ValueTypeEnum.INT: string2int,
|
||||
ValueTypeEnum.FLOAT: float,
|
||||
ValueTypeEnum.TEXT: lambda x: x,
|
||||
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
|
||||
ValueTypeEnum.TEXT: lambda x: escape(x).encode('utf-8').decode('utf-8'),
|
||||
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(escape(x).encode('utf-8').decode('utf-8'))[0],
|
||||
ValueTypeEnum.DATETIME: str2datetime,
|
||||
ValueTypeEnum.DATE: str2datetime,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
|
@@ -80,10 +80,9 @@ class AttributeValueManager(object):
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _deserialize_value(value_type, value):
|
||||
def __deserialize_value(value_type, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
deserialize = ValueTypeMap.deserialize[value_type]
|
||||
try:
|
||||
v = deserialize(value)
|
||||
@@ -92,13 +91,13 @@ class AttributeValueManager(object):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
@staticmethod
|
||||
def _check_is_choice(attr, value_type, value):
|
||||
def __check_is_choice(attr, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook)
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
@staticmethod
|
||||
def _check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
def __check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
existed = db.session.query(value_table.attr_id).join(CI, CI.id == value_table.ci_id).filter(
|
||||
CI.type_id == type_id).filter(
|
||||
value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter(
|
||||
@@ -107,20 +106,20 @@ class AttributeValueManager(object):
|
||||
existed and abort(400, ErrFormat.attribute_value_unique_required.format(attr.alias, value))
|
||||
|
||||
@staticmethod
|
||||
def _check_is_required(type_id, attr, value, type_attr=None):
|
||||
def __check_is_required(type_id, attr, value, type_attr=None):
|
||||
type_attr = type_attr or CITypeAttributeCache.get(type_id, attr.id)
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
v = self.__deserialize_value(attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self._check_is_unique(
|
||||
attr.is_choice and value and self.__check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self.__check_is_unique(
|
||||
value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
|
||||
|
||||
self._check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
self.__check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
@@ -145,7 +144,7 @@ class AttributeValueManager(object):
|
||||
return record_id
|
||||
|
||||
@staticmethod
|
||||
def _compute_attr_value_from_expr(expr, ci_dict):
|
||||
def __compute_attr_value_from_expr(expr, ci_dict):
|
||||
t = jinja2.Template(expr).render(ci_dict)
|
||||
|
||||
try:
|
||||
@@ -155,7 +154,7 @@ class AttributeValueManager(object):
|
||||
return t
|
||||
|
||||
@staticmethod
|
||||
def _compute_attr_value_from_script(script, ci_dict):
|
||||
def __compute_attr_value_from_script(script, ci_dict):
|
||||
script = jinja2.Template(script).render(ci_dict)
|
||||
|
||||
script_f = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
|
||||
@@ -184,22 +183,22 @@ class AttributeValueManager(object):
|
||||
|
||||
return [var for var in schema.get("properties")]
|
||||
|
||||
def _compute_attr_value(self, attr, payload, ci_id):
|
||||
attrs = (self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr')
|
||||
else self._jinja2_parse(attr['compute_script']))
|
||||
def _compute_attr_value(self, attr, payload, ci):
|
||||
attrs = self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr') else \
|
||||
self._jinja2_parse(attr['compute_script'])
|
||||
not_existed = [i for i in attrs if i not in payload]
|
||||
if ci_id is not None:
|
||||
payload.update(self.get_attr_values(not_existed, ci_id))
|
||||
if ci is not None:
|
||||
payload.update(self.get_attr_values(not_existed, ci.id))
|
||||
|
||||
if attr['compute_expr']:
|
||||
return self._compute_attr_value_from_expr(attr['compute_expr'], payload)
|
||||
return self.__compute_attr_value_from_expr(attr['compute_expr'], payload)
|
||||
elif attr['compute_script']:
|
||||
return self._compute_attr_value_from_script(attr['compute_script'], payload)
|
||||
return self.__compute_attr_value_from_script(attr['compute_script'], payload)
|
||||
|
||||
def handle_ci_compute_attributes(self, ci_dict, computed_attrs, ci):
|
||||
payload = copy.deepcopy(ci_dict)
|
||||
for attr in computed_attrs:
|
||||
computed_value = self._compute_attr_value(attr, payload, ci and ci.id)
|
||||
computed_value = self._compute_attr_value(attr, payload, ci)
|
||||
if computed_value is not None:
|
||||
ci_dict[attr['name']] = computed_value
|
||||
|
||||
@@ -221,7 +220,7 @@ class AttributeValueManager(object):
|
||||
for i in handle_arg_list(value)]
|
||||
ci_dict[key] = value_list
|
||||
if not value_list:
|
||||
self._check_is_required(type_id, attr, '')
|
||||
self.__check_is_required(type_id, attr, '')
|
||||
|
||||
else:
|
||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||
@@ -311,7 +310,7 @@ class AttributeValueManager(object):
|
||||
if attr.is_list:
|
||||
value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)]
|
||||
if not value_list:
|
||||
self._check_is_required(ci.type_id, attr, '')
|
||||
self.__check_is_required(ci.type_id, attr, '')
|
||||
|
||||
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]
|
||||
|
@@ -6,7 +6,6 @@ from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.cache import RoleCache, AppCache
|
||||
from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
from api.lib.perm.acl.resource import ResourceTypeCRUD, ResourceCRUD
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
@@ -95,22 +94,3 @@ class ACLManager(object):
|
||||
avatar=user_info.get('avatar'))
|
||||
|
||||
return result
|
||||
|
||||
def validate_app(self):
|
||||
return AppCache.get(self.app_name)
|
||||
|
||||
def get_all_resources_types(self, q=None, page=1, page_size=999999):
|
||||
app_id = self.validate_app().id
|
||||
numfound, res, id2perms = ResourceTypeCRUD.search(q, app_id, page, page_size)
|
||||
|
||||
return dict(
|
||||
numfound=numfound,
|
||||
groups=[i.to_dict() for i in res],
|
||||
id2perms=id2perms
|
||||
)
|
||||
|
||||
def create_resource(self, payload):
|
||||
payload['app_id'] = self.validate_app().id
|
||||
resource = ResourceCRUD.add(**payload)
|
||||
|
||||
return resource.to_dict()
|
||||
|
@@ -1,46 +0,0 @@
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.models.common_setting import CommonData
|
||||
|
||||
|
||||
class CommonDataCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_data_by_type(data_type):
|
||||
return CommonData.get_by(data_type=data_type)
|
||||
|
||||
@staticmethod
|
||||
def get_data_by_id(_id, to_dict=True):
|
||||
return CommonData.get_by(first=True, id=_id, to_dict=to_dict)
|
||||
|
||||
@staticmethod
|
||||
def create_new_data(data_type, **kwargs):
|
||||
try:
|
||||
return CommonData.create(data_type=data_type, **kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def update_data(_id, **kwargs):
|
||||
existed = CommonDataCRUD.get_data_by_id(_id, to_dict=False)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
return existed.update(**kwargs)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
existed = CommonDataCRUD.get_data_by_id(_id, to_dict=False)
|
||||
if not existed:
|
||||
abort(404, ErrFormat.common_data_not_found.format(_id))
|
||||
try:
|
||||
existed.soft_delete()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
abort(400, str(e))
|
@@ -54,4 +54,3 @@ class ErrFormat(CommonErrFormat):
|
||||
email_is_required = "邮箱不能为空"
|
||||
email_format_error = "邮箱格式错误"
|
||||
|
||||
common_data_not_found = "ID {} 找不到记录"
|
||||
|
@@ -4,7 +4,8 @@ from api.lib.common_setting.utils import get_cur_time_str
|
||||
|
||||
|
||||
def allowed_file(filename, allowed_extensions):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||
|
||||
|
||||
def generate_new_file_name(name):
|
||||
@@ -12,5 +13,4 @@ def generate_new_file_name(name):
|
||||
prev_name = ''.join(name.split(f".{ext}")[:-1])
|
||||
uid = str(uuid.uuid4())
|
||||
cur_str = get_cur_time_str('_')
|
||||
|
||||
return f"{prev_name}_{cur_str}_{uid}.{ext}"
|
||||
|
@@ -55,8 +55,8 @@ def args_validate(model_cls, exclude_args=None):
|
||||
if exclude_args and arg in exclude_args:
|
||||
continue
|
||||
|
||||
if attr.type.python_type == str and attr.type.length and (
|
||||
len(request.values[arg] or '') > attr.type.length):
|
||||
if attr.type.python_type == str and attr.type.length and \
|
||||
len(request.values[arg] or '') > attr.type.length:
|
||||
|
||||
return abort(400, CommonErrFormat.argument_str_length_limit.format(arg, attr.type.length))
|
||||
elif attr.type.python_type in (int, float) and request.values[arg]:
|
||||
|
@@ -19,7 +19,6 @@ def build_api_key(path, params):
|
||||
_secret = "".join([path, secret, values]).encode("utf-8")
|
||||
params["_secret"] = hashlib.sha1(_secret).hexdigest()
|
||||
params["_key"] = key
|
||||
|
||||
return params
|
||||
|
||||
|
||||
|
@@ -5,10 +5,8 @@ import hashlib
|
||||
|
||||
import requests
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import abort, session
|
||||
from flask import current_app, request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import cache
|
||||
@@ -87,8 +85,8 @@ class ACLManager(object):
|
||||
if user:
|
||||
return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False)
|
||||
|
||||
return (Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or
|
||||
Role.get_by(name=name, first=True, to_dict=False))
|
||||
return Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or \
|
||||
Role.get_by(name=name, first=True, to_dict=False)
|
||||
|
||||
def add_resource(self, name, resource_type_name=None):
|
||||
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
||||
|
@@ -8,9 +8,7 @@ from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditScope
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from api.models.acl import App
|
||||
|
||||
|
@@ -4,21 +4,13 @@ import json
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from flask import has_request_context, request
|
||||
from flask import g, has_request_context, request
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.lib.perm.acl import AppCache
|
||||
from api.models.acl import AuditPermissionLog
|
||||
from api.models.acl import AuditResourceLog
|
||||
from api.models.acl import AuditRoleLog
|
||||
from api.models.acl import AuditTriggerLog
|
||||
from api.models.acl import Permission
|
||||
from api.models.acl import Resource
|
||||
from api.models.acl import ResourceGroup
|
||||
from api.models.acl import ResourceType
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import RolePermission
|
||||
from api.models.acl import AuditPermissionLog, AuditResourceLog, AuditRoleLog, AuditTriggerLog, Permission, Resource, \
|
||||
ResourceGroup, ResourceType, Role, RolePermission
|
||||
|
||||
|
||||
class AuditScope(str, Enum):
|
||||
@@ -57,7 +49,7 @@ class AuditCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_current_operate_uid(uid=None):
|
||||
user_id = uid or (getattr(current_user, 'uid', None)) or getattr(current_user, 'user_id', None)
|
||||
user_id = uid or getattr(current_user, 'uid', None)
|
||||
|
||||
if has_request_context() and request.headers.get('X-User-Id'):
|
||||
_user_id = request.headers['X-User-Id']
|
||||
@@ -99,8 +91,11 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditPermissionLog.operate_type == v)
|
||||
|
||||
records = AuditPermissionLog.query.filter(
|
||||
AuditPermissionLog.deleted == 0, *criterion).order_by(
|
||||
AuditPermissionLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
AuditPermissionLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditPermissionLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -163,8 +158,10 @@ class AuditCRUD(object):
|
||||
elif k == 'operate_type':
|
||||
criterion.append(AuditRoleLog.operate_type == v)
|
||||
|
||||
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion).order_by(
|
||||
AuditRoleLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion) \
|
||||
.order_by(AuditRoleLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -226,8 +223,11 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditResourceLog.operate_type == v)
|
||||
|
||||
records = AuditResourceLog.query.filter(
|
||||
AuditResourceLog.deleted == 0, *criterion).order_by(
|
||||
AuditResourceLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
AuditResourceLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditResourceLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -257,8 +257,11 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditTriggerLog.operate_type == v)
|
||||
|
||||
records = AuditTriggerLog.query.filter(
|
||||
AuditTriggerLog.deleted == 0, *criterion).order_by(
|
||||
AuditTriggerLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
AuditTriggerLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditTriggerLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
|
@@ -60,15 +60,15 @@ class UserCache(object):
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
user = (cache.get(cls.PREFIX_ID.format(key)) or
|
||||
cache.get(cls.PREFIX_NAME.format(key)) or
|
||||
cache.get(cls.PREFIX_NICK.format(key)) or
|
||||
cache.get(cls.PREFIX_WXID.format(key)))
|
||||
user = cache.get(cls.PREFIX_ID.format(key)) or \
|
||||
cache.get(cls.PREFIX_NAME.format(key)) or \
|
||||
cache.get(cls.PREFIX_NICK.format(key)) or \
|
||||
cache.get(cls.PREFIX_WXID.format(key))
|
||||
if not user:
|
||||
user = (User.query.get(key) or
|
||||
User.query.get_by_username(key) or
|
||||
User.query.get_by_nickname(key) or
|
||||
User.query.get_by_wxid(key))
|
||||
user = User.query.get(key) or \
|
||||
User.query.get_by_username(key) or \
|
||||
User.query.get_by_nickname(key) or \
|
||||
User.query.get_by_wxid(key)
|
||||
if user:
|
||||
cls.set(user)
|
||||
|
||||
|
@@ -4,9 +4,7 @@ import datetime
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateSource
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource
|
||||
from api.lib.perm.acl.cache import PermissionCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -99,8 +97,8 @@ class PermissionCRUD(object):
|
||||
elif group_id is not None:
|
||||
from api.models.acl import ResourceGroup
|
||||
|
||||
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
group = ResourceGroup.get_by_id(group_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
app_id = group.app_id
|
||||
rt_id = group.resource_type_id
|
||||
if not perms:
|
||||
@@ -208,8 +206,8 @@ class PermissionCRUD(object):
|
||||
if resource_id is not None:
|
||||
from api.models.acl import Resource
|
||||
|
||||
resource = Resource.get_by_id(resource_id) or abort(
|
||||
404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
||||
resource = Resource.get_by_id(resource_id) or \
|
||||
abort(404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
||||
app_id = resource.app_id
|
||||
rt_id = resource.resource_type_id
|
||||
if not perms:
|
||||
@@ -218,8 +216,8 @@ class PermissionCRUD(object):
|
||||
elif group_id is not None:
|
||||
from api.models.acl import ResourceGroup
|
||||
|
||||
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
group = ResourceGroup.get_by_id(group_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
app_id = group.app_id
|
||||
|
||||
rt_id = group.resource_type_id
|
||||
|
@@ -5,9 +5,7 @@ from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditScope
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import ResourceCache
|
||||
from api.lib.perm.acl.cache import ResourceGroupCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -104,8 +102,8 @@ class ResourceTypeCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def delete(cls, rt_id):
|
||||
rt = ResourceType.get_by_id(rt_id) or abort(
|
||||
404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
|
||||
rt = ResourceType.get_by_id(rt_id) or \
|
||||
abort(404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
|
||||
|
||||
Resource.get_by(resource_type_id=rt_id) and abort(400, ErrFormat.resource_type_cannot_delete)
|
||||
|
||||
@@ -167,8 +165,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def add(name, type_id, app_id, uid=None):
|
||||
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||
400, ErrFormat.resource_group_exists.format(name))
|
||||
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \
|
||||
abort(400, ErrFormat.resource_group_exists.format(name))
|
||||
rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||
|
||||
AuditCRUD.add_resource_log(app_id, AuditOperateType.create,
|
||||
@@ -177,8 +175,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def update(rg_id, items):
|
||||
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
|
||||
existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False)
|
||||
existed_ids = [i.resource_id for i in existed]
|
||||
@@ -198,8 +196,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(rg_id):
|
||||
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
|
||||
origin = rg.to_dict()
|
||||
rg.soft_delete()
|
||||
@@ -268,8 +266,8 @@ class ResourceCRUD(object):
|
||||
def add(cls, name, type_id, app_id, uid=None):
|
||||
type_id = cls._parse_resource_type_id(type_id, app_id)
|
||||
|
||||
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||
400, ErrFormat.resource_exists.format(name))
|
||||
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \
|
||||
abort(400, ErrFormat.resource_exists.format(name))
|
||||
|
||||
r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||
|
||||
|
@@ -10,9 +10,7 @@ from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditScope
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import HasResourceRoleCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
@@ -71,16 +69,16 @@ class RoleRelationCRUD(object):
|
||||
@staticmethod
|
||||
def get_parent_ids(rid, app_id):
|
||||
if app_id is not None:
|
||||
return ([i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] +
|
||||
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)])
|
||||
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] + \
|
||||
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)]
|
||||
else:
|
||||
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)]
|
||||
|
||||
@staticmethod
|
||||
def get_child_ids(rid, app_id):
|
||||
if app_id is not None:
|
||||
return ([i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] +
|
||||
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)])
|
||||
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] + \
|
||||
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)]
|
||||
else:
|
||||
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)]
|
||||
|
||||
|
@@ -6,10 +6,9 @@ import json
|
||||
import re
|
||||
from fnmatch import fnmatch
|
||||
|
||||
from flask import abort
|
||||
from flask import abort, current_app
|
||||
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
@@ -9,9 +9,7 @@ from flask import abort
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.audit import AuditScope
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
@@ -51,9 +49,11 @@ class UserCRUD(object):
|
||||
kwargs['block'] = 0
|
||||
kwargs['key'], kwargs['secret'] = cls.gen_key_secret()
|
||||
|
||||
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(User.employee_id.desc()).first()
|
||||
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(
|
||||
User.employee_id.desc()).first()
|
||||
|
||||
biggest_employee_id = int(float(user_employee.employee_id)) if user_employee is not None else 0
|
||||
biggest_employee_id = int(float(user_employee.employee_id)) \
|
||||
if user_employee is not None else 0
|
||||
|
||||
kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1)
|
||||
user = User.create(**kwargs)
|
||||
|
@@ -9,8 +9,6 @@ class CommonErrFormat(object):
|
||||
|
||||
not_found = "不存在"
|
||||
|
||||
circular_dependency_error = "存在循环依赖!"
|
||||
|
||||
unknown_search_error = "未知搜索错误"
|
||||
|
||||
invalid_json = "json格式似乎不正确了, 请仔细确认一下!"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from typing import Set
|
||||
@@ -112,7 +113,7 @@ class RedisHandler(object):
|
||||
try:
|
||||
ret = self.r.hdel(prefix, key_id)
|
||||
if not ret:
|
||||
current_app.logger.warning("[{0}] is not in redis".format(key_id))
|
||||
current_app.logger.warn("[{0}] is not in redis".format(key_id))
|
||||
except Exception as e:
|
||||
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
||||
|
||||
@@ -203,9 +204,9 @@ class ESHandler(object):
|
||||
|
||||
res = self.es.search(index=self.index, body=query, filter_path=filter_path)
|
||||
if res['hits'].get('hits'):
|
||||
return (res['hits']['total']['value'],
|
||||
[i['_source'] for i in res['hits']['hits']],
|
||||
res.get("aggregations", {}))
|
||||
return res['hits']['total']['value'], \
|
||||
[i['_source'] for i in res['hits']['hits']], \
|
||||
res.get("aggregations", {})
|
||||
else:
|
||||
return 0, [], {}
|
||||
|
||||
@@ -256,10 +257,93 @@ class Lock(object):
|
||||
self.release()
|
||||
|
||||
|
||||
class Redis2Handler(object):
|
||||
def __init__(self, flask_app=None, prefix=None):
|
||||
self.flask_app = flask_app
|
||||
self.prefix = prefix
|
||||
self.r = None
|
||||
|
||||
def init_app(self, app):
|
||||
self.flask_app = app
|
||||
config = self.flask_app.config
|
||||
try:
|
||||
pool = redis.ConnectionPool(
|
||||
max_connections=config.get("REDIS_MAX_CONN"),
|
||||
host=config.get("ONEAGENT_REDIS_HOST"),
|
||||
port=config.get("ONEAGENT_REDIS_PORT"),
|
||||
db=config.get("ONEAGENT_REDIS_DB"),
|
||||
password=config.get("ONEAGENT_REDIS_PASSWORD")
|
||||
)
|
||||
self.r = redis.Redis(connection_pool=pool)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
current_app.logger.error("init redis connection failed")
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
value = json.loads(self.r.get(key))
|
||||
except:
|
||||
return
|
||||
|
||||
return value
|
||||
|
||||
def lrange(self, key, start=0, end=-1):
|
||||
try:
|
||||
value = "".join(map(redis_decode, self.r.lrange(key, start, end) or []))
|
||||
except:
|
||||
return
|
||||
|
||||
return value
|
||||
|
||||
def lrange2(self, key, start=0, end=-1):
|
||||
try:
|
||||
return list(map(redis_decode, self.r.lrange(key, start, end) or []))
|
||||
except:
|
||||
return []
|
||||
|
||||
def llen(self, key):
|
||||
try:
|
||||
return self.r.llen(key) or 0
|
||||
except:
|
||||
return 0
|
||||
|
||||
def hget(self, key, field):
|
||||
try:
|
||||
return self.r.hget(key, field)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("hget redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
def hset(self, key, field, value):
|
||||
try:
|
||||
self.r.hset(key, field, value)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("hset redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
def expire(self, key, timeout):
|
||||
try:
|
||||
self.r.expire(key, timeout)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("expire redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
|
||||
def redis_decode(x):
|
||||
try:
|
||||
return x.decode()
|
||||
except Exception as e:
|
||||
print(x, e)
|
||||
try:
|
||||
return x.decode("gb18030")
|
||||
except:
|
||||
return "decode failed"
|
||||
|
||||
|
||||
class AESCrypto(object):
|
||||
BLOCK_SIZE = 16 # Bytes
|
||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE))
|
||||
pad = lambda s: s + (AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) * \
|
||||
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE)
|
||||
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
|
||||
|
||||
iv = '0102030405060708'
|
||||
@@ -268,7 +352,7 @@ class AESCrypto(object):
|
||||
def key():
|
||||
key = current_app.config.get("SECRET_KEY")[:16]
|
||||
if len(key) < 16:
|
||||
key = "{}{}".format(key, (16 - len(key)) * "x")
|
||||
key = "{}{}".format(key, (16 - len(key) * "x"))
|
||||
|
||||
return key.encode('utf8')
|
||||
|
||||
|
@@ -80,10 +80,3 @@ class InternalMessage(Model):
|
||||
category = db.Column(db.VARCHAR(128), nullable=False)
|
||||
message_data = db.Column(db.JSON, nullable=True)
|
||||
employee_id = db.Column(db.Integer, db.ForeignKey('common_employee.employee_id'), comment='ID')
|
||||
|
||||
|
||||
class CommonData(Model):
|
||||
__table_name__ = 'common_data'
|
||||
|
||||
data_type = db.Column(db.VARCHAR(255), default='')
|
||||
data = db.Column(db.JSON)
|
||||
|
@@ -2,8 +2,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from inspect import getmembers
|
||||
from inspect import isclass
|
||||
from inspect import getmembers, isclass
|
||||
|
||||
import six
|
||||
from flask import jsonify
|
||||
@@ -28,15 +27,16 @@ class APIView(Resource):
|
||||
return send_file(*args, **kwargs)
|
||||
|
||||
|
||||
API_PACKAGE = os.path.abspath(os.path.dirname(__file__))
|
||||
API_PACKAGE = "api"
|
||||
|
||||
|
||||
def register_resources(resource_path, rest_api):
|
||||
for root, _, files in os.walk(os.path.join(resource_path)):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
if root not in sys.path:
|
||||
sys.path.insert(1, root)
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("views"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
view = __import__(os.path.splitext(filename)[0])
|
||||
resource_list = [o[0] for o in getmembers(view) if isclass(o[1]) and issubclass(o[1], Resource)]
|
||||
resource_list = [i for i in resource_list if i != "APIView"]
|
||||
|
@@ -5,20 +5,17 @@ import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
|
||||
from api.extensions import celery
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateSource
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import RoleRelationCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.record import OperateRecordCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource
|
||||
from api.models.acl import Resource
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
@@ -7,7 +7,6 @@ import time
|
||||
import jinja2
|
||||
import requests
|
||||
from flask import current_app
|
||||
from flask_login import login_user
|
||||
|
||||
import api.lib.cmdb.ci
|
||||
from api.extensions import celery
|
||||
@@ -19,12 +18,8 @@ from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.mail import send_mail
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.utils import Lock
|
||||
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
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
||||
@@ -89,51 +84,6 @@ def ci_relation_cache(parent_id, child_id):
|
||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_add", queue=CMDB_QUEUE)
|
||||
def ci_relation_add(parent_dict, child_id, uid):
|
||||
"""
|
||||
:param parent_dict: key is '$parent_model.attr_name'
|
||||
:param child_id:
|
||||
:param uid:
|
||||
:return:
|
||||
"""
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
db.session.remove()
|
||||
|
||||
for parent in parent_dict:
|
||||
parent_ci_type_name, _attr_name = parent.strip()[1:].split('.', 1)
|
||||
attr_name = CITypeAttributeManager.get_attr_name(parent_ci_type_name, _attr_name)
|
||||
if attr_name is None:
|
||||
current_app.logger.warning("attr name {} does not exist".format(_attr_name))
|
||||
continue
|
||||
|
||||
parent_dict[parent] = handle_arg_list(parent_dict[parent])
|
||||
for v in parent_dict[parent]:
|
||||
query = "_type:{},{}:{}".format(parent_ci_type_name, attr_name, v)
|
||||
s = search(query)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error('ci relation add failed: {}'.format(e))
|
||||
continue
|
||||
|
||||
for ci in response:
|
||||
try:
|
||||
CIRelationManager.add(ci['_id'], child_id)
|
||||
ci_relation_cache(ci['_id'], child_id)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(e)
|
||||
finally:
|
||||
db.session.remove()
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
def ci_relation_delete(parent_id, child_id):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
@@ -206,18 +156,3 @@ def trigger_notify(notify, ci_id):
|
||||
for i in notify['mail_to'] if i], subject, body)
|
||||
except Exception as e:
|
||||
current_app.logger.error("Send mail failed: {0}".format(str(e)))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.calc_computed_attribute", queue=CMDB_QUEUE)
|
||||
def calc_computed_attribute(attr_id, uid):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
db.session.remove()
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
|
||||
cis = CI.get_by(type_id=i.type_id, to_dict=False)
|
||||
for ci in cis:
|
||||
CIManager.update(ci.id, {})
|
||||
|
@@ -2,13 +2,12 @@
|
||||
|
||||
import datetime
|
||||
|
||||
import jwt
|
||||
import six
|
||||
import jwt
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask_login import login_user
|
||||
from flask_login import logout_user
|
||||
from flask_login import login_user, logout_user
|
||||
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.cache import User
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
@@ -103,7 +104,7 @@ class ResourceView(APIView):
|
||||
type_id = request.values.get('type_id')
|
||||
app_id = request.values.get('app_id')
|
||||
uid = request.values.get('uid')
|
||||
if not uid and hasattr(current_user, "uid"):
|
||||
if not uid and hasattr(g, "user") and hasattr(current_user, "uid"):
|
||||
uid = current_user.uid
|
||||
|
||||
resource = ResourceCRUD.add(name, type_id, app_id, uid)
|
||||
|
@@ -4,6 +4,7 @@
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
@@ -160,7 +161,7 @@ class UserResetPasswordView(APIView):
|
||||
if app.name not in ('cas-server', 'acl'):
|
||||
return abort(403, ErrFormat.invalid_request)
|
||||
|
||||
elif hasattr(current_user, 'username'):
|
||||
elif hasattr(g, 'user'):
|
||||
if current_user.username != request.values['username']:
|
||||
return abort(403, ErrFormat.invalid_request)
|
||||
|
||||
|
@@ -33,8 +33,7 @@ class AttributeSearchView(APIView):
|
||||
|
||||
|
||||
class AttributeView(APIView):
|
||||
url_prefix = ("/attributes", "/attributes/<string:attr_name>", "/attributes/<int:attr_id>",
|
||||
"/attributes/<int:attr_id>/calc_computed_attribute")
|
||||
url_prefix = ("/attributes", "/attributes/<string:attr_name>", "/attributes/<int:attr_id>")
|
||||
|
||||
def get(self, attr_name=None, attr_id=None):
|
||||
attr_manager = AttributeManager()
|
||||
@@ -69,11 +68,6 @@ class AttributeView(APIView):
|
||||
|
||||
@args_validate(AttributeManager.cls)
|
||||
def put(self, attr_id):
|
||||
if request.url.endswith("/calc_computed_attribute"):
|
||||
AttributeManager.calc_computed_attribute(attr_id)
|
||||
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
choice_value = handle_arg_list(request.values.get("choice_value"))
|
||||
params = request.values
|
||||
params["choice_value"] = choice_value
|
||||
|
@@ -11,8 +11,7 @@ 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.const import ExistPolicy
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
@@ -107,7 +106,6 @@ class CIView(APIView):
|
||||
_is_admin=request.values.pop('__is_admin', False),
|
||||
**ci_dict)
|
||||
else:
|
||||
request.values.pop('exist_policy', None)
|
||||
ci_id = manager.add(ci_type,
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
@@ -154,15 +154,9 @@ class EnableCITypeView(APIView):
|
||||
|
||||
|
||||
class CITypeAttributeView(APIView):
|
||||
url_prefix = ("/ci_types/<int:type_id>/attributes", "/ci_types/<string:type_name>/attributes",
|
||||
"/ci_types/common_attributes")
|
||||
url_prefix = ("/ci_types/<int:type_id>/attributes", "/ci_types/<string:type_name>/attributes")
|
||||
|
||||
def get(self, type_id=None, type_name=None):
|
||||
if request.path.endswith("/common_attributes"):
|
||||
type_ids = handle_arg_list(request.values.get('type_ids'))
|
||||
|
||||
return self.jsonify(attributes=CITypeAttributeManager.get_common_attributes(type_ids))
|
||||
|
||||
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found)
|
||||
type_id = t.id
|
||||
unique_id = t.unique_id
|
||||
@@ -506,4 +500,3 @@ class CITypeFilterPermissionView(APIView):
|
||||
@auth_with_app_token
|
||||
def get(self, type_id):
|
||||
return self.jsonify(CIFilterPermsCRUD().get(type_id))
|
||||
|
||||
|
@@ -6,9 +6,7 @@ from flask import request
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
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.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
@@ -19,14 +17,9 @@ from api.resource import APIView
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||
"/ci_type_relations/<int:parent_id>/recursive_level2children",
|
||||
)
|
||||
url_prefix = "/ci_type_relations/<int:parent_id>/children"
|
||||
|
||||
def get(self, parent_id):
|
||||
if request.url.endswith("recursive_level2children"):
|
||||
return self.jsonify(CITypeRelationManager.recursive_level2children(parent_id))
|
||||
|
||||
return self.jsonify(children=CITypeRelationManager.get_children(parent_id))
|
||||
|
||||
|
||||
|
@@ -13,8 +13,7 @@ from api.resource import APIView
|
||||
|
||||
|
||||
class CustomDashboardApiView(APIView):
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||
"/custom_dashboard/preview")
|
||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch")
|
||||
|
||||
def get(self):
|
||||
return self.jsonify(CustomDashboardManager.get())
|
||||
@@ -22,26 +21,17 @@ class CustomDashboardApiView(APIView):
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def post(self):
|
||||
if request.url.endswith("/preview"):
|
||||
return self.jsonify(counter=CustomDashboardManager.preview(**request.values))
|
||||
cm = CustomDashboardManager.add(**request.values)
|
||||
|
||||
cm, counter = CustomDashboardManager.add(**request.values)
|
||||
|
||||
res = cm.to_dict()
|
||||
res.update(counter=counter)
|
||||
|
||||
return self.jsonify(res)
|
||||
return self.jsonify(cm.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_validate(CustomDashboardManager.cls)
|
||||
def put(self, _id=None):
|
||||
if _id is not None:
|
||||
cm, counter = CustomDashboardManager.update(_id, **request.values)
|
||||
cm = CustomDashboardManager.update(_id, **request.values)
|
||||
|
||||
res = cm.to_dict()
|
||||
res.update(counter=counter)
|
||||
|
||||
return self.jsonify(res)
|
||||
return self.jsonify(cm.to_dict())
|
||||
|
||||
CustomDashboardManager.batch_update(request.values.get("id2options"))
|
||||
|
||||
|
@@ -7,8 +7,7 @@ from flask import abort
|
||||
from flask import request
|
||||
|
||||
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 ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
|
@@ -5,9 +5,7 @@ from flask import abort
|
||||
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.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
|
@@ -1,35 +0,0 @@
|
||||
from flask import request
|
||||
|
||||
from api.lib.common_setting.common_data import CommonDataCRUD
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/data'
|
||||
|
||||
|
||||
class DataView(APIView):
|
||||
url_prefix = (f'{prefix}/<string:data_type>',)
|
||||
|
||||
def get(self, data_type):
|
||||
data_list = CommonDataCRUD.get_data_by_type(data_type)
|
||||
|
||||
return self.jsonify(data_list)
|
||||
|
||||
def post(self, data_type):
|
||||
params = request.json
|
||||
CommonDataCRUD.create_new_data(data_type, **params)
|
||||
|
||||
return self.jsonify(params)
|
||||
|
||||
|
||||
class DataViewWithId(APIView):
|
||||
url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',)
|
||||
|
||||
def put(self, data_type, _id):
|
||||
params = request.json
|
||||
res = CommonDataCRUD.update_data(_id, **params)
|
||||
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
def delete(self, data_type, _id):
|
||||
CommonDataCRUD.delete(_id)
|
||||
return self.jsonify({})
|
@@ -16,16 +16,15 @@ class CompanyInfoView(APIView):
|
||||
return self.jsonify(CompanyInfoCRUD.get())
|
||||
|
||||
def post(self):
|
||||
info = CompanyInfoCRUD.get()
|
||||
if info:
|
||||
abort(400, ErrFormat.company_info_is_already_existed)
|
||||
data = {
|
||||
'info': {
|
||||
**request.values
|
||||
}
|
||||
}
|
||||
info = CompanyInfoCRUD.get()
|
||||
if info:
|
||||
d = CompanyInfoCRUD.update(info.get('id'), **data)
|
||||
else:
|
||||
d = CompanyInfoCRUD.create(**data)
|
||||
d = CompanyInfoCRUD.create(**data)
|
||||
res = d.to_dict()
|
||||
return self.jsonify(res)
|
||||
|
||||
|
@@ -6,9 +6,7 @@ from flask import Blueprint
|
||||
from flask_restful import Api
|
||||
|
||||
from api.resource import register_resources
|
||||
from .account import AuthWithKeyView
|
||||
from .account import LoginView
|
||||
from .account import LogoutView
|
||||
from .account import LoginView, LogoutView, AuthWithKeyView
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
@@ -35,7 +35,6 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_REDIS_PASSWORD = ""
|
||||
CACHE_KEY_PREFIX = "CMDB::"
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
@@ -87,10 +86,12 @@ DEFAULT_PAGE_COUNT = 50
|
||||
|
||||
# # permission
|
||||
WHITE_LIST = ["127.0.0.1"]
|
||||
USE_ACL = True
|
||||
USE_ACL = False
|
||||
|
||||
# # elastic search
|
||||
ES_HOST = '127.0.0.1'
|
||||
USE_ES = False
|
||||
|
||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
|
||||
|
||||
CMDB_API = "http://127.0.0.1:5000/api/v0.1"
|
||||
|
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""provide some sample data in database"""
|
||||
import random
|
||||
import uuid
|
||||
import random
|
||||
|
||||
|
||||
from api.lib.cmdb.ci import CIManager, CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.models.acl import User
|
||||
from api.models.cmdb import (
|
||||
Attribute,
|
||||
CIType,
|
||||
@@ -14,12 +12,16 @@ from api.models.cmdb import (
|
||||
CITypeRelation,
|
||||
RelationType
|
||||
)
|
||||
from api.models.acl import User
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci import CIManager, CIRelationManager
|
||||
|
||||
|
||||
def force_add_user():
|
||||
from flask_login import current_user, login_user
|
||||
if not getattr(current_user, "username", None):
|
||||
login_user(User.query.first())
|
||||
from flask import g
|
||||
if not getattr(g, "user", None):
|
||||
g.user = User.query.first()
|
||||
|
||||
|
||||
def init_attributes(num=1):
|
||||
@@ -76,12 +78,12 @@ def init_relation_type(num=1):
|
||||
|
||||
def init_ci_type_relation(num=1):
|
||||
result = []
|
||||
ci_types = init_ci_types(num + 1)
|
||||
ci_types = init_ci_types(num+1)
|
||||
relation_types = init_relation_type(num)
|
||||
for i in range(num):
|
||||
result.append(CITypeRelation.create(
|
||||
parent_id=ci_types[i].id,
|
||||
child_id=ci_types[i + 1].id,
|
||||
child_id=ci_types[i+1].id,
|
||||
relation_type_id=relation_types[i].id
|
||||
))
|
||||
return result
|
||||
|
@@ -53,7 +53,7 @@
|
||||
"vuedraggable": "^2.23.0",
|
||||
"vuex": "^3.1.1",
|
||||
"vxe-table": "3.6.9",
|
||||
"vxe-table-plugin-export-xlsx": "2.0.0",
|
||||
"vxe-table-plugin-export-xlsx": "^3.0.4",
|
||||
"xe-utils": "3",
|
||||
"xlsx": "0.15.0",
|
||||
"xlsx-js-style": "^1.2.0"
|
||||
|
@@ -54,84 +54,6 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-histogram</div>
|
||||
<div class="code-name">&#xe886;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-index</div>
|
||||
<div class="code-name">&#xe883;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-piechart</div>
|
||||
<div class="code-name">&#xe884;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-line</div>
|
||||
<div class="code-name">&#xe885;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cmdb-table</div>
|
||||
<div class="code-name">&#xe882;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-all</div>
|
||||
<div class="code-name">&#xe87f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-reply</div>
|
||||
<div class="code-name">&#xe87e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-information</div>
|
||||
<div class="code-name">&#xe880;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-contact</div>
|
||||
<div class="code-name">&#xe881;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-my-processed</div>
|
||||
<div class="code-name">&#xe87d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">rule_7</div>
|
||||
<div class="code-name">&#xe87c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-my-completed</div>
|
||||
<div class="code-name">&#xe879;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-my-plan</div>
|
||||
<div class="code-name">&#xe87b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">rule_100</div>
|
||||
@@ -3954,9 +3876,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
|
||||
url('iconfont.woff?t=1694508259411') format('woff'),
|
||||
url('iconfont.ttf?t=1694508259411') format('truetype');
|
||||
src: url('iconfont.woff2?t=1688550067963') format('woff2'),
|
||||
url('iconfont.woff?t=1688550067963') format('woff'),
|
||||
url('iconfont.ttf?t=1688550067963') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -3982,123 +3904,6 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont cmdb-bar"></span>
|
||||
<div class="name">
|
||||
cmdb-histogram
|
||||
</div>
|
||||
<div class="code-name">.cmdb-bar
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont cmdb-count"></span>
|
||||
<div class="name">
|
||||
cmdb-index
|
||||
</div>
|
||||
<div class="code-name">.cmdb-count
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont cmdb-pie"></span>
|
||||
<div class="name">
|
||||
cmdb-piechart
|
||||
</div>
|
||||
<div class="code-name">.cmdb-pie
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont cmdb-line"></span>
|
||||
<div class="name">
|
||||
cmdb-line
|
||||
</div>
|
||||
<div class="code-name">.cmdb-line
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont cmdb-table"></span>
|
||||
<div class="name">
|
||||
cmdb-table
|
||||
</div>
|
||||
<div class="code-name">.cmdb-table
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-all"></span>
|
||||
<div class="name">
|
||||
itsm-all
|
||||
</div>
|
||||
<div class="code-name">.itsm-all
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-reply"></span>
|
||||
<div class="name">
|
||||
itsm-reply
|
||||
</div>
|
||||
<div class="code-name">.itsm-reply
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-information"></span>
|
||||
<div class="name">
|
||||
itsm-information
|
||||
</div>
|
||||
<div class="code-name">.itsm-information
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-contact"></span>
|
||||
<div class="name">
|
||||
itsm-contact
|
||||
</div>
|
||||
<div class="code-name">.itsm-contact
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-my-my_already_handle"></span>
|
||||
<div class="name">
|
||||
itsm-my-processed
|
||||
</div>
|
||||
<div class="code-name">.itsm-my-my_already_handle
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont rule_7"></span>
|
||||
<div class="name">
|
||||
rule_7
|
||||
</div>
|
||||
<div class="code-name">.rule_7
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-my-completed"></span>
|
||||
<div class="name">
|
||||
itsm-my-completed
|
||||
</div>
|
||||
<div class="code-name">.itsm-my-completed
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-my-plan"></span>
|
||||
<div class="name">
|
||||
itsm-my-plan
|
||||
</div>
|
||||
<div class="code-name">.itsm-my-plan
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont rule_100"></span>
|
||||
<div class="name">
|
||||
@@ -5954,11 +5759,11 @@
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-node-start"></span>
|
||||
<span class="icon iconfont itsm-node-strat"></span>
|
||||
<div class="name">
|
||||
itsm-node-strat
|
||||
</div>
|
||||
<div class="code-name">.itsm-node-start
|
||||
<div class="code-name">.itsm-node-strat
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -9832,110 +9637,6 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#cmdb-bar"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-histogram</div>
|
||||
<div class="code-name">#cmdb-bar</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#cmdb-count"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-index</div>
|
||||
<div class="code-name">#cmdb-count</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#cmdb-pie"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-piechart</div>
|
||||
<div class="code-name">#cmdb-pie</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#cmdb-line"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-line</div>
|
||||
<div class="code-name">#cmdb-line</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#cmdb-table"></use>
|
||||
</svg>
|
||||
<div class="name">cmdb-table</div>
|
||||
<div class="code-name">#cmdb-table</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-all"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-all</div>
|
||||
<div class="code-name">#itsm-all</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-reply"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-reply</div>
|
||||
<div class="code-name">#itsm-reply</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-information"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-information</div>
|
||||
<div class="code-name">#itsm-information</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-contact"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-contact</div>
|
||||
<div class="code-name">#itsm-contact</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-my-my_already_handle"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-my-processed</div>
|
||||
<div class="code-name">#itsm-my-my_already_handle</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#rule_7"></use>
|
||||
</svg>
|
||||
<div class="name">rule_7</div>
|
||||
<div class="code-name">#rule_7</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-my-completed"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-my-completed</div>
|
||||
<div class="code-name">#itsm-my-completed</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-my-plan"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-my-plan</div>
|
||||
<div class="code-name">#itsm-my-plan</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#rule_100"></use>
|
||||
@@ -11586,10 +11287,10 @@
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-node-start"></use>
|
||||
<use xlink:href="#itsm-node-strat"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-node-strat</div>
|
||||
<div class="code-name">#itsm-node-start</div>
|
||||
<div class="code-name">#itsm-node-strat</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
|
||||
url('iconfont.woff?t=1694508259411') format('woff'),
|
||||
url('iconfont.ttf?t=1694508259411') format('truetype');
|
||||
src: url('iconfont.woff2?t=1688550067963') format('woff2'),
|
||||
url('iconfont.woff?t=1688550067963') format('woff'),
|
||||
url('iconfont.ttf?t=1688550067963') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,58 +13,6 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.cmdb-bar:before {
|
||||
content: "\e886";
|
||||
}
|
||||
|
||||
.cmdb-count:before {
|
||||
content: "\e883";
|
||||
}
|
||||
|
||||
.cmdb-pie:before {
|
||||
content: "\e884";
|
||||
}
|
||||
|
||||
.cmdb-line:before {
|
||||
content: "\e885";
|
||||
}
|
||||
|
||||
.cmdb-table:before {
|
||||
content: "\e882";
|
||||
}
|
||||
|
||||
.itsm-all:before {
|
||||
content: "\e87f";
|
||||
}
|
||||
|
||||
.itsm-reply:before {
|
||||
content: "\e87e";
|
||||
}
|
||||
|
||||
.itsm-information:before {
|
||||
content: "\e880";
|
||||
}
|
||||
|
||||
.itsm-contact:before {
|
||||
content: "\e881";
|
||||
}
|
||||
|
||||
.itsm-my-my_already_handle:before {
|
||||
content: "\e87d";
|
||||
}
|
||||
|
||||
.rule_7:before {
|
||||
content: "\e87c";
|
||||
}
|
||||
|
||||
.itsm-my-completed:before {
|
||||
content: "\e879";
|
||||
}
|
||||
|
||||
.itsm-my-plan:before {
|
||||
content: "\e87b";
|
||||
}
|
||||
|
||||
.rule_100:before {
|
||||
content: "\e87a";
|
||||
}
|
||||
@@ -889,7 +837,7 @@
|
||||
content: "\e7ad";
|
||||
}
|
||||
|
||||
.itsm-node-start:before {
|
||||
.itsm-node-strat:before {
|
||||
content: "\e7ae";
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,97 +5,6 @@
|
||||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "37334642",
|
||||
"name": "cmdb-histogram",
|
||||
"font_class": "cmdb-bar",
|
||||
"unicode": "e886",
|
||||
"unicode_decimal": 59526
|
||||
},
|
||||
{
|
||||
"icon_id": "37334651",
|
||||
"name": "cmdb-index",
|
||||
"font_class": "cmdb-count",
|
||||
"unicode": "e883",
|
||||
"unicode_decimal": 59523
|
||||
},
|
||||
{
|
||||
"icon_id": "37334650",
|
||||
"name": "cmdb-piechart",
|
||||
"font_class": "cmdb-pie",
|
||||
"unicode": "e884",
|
||||
"unicode_decimal": 59524
|
||||
},
|
||||
{
|
||||
"icon_id": "37334648",
|
||||
"name": "cmdb-line",
|
||||
"font_class": "cmdb-line",
|
||||
"unicode": "e885",
|
||||
"unicode_decimal": 59525
|
||||
},
|
||||
{
|
||||
"icon_id": "37334627",
|
||||
"name": "cmdb-table",
|
||||
"font_class": "cmdb-table",
|
||||
"unicode": "e882",
|
||||
"unicode_decimal": 59522
|
||||
},
|
||||
{
|
||||
"icon_id": "37310392",
|
||||
"name": "itsm-all",
|
||||
"font_class": "itsm-all",
|
||||
"unicode": "e87f",
|
||||
"unicode_decimal": 59519
|
||||
},
|
||||
{
|
||||
"icon_id": "36998696",
|
||||
"name": "itsm-reply",
|
||||
"font_class": "itsm-reply",
|
||||
"unicode": "e87e",
|
||||
"unicode_decimal": 59518
|
||||
},
|
||||
{
|
||||
"icon_id": "36639018",
|
||||
"name": "itsm-information",
|
||||
"font_class": "itsm-information",
|
||||
"unicode": "e880",
|
||||
"unicode_decimal": 59520
|
||||
},
|
||||
{
|
||||
"icon_id": "36639017",
|
||||
"name": "itsm-contact",
|
||||
"font_class": "itsm-contact",
|
||||
"unicode": "e881",
|
||||
"unicode_decimal": 59521
|
||||
},
|
||||
{
|
||||
"icon_id": "36557425",
|
||||
"name": "itsm-my-processed",
|
||||
"font_class": "itsm-my-my_already_handle",
|
||||
"unicode": "e87d",
|
||||
"unicode_decimal": 59517
|
||||
},
|
||||
{
|
||||
"icon_id": "36488174",
|
||||
"name": "rule_7",
|
||||
"font_class": "rule_7",
|
||||
"unicode": "e87c",
|
||||
"unicode_decimal": 59516
|
||||
},
|
||||
{
|
||||
"icon_id": "36380087",
|
||||
"name": "itsm-my-completed",
|
||||
"font_class": "itsm-my-completed",
|
||||
"unicode": "e879",
|
||||
"unicode_decimal": 59513
|
||||
},
|
||||
{
|
||||
"icon_id": "36380096",
|
||||
"name": "itsm-my-plan",
|
||||
"font_class": "itsm-my-plan",
|
||||
"unicode": "e87b",
|
||||
"unicode_decimal": 59515
|
||||
},
|
||||
{
|
||||
"icon_id": "36304673",
|
||||
"name": "rule_100",
|
||||
@@ -1541,7 +1450,7 @@
|
||||
{
|
||||
"icon_id": "35024980",
|
||||
"name": "itsm-node-strat",
|
||||
"font_class": "itsm-node-start",
|
||||
"font_class": "itsm-node-strat",
|
||||
"unicode": "e7ae",
|
||||
"unicode_decimal": 59310
|
||||
},
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -20,7 +20,13 @@ export function putCompanyInfo(id, parameter) {
|
||||
data: parameter,
|
||||
})
|
||||
}
|
||||
|
||||
export function postImageFile(parameter) {
|
||||
return axios({
|
||||
url: '/common-setting/v1/file',
|
||||
method: 'post',
|
||||
data: parameter,
|
||||
})
|
||||
}
|
||||
export function getDepartmentList(params) {
|
||||
// ?department_parent_id=-1 查询第一级部门,下面的id根据实际的传
|
||||
return axios({
|
||||
|
@@ -1,31 +0,0 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function postImageFile(parameter) {
|
||||
return axios({
|
||||
url: '/common-setting/v1/file',
|
||||
method: 'post',
|
||||
data: parameter,
|
||||
})
|
||||
}
|
||||
|
||||
export function getFileData(data_type) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/data/${data_type}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addFileData(data_type, data) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/data/${data_type}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFileData(data_type, id) {
|
||||
return axios({
|
||||
url: `/common-setting/v1/data/${data_type}/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
@@ -68,8 +68,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
visibleChange(open) {
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
@@ -152,20 +151,15 @@ export default {
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
this.canSearchPreferenceAttrList && this.canSearchPreferenceAttrList.length
|
||||
? this.canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
|
@@ -15,117 +15,31 @@
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div :class="`${currentIconType === '4' ? 'selected' : ''}`" @click="handleChangeIconType('4')">
|
||||
自定义
|
||||
</div>
|
||||
<a-upload
|
||||
slot="description"
|
||||
name="avatar"
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
accept=".svg,.png,.jpg,.jpeg"
|
||||
v-if="currentIconType === '4'"
|
||||
>
|
||||
<a-button icon="plus" size="small" type="primary">添加</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
<div class="custom-icon-select-popover-content">
|
||||
<template v-if="iconList && iconList.length">
|
||||
<template v-if="currentIconType !== '4'">
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<div
|
||||
v-for="name in category.list"
|
||||
:key="name.value"
|
||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
||||
@click="clickIcon(name.value)"
|
||||
>
|
||||
<ops-icon :type="name.value" />
|
||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="custom-icon-select-popover-content-wrapper" :style="{ marginTop: '10px' }" v-else>
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<div
|
||||
v-for="icon in iconList"
|
||||
:key="icon.id"
|
||||
:class="`custom-icon-select-popover-item ${value.id === icon.id ? 'selected' : ''}`"
|
||||
@click="clickCustomIcon(icon)"
|
||||
v-for="name in category.list"
|
||||
:key="name.value"
|
||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
||||
@click="clickIcon(name.value)"
|
||||
>
|
||||
<div class="custom-icon-select-popover-content-img-box">
|
||||
<img :src="`/api/common-setting/v1/file/${icon.data.url}`" />
|
||||
<a-popconfirm
|
||||
overlayClassName="custom-icon-select-confirm-popover"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
title="确认删除?"
|
||||
@confirm="(e) => deleteIcon(e, icon)"
|
||||
@cancel="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
"
|
||||
>
|
||||
<a-icon
|
||||
type="close"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
<span class="custom-icon-select-popover-item-label" :title="icon.data.name">{{ icon.data.name }}</span>
|
||||
<ops-icon :type="name.value" />
|
||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty v-else :style="{ marginTop: '15%' }">
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<a-upload
|
||||
slot="description"
|
||||
name="avatar"
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
accept=".svg,.png,.jpg,.jpeg"
|
||||
>
|
||||
<a> 暂无自定义图标,点击此处上传 </a>
|
||||
</a-upload>
|
||||
</a-empty>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!['0', '3', '4'].includes(currentIconType)">
|
||||
<template v-if="currentIconType !== '0' && currentIconType !== '3'">
|
||||
<a-divider :style="{ margin: '5px 0' }" />
|
||||
<el-color-picker size="mini" v-model="value.color"> </el-color-picker>
|
||||
</template>
|
||||
<a-form class="custom-icon-select-form" :form="form" v-show="currentIconType === '4' && formVisible">
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:labelCol="{ span: 4 }"
|
||||
:wrapperCol="{ span: 16 }"
|
||||
><a-input
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入名称' }] }]"
|
||||
/></a-form-item>
|
||||
<a-form-item label="预览" :labelCol="{ span: 4 }">
|
||||
<div class="custom-icon-select-form-img">
|
||||
<img :src="formImg" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :labelCol="{ span: 16 }">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleCancel">取消</a-button>
|
||||
<a-button size="small" type="primary" @click="handleOk">确定</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect">
|
||||
<img v-if="value.id && value.url" :src="`/api/common-setting/v1/file/${value.url}`" />
|
||||
<ops-icon
|
||||
v-else
|
||||
:type="value.name"
|
||||
:style="{ color: value.name && value.name.startsWith('icon-') ? value.color || '' : '' }"
|
||||
/>
|
||||
@@ -142,8 +56,6 @@ import {
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
} from './constants'
|
||||
import { postImageFile, getFileData, addFileData, deleteFileData } from '@/api/file'
|
||||
|
||||
export default {
|
||||
name: 'CustomIconSelect',
|
||||
components: { ElColorPicker: ColorPicker },
|
||||
@@ -165,18 +77,13 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
iconTypeList,
|
||||
commonIconList,
|
||||
linearIconList,
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
visible: false,
|
||||
currentIconType: '3',
|
||||
customIconList: [],
|
||||
formVisible: false,
|
||||
formImg: null,
|
||||
file: null,
|
||||
currentIconType: '1',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -190,30 +97,18 @@ export default {
|
||||
return this.fillIconList
|
||||
case '3': // 多色
|
||||
return this.multicolorIconList
|
||||
case '4': // 自定义
|
||||
return this.customIconList
|
||||
default:
|
||||
return this.linearIconList
|
||||
}
|
||||
},
|
||||
fileName() {
|
||||
const splitFileName = this.file.name.split('.')
|
||||
return splitFileName.splice(0, splitFileName.length - 1).join('')
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.eventListener)
|
||||
this.getFileData()
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.eventListener)
|
||||
},
|
||||
methods: {
|
||||
getFileData() {
|
||||
getFileData('ops-custom-icon').then((res) => {
|
||||
this.customIconList = res
|
||||
})
|
||||
},
|
||||
eventListener(e) {
|
||||
if (this.visible) {
|
||||
const dom = document.getElementById(`custom-icon-select-popover`)
|
||||
@@ -242,87 +137,25 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
clickCustomIcon(icon) {
|
||||
if (icon.id === this.value.id) {
|
||||
this.$emit('change', {
|
||||
name: '',
|
||||
color: '',
|
||||
})
|
||||
} else {
|
||||
this.$emit('change', { name: icon.data.name, id: icon.id, url: icon.data.url })
|
||||
}
|
||||
},
|
||||
showSelect() {
|
||||
this.visible = true
|
||||
console.log(this.value)
|
||||
if (!this.value.name) {
|
||||
this.currentIconType = '3'
|
||||
this.currentIconType = '1'
|
||||
return
|
||||
}
|
||||
// changyong已废弃
|
||||
if (this.value.name.startsWith('changyong-')) {
|
||||
this.currentIconType = '0'
|
||||
} else if (this.value.name.startsWith('icon-xianxing')) {
|
||||
this.currentIconType = '1'
|
||||
} else if (this.value.name.startsWith('icon-shidi')) {
|
||||
this.currentIconType = '2'
|
||||
} else if (this.value.name.startsWith('caise')) {
|
||||
this.currentIconType = '3'
|
||||
} else {
|
||||
this.currentIconType = '4'
|
||||
this.currentIconType = '3'
|
||||
}
|
||||
},
|
||||
handleChangeIconType(value) {
|
||||
this.currentIconType = value
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
if (!isLt2M) {
|
||||
this.$message.error('图片大小不可超过2MB!')
|
||||
return false
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
this.formVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.file = file
|
||||
this.formImg = reader.result
|
||||
this.form.setFieldsValue({ name: this.fileName })
|
||||
})
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleCancel() {
|
||||
this.formVisible = false
|
||||
this.form.setFieldsValue({ name: '' })
|
||||
this.formImg = null
|
||||
},
|
||||
handleOk() {
|
||||
const fm = new FormData()
|
||||
fm.append('file', this.file)
|
||||
postImageFile(fm).then((res) => {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
addFileData('ops-custom-icon', { data: { name: values.name, url: res.file_name } }).then(() => {
|
||||
this.$message.success('上传成功!')
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
deleteIcon(e, icon) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
deleteFileData('ops-custom-icon', icon.id).then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -343,7 +176,7 @@ export default {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.custom-icon-select-popover-content {
|
||||
height: 400px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
.category {
|
||||
font-size: 14px;
|
||||
@@ -364,43 +197,12 @@ export default {
|
||||
padding: 5px 5px 2px 5px;
|
||||
margin: 0 2px 6px;
|
||||
color: #666;
|
||||
position: relative;
|
||||
.custom-icon-select-popover-item-label {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #eeeeee;
|
||||
.custom-icon-select-popover-content-img-box > i {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.custom-icon-select-popover-content-img-box {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
|
||||
> i {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #2f54eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
@@ -410,8 +212,6 @@ export default {
|
||||
}
|
||||
.custom-icon-select-popover-icon-type {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
> div {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -424,16 +224,6 @@ export default {
|
||||
.selected {
|
||||
border-color: #2f54eb;
|
||||
}
|
||||
.ant-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-icon-select-confirm-popover .ant-popover-inner-content {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -444,39 +234,15 @@ export default {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border: 1px solid #eeeeee;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
> i,
|
||||
> img {
|
||||
> i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
> img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
> i {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.custom-icon-select-form {
|
||||
.custom-icon-select-form-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d9d9d9;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -222,9 +222,6 @@ export default {
|
||||
renderIcon({ icon, selectedIcon, customIcon = undefined, name = undefined, typeId = undefined, routeName }) {
|
||||
if (typeId) {
|
||||
if (customIcon) {
|
||||
if (customIcon.split('$$')[2]) {
|
||||
return <img style={{ maxHeight: '14px', maxWidth: '14px', marginRight: '10px' }} src={`/api/common-setting/v1/file/${customIcon.split('$$')[3]}`}></img >
|
||||
}
|
||||
return <ops-icon
|
||||
style={{
|
||||
color: customIcon.split('$$')[1],
|
||||
|
@@ -77,14 +77,6 @@ export function getCITypeAttributesByTypeIds(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeCommonAttributesByTypeIds(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/common_attributes`,
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除属性
|
||||
* @param attrId
|
||||
@@ -161,10 +153,3 @@ export function canDefineComputed() {
|
||||
method: 'HEAD',
|
||||
})
|
||||
}
|
||||
|
||||
export function calcComputedAttribute(attr_id) {
|
||||
return axios({
|
||||
url: `/v0.1/attributes/${attr_id}/calc_computed_attribute`,
|
||||
method: 'PUT',
|
||||
})
|
||||
}
|
||||
|
@@ -61,10 +61,3 @@ export function revokeTypeRelation(first_type_id, second_type_id, rid, data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getRecursive_level2children(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${type_id}/recursive_level2children`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
@@ -37,11 +37,3 @@ export function batchUpdateCustomDashboard(data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function postCustomDashboardPreview(data) {
|
||||
return axios({
|
||||
url: '/v0.1/custom_dashboard/preview',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable */
|
||||
import _ from 'lodash'
|
||||
import XLSX from 'xlsx'
|
||||
import XLSXS from 'xlsx-js-style'
|
||||
export function sum(arr) {
|
||||
if (!arr.length) {
|
||||
return 0
|
||||
@@ -151,26 +149,4 @@ export const toThousands = (num = 0) => {
|
||||
return num.toString().replace(/\d+/, function (n) {
|
||||
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
|
||||
})
|
||||
}
|
||||
|
||||
export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH:mm:ss')}.xls`) => {
|
||||
// STEP 1: Create a new workbook
|
||||
const wb = XLSXS.utils.book_new()
|
||||
// STEP 2: Create data rows and styles
|
||||
const rowArray = data
|
||||
// STEP 3: Create worksheet with rows; Add worksheet to workbook
|
||||
const ws = XLSXS.utils.aoa_to_sheet(rowArray)
|
||||
XLSXS.utils.book_append_sheet(wb, ws, fileName)
|
||||
|
||||
let maxColumnNumber = 1 // 默认最大列数
|
||||
rowArray.forEach(item => { if (item.length > maxColumnNumber) { maxColumnNumber = item.length } })
|
||||
|
||||
// 添加列宽
|
||||
ws['!cols'] = (rowArray[0].map(item => {
|
||||
return { width: 22 }
|
||||
}))
|
||||
// // 添加行高
|
||||
// ws['!rows'] = [{ 'hpt': 80 }]
|
||||
// STEP 4: Write Excel file to browser #导出
|
||||
XLSXS.writeFile(wb, fileName + '.xlsx')
|
||||
}
|
||||
}
|
@@ -14,83 +14,20 @@
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
@click="openModal"
|
||||
@click="downLoadExcel"
|
||||
:disabled="!selectNum"
|
||||
type="primary"
|
||||
class="ops-button-primary"
|
||||
icon="download"
|
||||
>下载模板</a-button
|
||||
>
|
||||
<a-modal
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
width="800px"
|
||||
:title="`${ciTypeName}`"
|
||||
:visible="visible"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
wrapClassName="ci-type-choice-modal"
|
||||
>
|
||||
<a-divider orientation="left">模型属性</a-divider>
|
||||
<a-checkbox
|
||||
@change="changeCheckAll"
|
||||
:style="{ marginBottom: '20px' }"
|
||||
:indeterminate="indeterminate"
|
||||
:checked="checkAll"
|
||||
>
|
||||
全选
|
||||
</a-checkbox>
|
||||
<br />
|
||||
<a-checkbox-group v-model="checkedAttrs">
|
||||
<a-row>
|
||||
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
|
||||
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
|
||||
{{ item.alias || item.name }}
|
||||
<span style="color: red" v-if="item.name === selectCiTypeAttrList.unique">*</span>
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider orientation="left">模型关联</a-divider>
|
||||
<a-row :gutter="[24, 24]" align="top" type="flex">
|
||||
<a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id">
|
||||
<a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)">
|
||||
</a-checkbox>
|
||||
<span
|
||||
:style="{
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
width: '80px',
|
||||
margin: '0 5px',
|
||||
textAlign: 'right',
|
||||
}"
|
||||
:title="item.alias || item.name"
|
||||
>{{ item.alias || item.name }}</span
|
||||
>
|
||||
<a-select :style="{ flex: 1 }" size="small" v-model="parentsForm[item.alias || item.name].attr">
|
||||
<a-select-option
|
||||
:title="attr.alias || attr.name"
|
||||
v-for="attr in item.attributes"
|
||||
:key="attr.alias || attr.name"
|
||||
:value="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-modal>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { downloadExcel } from '../../../utils/helper'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { writeExcel } from '@/modules/cmdb/api/batch'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeChoice',
|
||||
@@ -100,13 +37,6 @@ export default {
|
||||
ciTypeName: '',
|
||||
selectNum: 0,
|
||||
selectCiTypeAttrList: [],
|
||||
visible: false,
|
||||
checkedAttrs: [],
|
||||
indeterminate: false,
|
||||
checkAll: true,
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
checkedParents: [],
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
@@ -114,18 +44,6 @@ export default {
|
||||
this.ciTypeList = res.ci_types
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
checkedAttrs() {
|
||||
if (this.checkedAttrs.length < this.selectCiTypeAttrList.attributes.length) {
|
||||
this.indeterminate = true
|
||||
this.checkAll = false
|
||||
}
|
||||
if (this.checkedAttrs.length === this.selectCiTypeAttrList.attributes.length) {
|
||||
this.indeterminate = false
|
||||
this.checkAll = true
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectCiType(el) {
|
||||
// 当选择好模板类型时的回调函数
|
||||
@@ -142,70 +60,24 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
openModal() {
|
||||
getCITypeParent(this.selectNum).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
const _parentsForm = {}
|
||||
res.parents.forEach((item) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
_parentsForm[item.alias || item.name] = { attr: _find?.alias || _find?.name, value: '' }
|
||||
})
|
||||
this.parentsForm = _parentsForm
|
||||
this.checkedParents = []
|
||||
this.visible = true
|
||||
this.checkedAttrs = this.selectCiTypeAttrList.attributes.map((item) => item.alias || item.name)
|
||||
downLoadExcel() {
|
||||
const columns = []
|
||||
this.selectCiTypeAttrList.attributes.forEach((item) => {
|
||||
columns.push(item.alias)
|
||||
})
|
||||
const excel = writeExcel(columns, this.ciTypeName)
|
||||
const tempLink = document.createElement('a')
|
||||
tempLink.download = this.ciTypeName + '.xls'
|
||||
tempLink.style.display = 'none'
|
||||
const blob = new Blob([excel])
|
||||
tempLink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
document.body.removeChild(tempLink)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
handleOk() {
|
||||
const columns1 = this.checkedAttrs.map((item) => {
|
||||
return {
|
||||
v: item,
|
||||
t: 's',
|
||||
s: {
|
||||
numFmt: 'string',
|
||||
},
|
||||
}
|
||||
})
|
||||
const columns2 = this.checkedParents.map((p) => {
|
||||
return {
|
||||
v: `$${p}.${this.parentsForm[p].attr}`,
|
||||
t: 's',
|
||||
s: {
|
||||
font: {
|
||||
color: {
|
||||
rgb: 'FF0000',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
downloadExcel([[...columns1, ...columns2]], this.ciTypeName)
|
||||
this.handleCancel()
|
||||
},
|
||||
changeCheckAll(e) {
|
||||
if (e.target.checked) {
|
||||
this.checkedAttrs = this.selectCiTypeAttrList.attributes.map((item) => item.alias || item.name)
|
||||
} else {
|
||||
const _find = this.selectCiTypeAttrList.attributes.find(
|
||||
(item) => item.name === this.selectCiTypeAttrList.unique
|
||||
)
|
||||
this.checkedAttrs = [_find?.alias || _find?.name]
|
||||
}
|
||||
},
|
||||
clickParent(item) {
|
||||
const _idx = this.checkedParents.findIndex((p) => p === (item.alias || item.name))
|
||||
if (_idx > -1) {
|
||||
this.checkedParents.splice(_idx, 1)
|
||||
} else {
|
||||
this.checkedParents.push(item.alias || item.name)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -233,15 +105,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ci-type-choice-modal {
|
||||
.ant-checkbox-disabled .ant-checkbox-inner {
|
||||
border-color: #2f54eb !important;
|
||||
background-color: #2f54eb;
|
||||
}
|
||||
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner::after {
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -40,25 +40,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
const _columns = []
|
||||
if (this.ciTypeAttrs.attributes) {
|
||||
_columns.push(
|
||||
...this.ciTypeAttrs.attributes.map((item) => {
|
||||
return {
|
||||
title: item.alias || item.name,
|
||||
field: item.alias || item.name,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (this.uploadData && this.uploadData.length) {
|
||||
Object.keys(this.uploadData[0]).forEach((key) => {
|
||||
if (key.startsWith('$')) {
|
||||
_columns.push({ title: key, field: key })
|
||||
return this.ciTypeAttrs.attributes.map((item) => {
|
||||
return {
|
||||
title: item.alias || item.name,
|
||||
field: item.alias || item.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
return _columns
|
||||
return []
|
||||
},
|
||||
dataSource() {
|
||||
return _.cloneDeep(this.uploadData)
|
||||
|
@@ -4,13 +4,13 @@
|
||||
ref="upload"
|
||||
:multiple="false"
|
||||
:customRequest="customRequest"
|
||||
accept=".xls,.xlsx"
|
||||
accept=".xls"
|
||||
:showUploadList="false"
|
||||
:fileList="fileList"
|
||||
>
|
||||
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
||||
<p class="ant-upload-text">点击或拖拽文件至此上传!</p>
|
||||
<p class="ant-upload-hint">支持文件类型:xls,xlsx</p>
|
||||
<p class="ant-upload-hint">支持文件类型:xls</p>
|
||||
</a-upload-dragger>
|
||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
||||
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||
|
@@ -124,21 +124,12 @@
|
||||
:key="'edit_' + col.field + idx"
|
||||
v-for="(choice, idx) in col.filters"
|
||||
>
|
||||
<span
|
||||
:style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }"
|
||||
>
|
||||
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
|
||||
<img
|
||||
v-if="choice[1].icon.id && choice[1].icon.url"
|
||||
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: choice[1].icon.color, marginRight: '5px' }"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
<span :style="choice[1] ? choice[1].style || {} : {}">
|
||||
<ops-icon
|
||||
:style="{ color: choice[1].icon.color }"
|
||||
v-if="choice[1] && choice[1].icon && choice[1].icon.name"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
{{ choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
@@ -161,18 +152,10 @@
|
||||
padding: '1px 5px',
|
||||
margin: '2px',
|
||||
...getChoiceValueStyle(col, value),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, value).id && getChoiceValueIcon(col, value).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, value).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
|
||||
:style="{ color: getChoiceValueIcon(col, value).color }"
|
||||
:type="getChoiceValueIcon(col, value).name"
|
||||
/>{{ value }}
|
||||
</span>
|
||||
@@ -184,18 +167,10 @@
|
||||
padding: '1px 5px',
|
||||
margin: '2px 0',
|
||||
...getChoiceValueStyle(col, row[col.field]),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="getChoiceValueIcon(col, row[col.field]).id && getChoiceValueIcon(col, row[col.field]).url"
|
||||
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(col, row[col.field]).url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color, marginRight: '5px' }"
|
||||
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }"
|
||||
:type="getChoiceValueIcon(col, row[col.field]).name"
|
||||
/>
|
||||
{{ row[col.field] }}</span
|
||||
@@ -209,19 +184,13 @@
|
||||
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" />
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-tooltip title="添加关系">
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
|
||||
<a-icon type="retweet" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a @click="deleteCI(row)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-space>
|
||||
<a @click="$refs.detail.create(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="deleteCI(row)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
|
@@ -38,7 +38,7 @@
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />关系</span>
|
||||
<div :style="{ padding: '24px' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
<CiDetailRelation :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
@@ -147,14 +147,8 @@ export default {
|
||||
},
|
||||
inject: ['reload', 'handleSearch', 'attrList'],
|
||||
methods: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
create(ciId) {
|
||||
this.visible = true
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
this.getAttributes()
|
||||
this.getCI()
|
||||
|
@@ -23,30 +23,6 @@
|
||||
:attributeList="attributeList"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">模型关系</a-divider>
|
||||
<a-form>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col :span="12" v-for="item in parentsType" :key="item.id">
|
||||
<a-form-item :label="item.alias || item.name" :colon="false">
|
||||
<a-input-group compact style="width: 100%">
|
||||
<a-select v-model="parentsForm[item.name].attr">
|
||||
<a-select-option
|
||||
:title="attr.alias || attr.name"
|
||||
v-for="attr in item.attributes"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input placeholder="多个值使用,分割" v-model="parentsForm[item.name].value" style="width: 50%" />
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="action === 'update'">
|
||||
<a-form :form="form">
|
||||
@@ -134,7 +110,6 @@ import { addCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
@@ -163,8 +138,6 @@ export default {
|
||||
batchUpdateLists: [],
|
||||
editAttr: null,
|
||||
attributesByGroup: [],
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -258,11 +231,6 @@ export default {
|
||||
}
|
||||
})
|
||||
values.ci_type = _this.typeId
|
||||
Object.keys(this.parentsForm).forEach((type) => {
|
||||
if (this.parentsForm[type].value) {
|
||||
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
|
||||
}
|
||||
})
|
||||
addCI(values).then((res) => {
|
||||
_this.$message.success('新增成功!')
|
||||
_this.visible = false
|
||||
@@ -281,17 +249,6 @@ export default {
|
||||
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
|
||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||
})
|
||||
if (action === 'create') {
|
||||
getCITypeParent(this.typeId).then((res) => {
|
||||
this.parentsType = res.parents
|
||||
const _parentsForm = {}
|
||||
res.parents.forEach((item) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
_parentsForm[item.name] = { attr: _find.name, value: '' }
|
||||
})
|
||||
this.parentsForm = _parentsForm
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getFieldType(name) {
|
||||
|
@@ -244,13 +244,14 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.ci)
|
||||
this.init(true)
|
||||
},
|
||||
methods: {
|
||||
async init(isFirst) {
|
||||
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
|
||||
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
|
||||
if (isFirst && this.$refs.ciDetailRelationTopo) {
|
||||
if (isFirst) {
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
}
|
||||
})
|
||||
@@ -394,6 +395,12 @@ export default {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 5px;
|
||||
color: #303133;
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
&:hover > a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -29,14 +29,11 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.icon,
|
||||
img {
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 6px;
|
||||
}
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
@@ -15,11 +15,7 @@ class BaseNode extends TreeNode {
|
||||
.attr('id', opts.id)
|
||||
let icon
|
||||
if (opts.options.icon) {
|
||||
if (opts.options.icon.split('$$')[2]) {
|
||||
icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`)
|
||||
} else {
|
||||
icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`)
|
||||
}
|
||||
icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`)
|
||||
} else {
|
||||
icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-form :form="form">
|
||||
<a-divider style="font-size: 14px; margin: 14px 0; font-weight: 700">{{ group.name || '其他' }}</a-divider>
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ group.name || '其他' }}</a-divider>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col
|
||||
:span="12"
|
||||
@@ -37,19 +37,12 @@
|
||||
:key="'New_' + attr.name + choice_idx"
|
||||
v-for="(choice, choice_idx) in attr.choice_value"
|
||||
>
|
||||
<span :style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }">
|
||||
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
|
||||
<img
|
||||
v-if="choice[1].icon.id && choice[1].icon.url"
|
||||
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
|
||||
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{ color: choice[1].icon.color, marginRight: '5px' }"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
</template>
|
||||
<span :style="choice[1] ? choice[1].style || {} : {}">
|
||||
<ops-icon
|
||||
:style="{ color: choice[1].icon.color }"
|
||||
v-if="choice[1] && choice[1].icon && choice[1].icon.name"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
{{ choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
|
@@ -51,9 +51,6 @@
|
||||
|
||||
<a-space class="attribute-card-operation">
|
||||
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
|
||||
<a-tooltip title="所有CI触发计算">
|
||||
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
|
||||
</a-tooltip>
|
||||
<a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
|
||||
</a-space>
|
||||
</div>
|
||||
@@ -62,7 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { deleteCITypeAttributesById, deleteAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
|
||||
import {
|
||||
ops_default_show,
|
||||
@@ -168,18 +165,6 @@ export default {
|
||||
openTrigger() {
|
||||
this.$refs.triggerForm.open(this.property)
|
||||
},
|
||||
handleCalcComputed() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: `确认触发所有CI的计算?`,
|
||||
onOk() {
|
||||
calcComputedAttribute(that.property.id).then(() => {
|
||||
that.$message.success('触发成功!')
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
:headerStyle="{ borderBottom: 'none' }"
|
||||
wrapClassName="attribute-edit-form"
|
||||
>
|
||||
<a-form :form="form" :layout="formLayout">
|
||||
<a-form :form="form" :layout="formLayout" @submit="handleSubmit">
|
||||
<a-divider style="font-size:14px;margin-top:6px;">基础设置</a-divider>
|
||||
<a-col :span="12">
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="属性名(英文)">
|
||||
@@ -343,13 +343,7 @@
|
||||
name="is_password"
|
||||
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]"
|
||||
/>
|
||||
<ComputedArea
|
||||
showCalcComputed
|
||||
ref="computedArea"
|
||||
v-show="isShowComputedArea"
|
||||
@handleCalcComputed="handleCalcComputed"
|
||||
:canDefineComputed="canDefineComputed"
|
||||
/>
|
||||
<ComputedArea ref="computedArea" v-show="isShowComputedArea" :canDefineComputed="canDefineComputed" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -359,7 +353,7 @@
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit(false)" type="primary">确定</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
@@ -372,7 +366,6 @@ import {
|
||||
updateAttributeById,
|
||||
updateCITypeAttributesById,
|
||||
canDefineComputed,
|
||||
calcComputedAttribute,
|
||||
} from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { valueTypeMap } from '../../utils/const'
|
||||
import ComputedArea from './computedArea.vue'
|
||||
@@ -583,14 +576,15 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
async handleSubmit(isCalcComputed = false) {
|
||||
await this.form.validateFields(async (err, values) => {
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
if (this.record.is_required !== values.is_required || this.record.default_show !== values.default_show) {
|
||||
console.log('changed is_required')
|
||||
await updateCITypeAttributesById(this.CITypeId, {
|
||||
updateCITypeAttributesById(this.CITypeId, {
|
||||
attributes: [
|
||||
{ attr_id: this.record.id, is_required: values.is_required, default_show: values.default_show },
|
||||
],
|
||||
@@ -636,21 +630,19 @@ export default {
|
||||
|
||||
const fontOptions = this.$refs.fontArea.getData()
|
||||
if (values.id) {
|
||||
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
|
||||
this.updateAttribute(values.id, { ...values, option: { fontOptions } })
|
||||
} else {
|
||||
// this.createAttribute(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
async updateAttribute(attrId, data, isCalcComputed = false) {
|
||||
await updateAttributeById(attrId, data)
|
||||
if (isCalcComputed) {
|
||||
await calcComputedAttribute(attrId)
|
||||
}
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
updateAttribute(attrId, data) {
|
||||
updateAttributeById(attrId, data).then((res) => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
},
|
||||
handleOk() {
|
||||
this.$emit('ok')
|
||||
@@ -690,9 +682,6 @@ export default {
|
||||
default_value: key,
|
||||
})
|
||||
},
|
||||
async handleCalcComputed() {
|
||||
await this.handleSubmit(true)
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
|
@@ -8,14 +8,6 @@
|
||||
<span style="font-size:12px;" slot="tab">代码</span>
|
||||
<codemirror style="z-index: 9999" :options="cmOptions" v-model="compute_script"></codemirror>
|
||||
</a-tab-pane>
|
||||
<template slot="tabBarExtraContent" v-if="showCalcComputed">
|
||||
<a-button type="primary" size="small" @click="handleCalcComputed">
|
||||
应用
|
||||
</a-button>
|
||||
<a-tooltip title="所有CI触发计算">
|
||||
<a-icon type="question-circle" style="margin-left:5px" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
@@ -33,10 +25,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCalcComputed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -74,16 +62,6 @@ export default {
|
||||
this.activeKey = 'expr'
|
||||
}
|
||||
},
|
||||
handleCalcComputed() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: `确认触发将保存当前配置及触发所有CI的计算?`,
|
||||
onOk() {
|
||||
that.$emit('handleCalcComputed')
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -113,20 +113,14 @@
|
||||
style="width: 17px; height: 17px; display: none; position: absolute; left: 15px; top: 5px"
|
||||
/>
|
||||
<span class="ci-types-left-detail-icon">
|
||||
<template v-if="ci.icon">
|
||||
<img
|
||||
v-if="ci.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ci.icon.split('$$')[3]}`"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ci.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="ci.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<ops-icon
|
||||
:style="{
|
||||
color: ci.icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
v-if="ci.icon"
|
||||
:type="ci.icon.split('$$')[0]"
|
||||
/>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ci.name[0].toUpperCase() }}</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -210,7 +204,7 @@
|
||||
:label="item.alias || item.name"
|
||||
>
|
||||
<span> {{ item.alias || item.name }}</span>
|
||||
<span :title="item.name" style="font-size:10px;color:#afafaf;"> {{ item.name }}</span>
|
||||
<span :title="item.name" style="font-size: 10px; color: #afafaf"> {{ item.name }}</span>
|
||||
</el-option>
|
||||
<a-divider :style="{ margin: '5px 0' }" />
|
||||
<div :style="{ textAlign: 'right' }">
|
||||
@@ -241,7 +235,7 @@
|
||||
:label="item.alias || item.name"
|
||||
>
|
||||
<span> {{ item.alias || item.name }}</span>
|
||||
<span :title="item.name" style="font-size:10px;color:#afafaf;"> {{ item.name }}</span>
|
||||
<span :title="item.name" style="font-size: 10px; color: #afafaf"> {{ item.name }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<a-divider type="vertical" />
|
||||
@@ -539,20 +533,21 @@ export default {
|
||||
e.preventDefault()
|
||||
this.form.validateFields(async (err, values) => {
|
||||
if (!err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Received values of form: ', values)
|
||||
const icon = this.$refs.iconArea.getIcon()
|
||||
this.loading = true
|
||||
if (values.default_order_attr && this.default_order_asc === '2') {
|
||||
values.default_order_attr = `-${values.default_order_attr}`
|
||||
}
|
||||
const _icon = this.$refs.iconArea.getIcon()
|
||||
const icon =
|
||||
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
|
||||
if (values.id) {
|
||||
await this.updateCIType(values.id, {
|
||||
...values,
|
||||
icon,
|
||||
icon: icon && icon.name ? `${icon.name}$$${icon.color || ''}` : '',
|
||||
})
|
||||
} else {
|
||||
await this.createCIType({ ...values, icon })
|
||||
await this.createCIType({ ...values, icon: icon && icon.name ? `${icon.name}$$${icon.color || ''}` : '' })
|
||||
// todo 把改ci 类型绑定在当前group下
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -736,8 +731,6 @@ export default {
|
||||
? {
|
||||
name: record.icon.split('$$')[0] || '',
|
||||
color: record.icon.split('$$')[1] || '',
|
||||
id: record.icon.split('$$')[2] ? Number(record.icon.split('$$')[2]) : null,
|
||||
url: record.icon.split('$$')[3] || '',
|
||||
}
|
||||
: {}
|
||||
)
|
||||
@@ -835,7 +828,7 @@ export default {
|
||||
margin-left: auto;
|
||||
}
|
||||
.ci-types-left-detail-icon {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
@@ -844,10 +837,6 @@ export default {
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
margin-right: 6px;
|
||||
background-color: #fff;
|
||||
img {
|
||||
max-height: 20px;
|
||||
max-width: 20px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: #e1efff;
|
||||
|
@@ -102,17 +102,8 @@
|
||||
"
|
||||
>
|
||||
<span :style="{ cursor: disabled ? 'default' : 'move' }">
|
||||
<img
|
||||
v-if="icon.id && icon.url"
|
||||
:src="`/api/common-setting/v1/file/${icon.url}`"
|
||||
:style="{ maxHeight: '12px', maxWidth: '12px', marginRight: '5px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="icon.name"
|
||||
:type="icon.name"
|
||||
:style="{ marginRight: '5px', color: icon.color || '#595959' }"
|
||||
/>
|
||||
<span>{{ item[0] }}</span>
|
||||
<ops-icon v-if="icon.name" :type="icon.name" :style="{ color: icon.color || '#595959' }" />
|
||||
{{ item[0] }}
|
||||
</span>
|
||||
<a
|
||||
class="pre-value-tag-dropdown"
|
||||
@@ -249,10 +240,6 @@ export default {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&:hover .pre-value-tag-dropdown-icon {
|
||||
display: inline !important;
|
||||
}
|
||||
|
@@ -18,30 +18,18 @@
|
||||
keep-source
|
||||
:max-height="windowHeight - 180"
|
||||
class="ops-stripe-table"
|
||||
:row-class-name="rowClass"
|
||||
>
|
||||
<vxe-column field="source_ci_type_name" title="源模型英文名"></vxe-column>
|
||||
<vxe-column field="relation_type" title="关联类型">
|
||||
<template #default="{row}">
|
||||
<span style="color:#2f54eb" v-if="row.isParent">被</span>
|
||||
{{ row.relation_type }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="relation_type" title="关联类型"></vxe-column>
|
||||
<vxe-column field="alias" title="目标模型名"></vxe-column>
|
||||
<vxe-column field="constraint" title="关系约束">
|
||||
<template #default="{row}">
|
||||
<span v-if="row.isParent && constraintMap[row.constraint]">{{
|
||||
constraintMap[row.constraint]
|
||||
.split('')
|
||||
.reverse()
|
||||
.join('')
|
||||
}}</span>
|
||||
<span v-else>{{ constraintMap[row.constraint] }}</span>
|
||||
<span>{{ constraintMap[row.constraint] }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operation" title="操作" width="100">
|
||||
<template #default="{row}">
|
||||
<a-space v-if="!row.isParent && row.source_ci_type_id">
|
||||
<a-space>
|
||||
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)">
|
||||
<a style="color: red;"><a-icon type="delete"/></a>
|
||||
@@ -49,12 +37,6 @@
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>暂无数据</div>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<a-modal
|
||||
:closable="false"
|
||||
@@ -113,13 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
createRelation,
|
||||
deleteRelation,
|
||||
getCITypeChildren,
|
||||
getCITypeParent,
|
||||
getRelationTypes,
|
||||
} from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import CMDBGrant from '../../components/cmdbGrant'
|
||||
|
||||
@@ -151,7 +127,6 @@ export default {
|
||||
'2': '多对多',
|
||||
},
|
||||
tableData: [],
|
||||
parentTableData: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -162,25 +137,12 @@ export default {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
mounted() {
|
||||
this.getCITypes()
|
||||
this.getRelationTypes()
|
||||
await this.getCITypeParent()
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
async getCITypeParent() {
|
||||
await getCITypeParent(this.CITypeId).then((res) => {
|
||||
this.parentTableData = res.parents.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
source_ci_type_name: this.CITypeName,
|
||||
source_ci_type_id: this.CITypeId,
|
||||
isParent: true,
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
getData() {
|
||||
getCITypeChildren(this.CITypeId).then((res) => {
|
||||
const data = res.children.map((obj) => {
|
||||
@@ -190,11 +152,7 @@ export default {
|
||||
source_ci_type_id: this.CITypeId,
|
||||
}
|
||||
})
|
||||
if (this.parentTableData && this.parentTableData.length) {
|
||||
this.tableData = [...data, { isDivider: true }, ...this.parentTableData]
|
||||
} else {
|
||||
this.tableData = data
|
||||
}
|
||||
this.tableData = data
|
||||
})
|
||||
},
|
||||
getCITypes() {
|
||||
@@ -259,25 +217,8 @@ export default {
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
rowClass({ row }) {
|
||||
if (row.isDivider) return 'relation-table-divider'
|
||||
if (row.isParent) return 'relation-table-parent'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ops-stripe-table .vxe-body--row.row--stripe.relation-table-divider {
|
||||
background-color: #b1b8d3 !important;
|
||||
}
|
||||
.ops-stripe-table .vxe-body--row.relation-table-parent {
|
||||
background-color: #f5f8ff !important;
|
||||
}
|
||||
.relation-table-divider {
|
||||
td {
|
||||
height: 1px !important;
|
||||
line-height: 1px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,55 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
:id="`cmdb-dashboard-${chartId}-${editable}-${isPreview}`"
|
||||
:style="{ width: '100%', height: 'calc(100% - 2.2vw)' }"
|
||||
>
|
||||
<div
|
||||
v-if="options.chartType === 'count'"
|
||||
:style="{ color: options.fontColor || '#fff' }"
|
||||
class="cmdb-dashboard-grid-item-chart"
|
||||
>
|
||||
<div class="cmdb-dashboard-grid-item-chart-icon" v-if="options.showIcon && ciType">
|
||||
<template v-if="ciType.icon">
|
||||
<img v-if="ciType.icon.split('$$')[2]" :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" />
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
<div :style="{ width: '100%', height: 'calc(100% - 2.2vw)' }">
|
||||
<div v-if="category === 0" class="cmdb-dashboard-grid-item-chart">
|
||||
<span :style="{ ...options.fontConfig }">{{ toThousands(data) }}</span>
|
||||
</div>
|
||||
<vxe-table
|
||||
:max-height="tableHeight"
|
||||
:data="tableData"
|
||||
:stripe="!!options.ret"
|
||||
size="mini"
|
||||
class="ops-stripe-table"
|
||||
v-if="options.chartType === 'table'"
|
||||
:span-method="mergeRowMethod"
|
||||
:border="!options.ret"
|
||||
:show-header="!!options.ret"
|
||||
>
|
||||
<template v-if="options.ret">
|
||||
<vxe-column v-for="col in columns" :key="col" :title="col" :field="col"></vxe-column>
|
||||
</template>
|
||||
<template v-else>
|
||||
<vxe-column
|
||||
v-for="(key, index) in Array(keyLength)"
|
||||
:key="`key${index}`"
|
||||
:title="`key${index}`"
|
||||
:field="`key${index}`"
|
||||
></vxe-column>
|
||||
<vxe-column field="value" title="value"></vxe-column>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<div
|
||||
:id="`cmdb-dashboard-${chartId}-${editable}`"
|
||||
v-else-if="category === 1 || category === 2"
|
||||
v-if="category === 1 || category === 2"
|
||||
class="cmdb-dashboard-grid-item-chart"
|
||||
></div>
|
||||
</div>
|
||||
@@ -59,27 +15,17 @@
|
||||
import * as echarts from 'echarts'
|
||||
import { mixin } from '@/utils/mixin'
|
||||
import { toThousands } from '../../utils/helper'
|
||||
import {
|
||||
category_1_bar_options,
|
||||
category_1_line_options,
|
||||
category_1_pie_options,
|
||||
category_2_bar_options,
|
||||
category_2_pie_options,
|
||||
} from './chartOptions'
|
||||
import { category_1_bar_options, category_1_pie_options, category_2_bar_options } from './chartOptions'
|
||||
export default {
|
||||
name: 'Chart',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
ci_types: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
chartId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
data: {
|
||||
type: [Number, Object, Array],
|
||||
type: [Number, Object],
|
||||
default: 0,
|
||||
},
|
||||
category: {
|
||||
@@ -94,65 +40,20 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type_id: {
|
||||
type: [Number, Array],
|
||||
default: null,
|
||||
},
|
||||
isPreview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
columns: [],
|
||||
tableHeight: '',
|
||||
tableData: [],
|
||||
keyLength: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ciType() {
|
||||
if (this.type_id || this.options?.type_ids) {
|
||||
const _find = this.ci_types.find((item) => item.id === this.type_id || item.id === this.options?.type_ids[0])
|
||||
return _find || null
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(newValue, oldValue) {
|
||||
if (this.category === 1 || this.category === 2) {
|
||||
if (this.options.chartType !== 'table' && Object.prototype.toString.call(newValue) === '[object Object]') {
|
||||
if (this.isPreview) {
|
||||
this.$nextTick(() => {
|
||||
this.setChart()
|
||||
})
|
||||
} else {
|
||||
this.setChart()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.options.chartType === 'table') {
|
||||
this.$nextTick(() => {
|
||||
const dom = document.getElementById(`cmdb-dashboard-${this.chartId}-${this.editable}-${this.isPreview}`)
|
||||
this.tableHeight = dom.offsetHeight
|
||||
})
|
||||
if (this.options.ret) {
|
||||
const excludeKeys = ['_X_ROW_KEY', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias', '_id', '_type']
|
||||
if (newValue && newValue.length) {
|
||||
this.columns = Object.keys(newValue[0]).filter((keys) => !excludeKeys.includes(keys))
|
||||
this.tableData = newValue
|
||||
}
|
||||
} else {
|
||||
const _data = []
|
||||
this.keyLength = this.options?.attr_ids?.length ?? 0
|
||||
this.formatTableData(_data, this.data, {})
|
||||
this.tableData = _data
|
||||
if (Object.prototype.toString.call(newValue) === '[object Object]') {
|
||||
this.setChart()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -180,19 +81,13 @@ export default {
|
||||
this.chart = echarts.init(document.getElementById(`cmdb-dashboard-${this.chartId}-${this.editable}`))
|
||||
}
|
||||
if (this.category === 1 && this.options.chartType === 'bar') {
|
||||
this.chart.setOption(category_1_bar_options(this.data, this.options), true)
|
||||
}
|
||||
if (this.category === 1 && this.options.chartType === 'line') {
|
||||
this.chart.setOption(category_1_line_options(this.data, this.options), true)
|
||||
this.chart.setOption(category_1_bar_options(this.data), true)
|
||||
}
|
||||
if (this.category === 1 && this.options.chartType === 'pie') {
|
||||
this.chart.setOption(category_1_pie_options(this.data, this.options), true)
|
||||
this.chart.setOption(category_1_pie_options(this.data), true)
|
||||
}
|
||||
if (this.category === 2 && ['bar', 'line'].includes(this.options.chartType)) {
|
||||
this.chart.setOption(category_2_bar_options(this.data, this.options, this.options.chartType), true)
|
||||
}
|
||||
if (this.category === 2 && this.options.chartType === 'pie') {
|
||||
this.chart.setOption(category_2_pie_options(this.data, this.options), true)
|
||||
if (this.category === 2) {
|
||||
this.chart.setOption(category_2_bar_options(this.data), true)
|
||||
}
|
||||
},
|
||||
resizeChart() {
|
||||
@@ -202,34 +97,6 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
formatTableData(_data, data, obj) {
|
||||
Object.keys(data).forEach((k) => {
|
||||
if (typeof data[k] === 'number') {
|
||||
_data.push({ ...obj, [`key${Object.keys(obj).length}`]: k, value: data[k] })
|
||||
} else {
|
||||
this.formatTableData(_data, data[k], { ...obj, [`key${Object.keys(obj).length}`]: k })
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['key0', 'key1', 'key2']
|
||||
const cellValue = row[column.field]
|
||||
if (cellValue && fields.includes(column.field)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow[column.field] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.field] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -239,28 +106,14 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
> span {
|
||||
font-size: 50px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cmdb-dashboard-grid-item-chart-icon {
|
||||
> i {
|
||||
font-size: 4vw;
|
||||
}
|
||||
> img {
|
||||
width: 4vw;
|
||||
}
|
||||
> span {
|
||||
display: inline-block;
|
||||
width: 4vw;
|
||||
height: 4vw;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
}
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,307 +1,65 @@
|
||||
<template>
|
||||
<a-modal
|
||||
width="1100px"
|
||||
:title="`${type === 'add' ? '新增' : '编辑'}图表`"
|
||||
:visible="visible"
|
||||
@cancel="handleclose"
|
||||
@ok="handleok"
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
>
|
||||
<div class="chart-wrapper">
|
||||
<div class="chart-left">
|
||||
<a-form-model ref="chartForm" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-model-item label="标题" prop="name">
|
||||
<a-input v-model="form.name" placeholder="请输入图表标题"></a-input>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="类型" prop="category" v-if="chartType !== 'count' && chartType !== 'table'">
|
||||
<a-radio-group
|
||||
@change="
|
||||
() => {
|
||||
resetForm()
|
||||
}
|
||||
"
|
||||
:default-value="1"
|
||||
v-model="form.category"
|
||||
>
|
||||
<a-radio-button :value="Number(key)" :key="key" v-for="key in Object.keys(dashboardCategory)">
|
||||
{{ dashboardCategory[key].label }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="类型" prop="tableCategory" v-if="chartType === 'table'">
|
||||
<a-radio-group
|
||||
@change="
|
||||
() => {
|
||||
resetForm()
|
||||
}
|
||||
"
|
||||
:default-value="1"
|
||||
v-model="form.tableCategory"
|
||||
>
|
||||
<a-radio-button :value="1">
|
||||
计算指标
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="2">
|
||||
资源数据
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item
|
||||
v-if="(chartType !== 'table' && form.category !== 2) || (chartType === 'table' && form.tableCategory === 1)"
|
||||
label="模型"
|
||||
prop="type_ids"
|
||||
>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="children"
|
||||
@change="changeCIType"
|
||||
v-model="form.type_ids"
|
||||
placeholder="请选择模型"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{
|
||||
ci_type.alias || ci_type.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-else label="模型" prop="type_id">
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="children"
|
||||
@change="changeCIType"
|
||||
v-model="form.type_id"
|
||||
placeholder="请选择模型"
|
||||
>
|
||||
<a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{
|
||||
ci_type.alias || ci_type.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item
|
||||
label="维度"
|
||||
prop="attr_ids"
|
||||
v-if="(['bar', 'line', 'pie'].includes(chartType) && form.category === 1) || chartType === 'table'"
|
||||
>
|
||||
<a-select @change="changeAttr" v-model="form.attr_ids" placeholder="请选择维度" mode="multiple" show-search>
|
||||
<a-select-option v-for="attr in commonAttributes" :key="attr.id" :value="attr.id">{{
|
||||
attr.alias || attr.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item
|
||||
prop="type_ids"
|
||||
label="关系模型"
|
||||
v-if="['bar', 'line', 'pie'].includes(chartType) && form.category === 2"
|
||||
>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="children"
|
||||
mode="multiple"
|
||||
v-model="form.type_ids"
|
||||
placeholder="请选择模型"
|
||||
>
|
||||
<a-select-opt-group
|
||||
v-for="(key, index) in Object.keys(level2children)"
|
||||
:key="key"
|
||||
:label="`层级${index + 1}`"
|
||||
>
|
||||
<a-select-option
|
||||
@click="(e) => clickLevel2children(e, citype, index + 1)"
|
||||
v-for="citype in level2children[key]"
|
||||
:key="citype.id"
|
||||
:value="citype.id"
|
||||
>
|
||||
{{ citype.alias || citype.name }}
|
||||
</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<div class="chart-left-preview">
|
||||
<span class="chart-left-preview-operation" @click="showPreview"><a-icon type="play-circle" /> 预览</span>
|
||||
<template v-if="isShowPreview">
|
||||
<div v-if="chartType !== 'count'" class="cmdb-dashboard-grid-item-title">
|
||||
<template v-if="form.showIcon && ciType">
|
||||
<template v-if="ciType.icon">
|
||||
<img
|
||||
v-if="ciType.icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: ciType.icon.split('$$')[1],
|
||||
}"
|
||||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</template>
|
||||
<span :style="{ color: '#000' }"> {{ form.name }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="chart-left-preview-box"
|
||||
:style="{
|
||||
height: chartType === 'count' ? '120px' : '',
|
||||
marginTop: chartType === 'count' ? '80px' : '',
|
||||
background:
|
||||
chartType === 'count'
|
||||
? Array.isArray(bgColor)
|
||||
? `linear-gradient(to bottom, ${bgColor[0]} 0%, ${bgColor[1]} 100%)`
|
||||
: bgColor
|
||||
: '#fafafa',
|
||||
}"
|
||||
>
|
||||
<div :style="{ color: fontColor }">{{ form.name }}</div>
|
||||
<Chart
|
||||
:ref="`chart_${item.id}`"
|
||||
:chartId="item.id"
|
||||
:data="previewData"
|
||||
:category="form.category"
|
||||
:options="{
|
||||
...item.options,
|
||||
name: form.name,
|
||||
fontColor: fontColor,
|
||||
bgColor: bgColor,
|
||||
chartType: chartType,
|
||||
showIcon: form.showIcon,
|
||||
barDirection: barDirection,
|
||||
barStack: barStack,
|
||||
chartColor: chartColor,
|
||||
type_ids: form.type_ids,
|
||||
attr_ids: form.attr_ids,
|
||||
isShadow: isShadow,
|
||||
}"
|
||||
:editable="false"
|
||||
:ci_types="ci_types"
|
||||
:type_id="form.type_id || form.type_ids"
|
||||
isPreview
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<a-form-model-item label="是否显示icon" prop="showIcon" :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
|
||||
<a-switch v-model="form.showIcon"></a-switch>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
|
||||
<div class="chart-right">
|
||||
<h4>图表类型</h4>
|
||||
<div class="chart-right-type">
|
||||
<div
|
||||
:class="{ 'chart-right-type-box': true, 'chart-right-type-box-selected': chartType === t.value }"
|
||||
v-for="t in chartTypeList"
|
||||
:key="t.value"
|
||||
@click="changeChartType(t)"
|
||||
>
|
||||
<ops-icon :type="`cmdb-${t.value}`" />
|
||||
<span>{{ t.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4>数据筛选</h4>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="attributes"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="filterExp ? `q=${filterExp}` : ''"
|
||||
/>
|
||||
<h4>格式</h4>
|
||||
<a-form-model :colon="false" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-form-model-item label="字体颜色" v-if="chartType === 'count'">
|
||||
<ColorPicker
|
||||
v-model="fontColor"
|
||||
:colorList="[
|
||||
'#1D2129',
|
||||
'#4E5969',
|
||||
'#103C93',
|
||||
'#86909C',
|
||||
'#ffffff',
|
||||
'#C9F2FF',
|
||||
'#FFEAC0',
|
||||
'#D6FFE6',
|
||||
'#F2DEFF',
|
||||
]"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="背景颜色" v-if="chartType === 'count'">
|
||||
<ColorPicker
|
||||
v-model="bgColor"
|
||||
:colorList="[
|
||||
['#6ABFFE', '#5375EB'],
|
||||
['#C69EFF', '#A377F9'],
|
||||
['#85EBC9', '#4AB8D8'],
|
||||
['#FEB58B', '#DF6463'],
|
||||
'#ffffff',
|
||||
'#FFFBF0',
|
||||
'#FFF1EC',
|
||||
'#E5FFFE',
|
||||
'#E5E7FF',
|
||||
]"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="图表颜色" v-else-if="chartType !== 'table'">
|
||||
<ColorListPicker v-model="chartColor" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="图表长度(%)">
|
||||
<a-radio-group class="chart-width" style="width:100%;" v-model="width">
|
||||
<a-radio-button :value="3">
|
||||
25
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="6">
|
||||
50
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="9">
|
||||
75
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="12">
|
||||
100
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="柱状图类型" v-if="chartType === 'bar'">
|
||||
<a-radio-group v-model="barStack">
|
||||
<a-radio value="total">
|
||||
堆积柱状图
|
||||
</a-radio>
|
||||
<a-radio value="">
|
||||
多系列柱状图
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="方向" v-if="chartType === 'bar'">
|
||||
<a-radio-group v-model="barDirection">
|
||||
<a-radio value="x">
|
||||
X轴
|
||||
</a-radio>
|
||||
<a-radio value="y">
|
||||
y轴
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="下方阴影" v-if="chartType === 'line'">
|
||||
<a-switch v-model="isShadow" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
</div>
|
||||
<a-modal :title="`${type === 'add' ? '新增' : '编辑'}图表`" :visible="visible" @cancel="handleclose" @ok="handleok">
|
||||
<a-form-model ref="chartForm" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
|
||||
<a-form-model-item label="类型" prop="category">
|
||||
<a-select v-model="form.category" @change="changeDashboardCategory">
|
||||
<a-select-option v-for="cate in Object.keys(dashboardCategory)" :key="cate" :value="Number(cate)">{{
|
||||
dashboardCategory[cate].label
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.category !== 0" label="名称" prop="name">
|
||||
<a-input v-model="form.name" placeholder="请输入图表名称"></a-input>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="模型" prop="type_id">
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="children"
|
||||
@change="changeCIType"
|
||||
v-model="form.type_id"
|
||||
placeholder="请选择模型"
|
||||
>
|
||||
<a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{
|
||||
ci_type.alias || ci_type.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.category === 1" label="模型属性" prop="attr_id">
|
||||
<a-select show-search optionFilterProp="children" v-model="form.attr_id" placeholder="请选择模型属性">
|
||||
<a-select-option v-for="attr in attributes" :key="attr.id" :value="attr.id">{{
|
||||
attr.alias || attr.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.category === 1" label="图表类型" prop="chartType">
|
||||
<a-radio-group v-model="chartType">
|
||||
<a-radio value="bar">
|
||||
柱状图
|
||||
</a-radio>
|
||||
<a-radio value="pie">
|
||||
饼图
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.category === 2" label="关系层级" prop="level">
|
||||
<a-input v-model="form.level" placeholder="请输入关系层级"></a-input>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.category === 0" label="字体">
|
||||
<FontConfig ref="fontConfig" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Chart from './chart.vue'
|
||||
import { dashboardCategory } from './constant'
|
||||
import { postCustomDashboard, putCustomDashboard, postCustomDashboardPreview } from '../../api/customDashboard'
|
||||
import { getCITypeAttributesByTypeIds, getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import { getRecursive_level2children } from '../../api/CITypeRelation'
|
||||
import { postCustomDashboard, putCustomDashboard } from '../../api/customDashboard'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import { getLastLayout } from '../../utils/helper'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import ColorPicker from './colorPicker.vue'
|
||||
import ColorListPicker from './colorListPicker.vue'
|
||||
|
||||
import FontConfig from './fontConfig.vue'
|
||||
export default {
|
||||
name: 'ChartForm',
|
||||
components: { Chart, FilterComp, ColorPicker, ColorListPicker },
|
||||
components: { FontConfig },
|
||||
props: {
|
||||
ci_types: {
|
||||
type: Array,
|
||||
@@ -309,226 +67,100 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const chartTypeList = [
|
||||
{
|
||||
value: 'count',
|
||||
label: '指标',
|
||||
},
|
||||
{
|
||||
value: 'bar',
|
||||
label: '柱状图',
|
||||
},
|
||||
{
|
||||
value: 'line',
|
||||
label: '折线图',
|
||||
},
|
||||
{
|
||||
value: 'pie',
|
||||
label: '饼状图',
|
||||
},
|
||||
{
|
||||
value: 'table',
|
||||
label: '表格',
|
||||
},
|
||||
]
|
||||
return {
|
||||
dashboardCategory,
|
||||
chartTypeList,
|
||||
visible: false,
|
||||
attributes: [],
|
||||
type: 'add',
|
||||
form: {
|
||||
category: 0,
|
||||
tableCategory: 1,
|
||||
name: undefined,
|
||||
type_id: undefined,
|
||||
type_ids: undefined,
|
||||
attr_ids: undefined,
|
||||
attr_id: undefined,
|
||||
level: undefined,
|
||||
showIcon: false,
|
||||
},
|
||||
rules: {
|
||||
category: [{ required: true, trigger: 'change' }],
|
||||
name: [{ required: true, message: '请输入图表名称' }],
|
||||
type_id: [{ required: true, message: '请选择模型', trigger: 'change' }],
|
||||
type_ids: [{ required: true, message: '请选择模型', trigger: 'change' }],
|
||||
attr_ids: [{ required: true, message: '请选择模型属性', trigger: 'change' }],
|
||||
attr_id: [{ required: true, message: '请选择模型属性', trigger: 'change' }],
|
||||
level: [{ required: true, message: '请输入关系层级' }],
|
||||
showIcon: [{ required: false }],
|
||||
},
|
||||
item: {},
|
||||
chartType: 'count', // table,bar,line,pie,count
|
||||
width: 3,
|
||||
fontColor: '#ffffff',
|
||||
bgColor: ['#6ABFFE', '#5375EB'],
|
||||
chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色
|
||||
isShowPreview: false,
|
||||
filterExp: undefined,
|
||||
previewData: null,
|
||||
barStack: 'total',
|
||||
barDirection: 'y',
|
||||
commonAttributes: [],
|
||||
level2children: {},
|
||||
isShadow: false,
|
||||
chartType: 'bar',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ciType() {
|
||||
if (this.form.type_id || this.form.type_ids) {
|
||||
const _find = this.ci_types.find((item) => item.id === this.form.type_id || item.id === this.form.type_ids[0])
|
||||
return _find || null
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
inject: ['layout'],
|
||||
methods: {
|
||||
async open(type, item = {}) {
|
||||
open(type, item = {}) {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
this.item = item
|
||||
const { category = 0, name, type_id, attr_id, level } = item
|
||||
const chartType = (item.options || {}).chartType || 'count'
|
||||
const fontColor = (item.options || {}).fontColor || '#ffffff'
|
||||
const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB']
|
||||
const width = (item.options || {}).w
|
||||
const showIcon = (item.options || {}).showIcon
|
||||
const type_ids = item?.options?.type_ids || []
|
||||
const attr_ids = item?.options?.attr_ids || []
|
||||
const ret = item?.options?.ret || ''
|
||||
this.width = width
|
||||
const chartType = (item.options || {}).chartType || 'bar'
|
||||
this.chartType = chartType
|
||||
this.filterExp = item?.options?.filter ?? ''
|
||||
this.chartColor = item?.options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD'
|
||||
this.isShadow = item?.options?.isShadow ?? false
|
||||
|
||||
if (chartType === 'count') {
|
||||
this.fontColor = fontColor
|
||||
this.bgColor = bgColor
|
||||
}
|
||||
if (type_ids && type_ids.length) {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: type_ids.join(',') }).then((res) => {
|
||||
if (type_id && attr_id) {
|
||||
getCITypeAttributesById(type_id).then((res) => {
|
||||
this.attributes = res.attributes
|
||||
})
|
||||
if ((['bar', 'line', 'pie'].includes(chartType) && category === 1) || chartType === 'table') {
|
||||
this.barDirection = item?.options?.barDirection ?? 'y'
|
||||
this.barStack = item?.options?.barStack ?? 'total'
|
||||
await getCITypeCommonAttributesByTypeIds({
|
||||
type_ids: type_ids.join(','),
|
||||
}).then((res) => {
|
||||
this.commonAttributes = res.attributes
|
||||
})
|
||||
}
|
||||
}
|
||||
if (type_id) {
|
||||
getRecursive_level2children(type_id).then((res) => {
|
||||
this.level2children = res
|
||||
})
|
||||
await getCITypeCommonAttributesByTypeIds({
|
||||
type_ids: type_id,
|
||||
}).then((res) => {
|
||||
this.commonAttributes = res.attributes
|
||||
})
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true, false)
|
||||
})
|
||||
const default_form = {
|
||||
category: 0,
|
||||
name: undefined,
|
||||
type_id: undefined,
|
||||
type_ids: undefined,
|
||||
attr_ids: undefined,
|
||||
attr_id: undefined,
|
||||
level: undefined,
|
||||
showIcon: false,
|
||||
tableCategory: 1,
|
||||
}
|
||||
this.form = {
|
||||
...default_form,
|
||||
category,
|
||||
name,
|
||||
type_id,
|
||||
type_ids,
|
||||
attr_ids,
|
||||
attr_id,
|
||||
level,
|
||||
showIcon,
|
||||
tableCategory: ret === 'cis' ? 2 : 1,
|
||||
}
|
||||
if (category === 0) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.fontConfig.setConfig((item.options || {}).fontConfig)
|
||||
})
|
||||
}
|
||||
},
|
||||
handleclose() {
|
||||
this.attributes = []
|
||||
this.$refs.chartForm.clearValidate()
|
||||
this.isShowPreview = false
|
||||
this.visible = false
|
||||
},
|
||||
changeCIType(value) {
|
||||
this.form.attr_ids = []
|
||||
this.commonAttributes = []
|
||||
getCITypeAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => {
|
||||
getCITypeAttributesById(value).then((res) => {
|
||||
this.attributes = res.attributes
|
||||
this.form = {
|
||||
...this.form,
|
||||
attr_id: undefined,
|
||||
}
|
||||
})
|
||||
if (!Array.isArray(value)) {
|
||||
getRecursive_level2children(value).then((res) => {
|
||||
this.level2children = res
|
||||
})
|
||||
}
|
||||
if ((['bar', 'line', 'pie'].includes(this.chartType) && this.form.category === 1) || this.chartType === 'table') {
|
||||
getCITypeCommonAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => {
|
||||
this.commonAttributes = res.attributes
|
||||
})
|
||||
}
|
||||
},
|
||||
handleok() {
|
||||
this.$refs.chartForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const name = this.form.name
|
||||
const { chartType, fontColor, bgColor } = this
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
const fontConfig = this.form.category === 0 ? this.$refs.fontConfig.getConfig() : undefined
|
||||
const _find = this.ci_types.find((attr) => attr.id === this.form.type_id)
|
||||
const name = this.form.name || (_find || {}).alias || (_find || {}).name
|
||||
if (this.item.id) {
|
||||
const params = {
|
||||
await putCustomDashboard(this.item.id, {
|
||||
...this.form,
|
||||
options: {
|
||||
...this.item.options,
|
||||
name,
|
||||
w: this.width,
|
||||
fontConfig,
|
||||
chartType: this.chartType,
|
||||
showIcon: this.form.showIcon,
|
||||
type_ids: this.form.type_ids,
|
||||
filter: this.filterExp,
|
||||
isShadow: this.isShadow,
|
||||
},
|
||||
}
|
||||
if (chartType === 'count') {
|
||||
params.options.fontColor = fontColor
|
||||
params.options.bgColor = bgColor
|
||||
}
|
||||
if (['bar', 'line', 'pie'].includes(chartType)) {
|
||||
if (this.form.category === 1) {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
}
|
||||
params.options.chartColor = this.chartColor
|
||||
}
|
||||
if (chartType === 'bar') {
|
||||
params.options.barDirection = this.barDirection
|
||||
params.options.barStack = this.barStack
|
||||
}
|
||||
if (chartType === 'table') {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
if (this.form.tableCategory === 2) {
|
||||
params.options.ret = 'cis'
|
||||
}
|
||||
}
|
||||
delete params.showIcon
|
||||
delete params.type_ids
|
||||
delete params.attr_ids
|
||||
delete params.tableCategory
|
||||
await putCustomDashboard(this.item.id, params)
|
||||
})
|
||||
} else {
|
||||
const { xLast, yLast, wLast } = getLastLayout(this.layout())
|
||||
const w = this.width
|
||||
const w = 3
|
||||
const x = xLast + wLast + w > 12 ? 0 : xLast + wLast
|
||||
const y = xLast + wLast + w > 12 ? yLast + 1 : yLast
|
||||
const params = {
|
||||
await postCustomDashboard({
|
||||
...this.form,
|
||||
options: {
|
||||
x,
|
||||
@@ -537,216 +169,23 @@ export default {
|
||||
h: this.form.category === 0 ? 3 : 5,
|
||||
name,
|
||||
chartType: this.chartType,
|
||||
showIcon: this.form.showIcon,
|
||||
type_ids: this.form.type_ids,
|
||||
filter: this.filterExp,
|
||||
isShadow: this.isShadow,
|
||||
fontConfig,
|
||||
},
|
||||
}
|
||||
if (chartType === 'count') {
|
||||
params.options.fontColor = fontColor
|
||||
params.options.bgColor = bgColor
|
||||
}
|
||||
if (['bar', 'line', 'pie'].includes(chartType)) {
|
||||
if (this.form.category === 1) {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
}
|
||||
params.options.chartColor = this.chartColor
|
||||
}
|
||||
if (chartType === 'bar') {
|
||||
params.options.barDirection = this.barDirection
|
||||
params.options.barStack = this.barStack
|
||||
}
|
||||
if (chartType === 'table') {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
if (this.form.tableCategory === 2) {
|
||||
params.options.ret = 'cis'
|
||||
}
|
||||
}
|
||||
delete params.showIcon
|
||||
delete params.type_ids
|
||||
delete params.attr_ids
|
||||
delete params.tableCategory
|
||||
await postCustomDashboard(params)
|
||||
})
|
||||
}
|
||||
this.handleclose()
|
||||
this.$emit('refresh')
|
||||
}
|
||||
})
|
||||
},
|
||||
// changeDashboardCategory(value) {
|
||||
// this.$refs.chartForm.clearValidate()
|
||||
// if (value === 1 && this.form.type_id) {
|
||||
// this.changeCIType(this.form.type_id)
|
||||
// }
|
||||
// },
|
||||
changeChartType(t) {
|
||||
this.chartType = t.value
|
||||
this.isShowPreview = false
|
||||
if (t.value === 'count') {
|
||||
this.form.category = 0
|
||||
} else {
|
||||
this.form.category = 1
|
||||
}
|
||||
this.resetForm()
|
||||
},
|
||||
showPreview() {
|
||||
this.$refs.chartForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isShowPreview = false
|
||||
const name = this.form.name
|
||||
const { chartType, fontColor, bgColor } = this
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
const params = {
|
||||
...this.form,
|
||||
options: {
|
||||
name,
|
||||
chartType,
|
||||
showIcon: this.form.showIcon,
|
||||
type_ids: this.form.type_ids,
|
||||
filter: this.filterExp,
|
||||
isShadow: this.isShadow,
|
||||
},
|
||||
}
|
||||
if (chartType === 'count') {
|
||||
params.options.fontColor = fontColor
|
||||
params.options.bgColor = bgColor
|
||||
}
|
||||
if (['bar', 'line', 'pie'].includes(chartType)) {
|
||||
if (this.form.category === 1) {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
}
|
||||
params.options.chartColor = this.chartColor
|
||||
}
|
||||
if (chartType === 'bar') {
|
||||
params.options.barDirection = this.barDirection
|
||||
params.options.barStack = this.barStack
|
||||
}
|
||||
if (chartType === 'table') {
|
||||
params.options.attr_ids = this.form.attr_ids
|
||||
if (this.form.tableCategory === 2) {
|
||||
params.options.ret = 'cis'
|
||||
}
|
||||
}
|
||||
delete params.showIcon
|
||||
delete params.type_ids
|
||||
delete params.attr_ids
|
||||
delete params.tableCategory
|
||||
postCustomDashboardPreview(params).then((res) => {
|
||||
this.isShowPreview = true
|
||||
this.previewData = res.counter
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
if (filterExp) {
|
||||
this.filterExp = `${filterExp}`
|
||||
} else {
|
||||
this.filterExp = undefined
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.form.type_id = undefined
|
||||
this.form.type_ids = []
|
||||
this.form.attr_ids = []
|
||||
changeDashboardCategory(value) {
|
||||
this.$refs.chartForm.clearValidate()
|
||||
},
|
||||
changeAttr(value) {
|
||||
if (value && value.length) {
|
||||
if (['line', 'pie'].includes(this.chartType)) {
|
||||
this.form.attr_ids = [value[value.length - 1]]
|
||||
}
|
||||
if (['bar'].includes(this.chartType) && value.length > 2) {
|
||||
this.form.attr_ids = value.slice(value.length - 2, value.length)
|
||||
}
|
||||
if (['table'].includes(this.chartType) && value.length > 3) {
|
||||
this.form.attr_ids = value.slice(value.length - 3, value.length)
|
||||
}
|
||||
if (value === 1 && this.form.type_id) {
|
||||
this.changeCIType(this.form.type_id)
|
||||
}
|
||||
},
|
||||
clickLevel2children(e, citype, level) {
|
||||
if (this.form.level !== level) {
|
||||
this.$nextTick(() => {
|
||||
this.form.type_ids = [citype.id]
|
||||
})
|
||||
}
|
||||
this.form.level = level
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-wrapper {
|
||||
display: flex;
|
||||
.chart-left {
|
||||
width: 50%;
|
||||
.chart-left-preview {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 2px;
|
||||
height: 280px;
|
||||
width: 92%;
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
.chart-left-preview-operation {
|
||||
color: #86909c;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.chart-left-preview-box {
|
||||
padding: 6px 12px;
|
||||
height: 250px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-right {
|
||||
width: 50%;
|
||||
h4 {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
.chart-right-type {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: #f0f5ff;
|
||||
padding: 6px 12px;
|
||||
.chart-right-type-box {
|
||||
cursor: pointer;
|
||||
width: 70px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> i {
|
||||
font-size: 32px;
|
||||
}
|
||||
> span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.chart-right-type-box-selected {
|
||||
background-color: #e5f1ff;
|
||||
}
|
||||
}
|
||||
.chart-width {
|
||||
width: 100%;
|
||||
> label {
|
||||
width: 25%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.chart-wrapper {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style></style>
|
||||
|
@@ -1,61 +1,23 @@
|
||||
export const category_1_bar_options = (data, options) => {
|
||||
// 计算一级分类
|
||||
const xData = Object.keys(data)
|
||||
// 计算共有多少二级分类
|
||||
const secondCategory = {}
|
||||
Object.keys(data).forEach(key => {
|
||||
if (Object.prototype.toString.call(data[key]) === '[object Object]') {
|
||||
Object.keys(data[key]).forEach(key1 => {
|
||||
secondCategory[key1] = Array.from({ length: xData.length }).fill(0)
|
||||
})
|
||||
} else {
|
||||
secondCategory['其他'] = Array.from({ length: xData.length }).fill(0)
|
||||
}
|
||||
})
|
||||
Object.keys(secondCategory).forEach(key => {
|
||||
xData.forEach((x, idx) => {
|
||||
if (data[x][key]) {
|
||||
secondCategory[key][idx] = data[x][key]
|
||||
}
|
||||
if (typeof data[x] === 'number') {
|
||||
secondCategory['其他'][idx] = data[x]
|
||||
}
|
||||
})
|
||||
})
|
||||
export const colorList = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc']
|
||||
|
||||
export const category_1_bar_options = (data) => {
|
||||
return {
|
||||
color: options.chartColor.split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
data: Object.keys(secondCategory),
|
||||
bottom: 0,
|
||||
type: 'scroll',
|
||||
},
|
||||
xAxis: options.barDirection === 'y' ? {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: xData
|
||||
}
|
||||
: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: options.barDirection === 'y' ? {
|
||||
data: Object.keys(data)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
} : {
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: xData
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -63,76 +25,34 @@ export const category_1_bar_options = (data, options) => {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
series: Object.keys(secondCategory).map(key => {
|
||||
return {
|
||||
name: key,
|
||||
type: 'bar',
|
||||
stack: options?.barStack ?? 'total',
|
||||
barGap: 0,
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: secondCategory[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const category_1_line_options = (data, options) => {
|
||||
const xData = Object.keys(data)
|
||||
return {
|
||||
color: options.chartColor.split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: xData.map(item => data[item]),
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
areaStyle: options?.isShadow ? {
|
||||
opacity: 0.5,
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: options.chartColor.split(',')[0] // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||
}],
|
||||
global: false // 缺省为 false
|
||||
data: Object.keys(data).map((key, index) => {
|
||||
return {
|
||||
value: data[key],
|
||||
itemStyle: { color: colorList[0] }
|
||||
}
|
||||
} : null
|
||||
}),
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
fontSize: 10,
|
||||
formatter(data) {
|
||||
return `${data.value || ''}`
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const category_1_pie_options = (data, options) => {
|
||||
export const category_1_pie_options = (data) => {
|
||||
return {
|
||||
color: options.chartColor.split(','),
|
||||
grid: {
|
||||
top: 10,
|
||||
left: 'left',
|
||||
right: 10,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
@@ -169,7 +89,7 @@ export const category_1_pie_options = (data, options) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const category_2_bar_options = (data, options, chartType) => {
|
||||
export const category_2_bar_options = (data) => {
|
||||
const xAxisData = Object.keys(data.detail)
|
||||
const _legend = []
|
||||
xAxisData.forEach(key => {
|
||||
@@ -177,11 +97,10 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
})
|
||||
const legend = [...new Set(_legend)]
|
||||
return {
|
||||
color: options.chartColor.split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
right: 10,
|
||||
right: 0,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
@@ -197,110 +116,41 @@ export const category_2_bar_options = (data, options, chartType) => {
|
||||
type: 'scroll',
|
||||
data: legend
|
||||
},
|
||||
xAxis: options.barDirection === 'y' || chartType === 'line' ? {
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: xAxisData
|
||||
}
|
||||
: {
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: xAxisData
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: options.barDirection === 'y' || chartType === 'line' ? {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
} : {
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: xAxisData
|
||||
},
|
||||
series: legend.map((le, index) => {
|
||||
],
|
||||
series: legend.map(le => {
|
||||
return {
|
||||
name: le,
|
||||
type: chartType,
|
||||
type: 'bar',
|
||||
barGap: 0,
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
stack: chartType === 'line' ? '' : options?.barStack ?? 'total',
|
||||
data: xAxisData.map(x => {
|
||||
return data.detail[x][le] || 0
|
||||
}),
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
areaStyle: chartType === 'line' && options?.isShadow ? {
|
||||
opacity: 0.5,
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: options.chartColor.split(',')[index % 8] // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1, color: '#ffffff' // 100% 处的颜色
|
||||
}],
|
||||
global: false // 缺省为 false
|
||||
show: true,
|
||||
position: 'top',
|
||||
fontSize: 10,
|
||||
formatter(data) {
|
||||
return `${data.value || ''}`
|
||||
}
|
||||
} : null
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const category_2_pie_options = (data, options) => {
|
||||
console.log(1111, options)
|
||||
const _legend = []
|
||||
Object.keys(data.detail).forEach(key => {
|
||||
Object.keys(data.detail[key]).forEach(key2 => {
|
||||
_legend.push({ value: data.detail[key][key2], name: `${key}-${key2}` })
|
||||
})
|
||||
})
|
||||
return {
|
||||
color: options.chartColor.split(','),
|
||||
grid: {
|
||||
top: 15,
|
||||
left: 'left',
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
type: 'scroll',
|
||||
formatter: function (name) {
|
||||
const _find = _legend.find(item => item.name === name)
|
||||
return `${name}:${_find.value}`
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '90%',
|
||||
data: _legend,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<a-select v-model="currenColor">
|
||||
<a-select-option v-for="i in list" :value="i" :key="i">
|
||||
<div>
|
||||
<span :style="{ backgroundColor: color }" class="color-box" v-for="color in i.split(',')" :key="color"></span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ColorListPicker',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [
|
||||
'#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD',
|
||||
'#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB',
|
||||
'#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB',
|
||||
'#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467',
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currenColor: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.color-box {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 10px;
|
||||
}
|
||||
</style>
|
@@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div class="color-picker">
|
||||
<div
|
||||
:style="{
|
||||
background: Array.isArray(item) ? `linear-gradient(to bottom, ${item[0]} 0%, ${item[1]} 100%)` : item,
|
||||
}"
|
||||
:class="{ 'color-picker-box': true, 'color-picker-box-selected': isEqual(currenColor, item) }"
|
||||
v-for="item in colorList"
|
||||
:key="Array.isArray(item) ? item.join() : item"
|
||||
@click="changeColor(item)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: null,
|
||||
},
|
||||
colorList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currenColor: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isEqual: _.isEqual,
|
||||
changeColor(item) {
|
||||
this.$emit('change', item)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.color-picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
.color-picker-box {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
border: 1px solid #dae2e7;
|
||||
border-radius: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.color-picker-box-selected {
|
||||
position: relative;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 1px solid #43bbff;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,4 +1,5 @@
|
||||
export const dashboardCategory = {
|
||||
1: { label: '默认' },
|
||||
2: { label: '关系' }
|
||||
0: { label: 'CI数统计' },
|
||||
1: { label: '按属性值分类统计' },
|
||||
2: { label: '关系统计' }
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@
|
||||
<template v-if="layout && layout.length">
|
||||
<div v-if="editable">
|
||||
<a-button
|
||||
:style="{ marginLeft: '22px', marginTop: '20px' }"
|
||||
@click="openChartForm('add', { options: { w: 3 } })"
|
||||
:style="{ marginLeft: '10px' }"
|
||||
@click="openChartForm('add', {})"
|
||||
ghost
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -39,44 +39,11 @@
|
||||
:h="item.h"
|
||||
:i="item.i"
|
||||
:key="item.i"
|
||||
:style="{
|
||||
background:
|
||||
item.options.chartType === 'count'
|
||||
? Array.isArray(item.options.bgColor)
|
||||
? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)`
|
||||
: item.options.bgColor
|
||||
: '#fafafa',
|
||||
}"
|
||||
:style="{ backgroundColor: '#fafafa' }"
|
||||
>
|
||||
<div class="cmdb-dashboard-grid-item-title">
|
||||
<template v-if="item.options.chartType !== 'count' && item.options.showIcon && getCiType(item)">
|
||||
<template v-if="getCiType(item).icon">
|
||||
<img
|
||||
v-if="getCiType(item).icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${getCiType(item).icon.split('$$')[3]}`"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: getCiType(item).icon.split('$$')[1],
|
||||
}"
|
||||
:type="getCiType(item).icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ getCiType(item).name[0].toUpperCase() }}</span>
|
||||
</template>
|
||||
<span :style="{ color: item.options.chartType === 'count' ? item.options.fontColor : '#000' }">{{
|
||||
item.options.name
|
||||
}}</span>
|
||||
</div>
|
||||
<CardTitle>{{ item.options.name }}</CardTitle>
|
||||
<a-dropdown v-if="editable">
|
||||
<a
|
||||
class="cmdb-dashboard-grid-item-operation"
|
||||
:style="{
|
||||
color: item.options.chartType === 'count' ? item.options.fontColor : '',
|
||||
}"
|
||||
><a-icon type="menu"></a-icon
|
||||
></a>
|
||||
<a class="cmdb-dashboard-grid-item-operation"><a-icon type="menu"></a-icon></a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item>
|
||||
<a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />编辑</a>
|
||||
@@ -86,13 +53,13 @@
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<!-- <a
|
||||
<a
|
||||
v-if="editable && item.category === 1"
|
||||
class="cmdb-dashboard-grid-item-chart-type"
|
||||
@click="changeChartType(item)"
|
||||
><a-icon
|
||||
:type="item.options.chartType === 'bar' ? 'bar-chart' : 'pie-chart'"
|
||||
/></a> -->
|
||||
/></a>
|
||||
<Chart
|
||||
:ref="`chart_${item.id}`"
|
||||
:chartId="item.id"
|
||||
@@ -100,26 +67,18 @@
|
||||
:category="item.category"
|
||||
:options="item.options"
|
||||
:editable="editable"
|
||||
:ci_types="ci_types"
|
||||
:type_id="item.type_id"
|
||||
/>
|
||||
</GridItem>
|
||||
</GridLayout>
|
||||
</template>
|
||||
<div v-else class="dashboard-empty">
|
||||
<a-empty :image="emptyImage" description=""></a-empty>
|
||||
<a-button
|
||||
@click="openChartForm('add', { options: { w: 3 } })"
|
||||
v-if="editable"
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="plus"
|
||||
>
|
||||
<a-button @click="openChartForm('add', {})" v-if="editable" size="small" type="primary" icon="plus">
|
||||
定制仪表盘
|
||||
</a-button>
|
||||
<span v-else>管理员暂未定制仪表盘</span>
|
||||
</div>
|
||||
<ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" :totalData="totalData" />
|
||||
<ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -168,14 +127,12 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getLayout()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
this.getLayout()
|
||||
},
|
||||
methods: {
|
||||
async getLayout() {
|
||||
const res = await getCustomDashboard()
|
||||
@@ -239,13 +196,6 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
getCiType(item) {
|
||||
if (item.type_id || item.options?.type_ids) {
|
||||
const _find = this.ci_types.find((type) => type.id === item.type_id || type.id === item.options?.type_ids[0])
|
||||
return _find || null
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -256,18 +206,15 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
.cmdb-dashboard-grid-item {
|
||||
border-radius: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 15px;
|
||||
.cmdb-dashboard-grid-item-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
color: #000000;
|
||||
padding-left: 6px;
|
||||
color: #000000bd;
|
||||
}
|
||||
.cmdb-dashboard-grid-item-operation {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
}
|
||||
.cmdb-dashboard-grid-item-chart-type {
|
||||
@@ -277,26 +224,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.cmdb-dashboard-grid-item-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> i {
|
||||
font-size: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
> img {
|
||||
width: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
> span:not(:last-child) {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -10,12 +10,7 @@
|
||||
<div class="discovery-bottom"></div>
|
||||
<div class="discovery-top">
|
||||
<div class="discovery-header">
|
||||
<img
|
||||
v-if="icon.id && icon.url"
|
||||
:src="`/api/common-setting/v1/file/${icon.url}`"
|
||||
:style="{ maxHeight: '30px', maxWidth: '30px' }"
|
||||
/>
|
||||
<ops-icon v-else :type="icon.name || 'caise-chajian'" :style="{ fontSize: '30px', color: icon.color }" />
|
||||
<ops-icon :type="icon.name || 'caise-chajian'" :style="{ fontSize: '30px', color: icon.color }"></ops-icon>
|
||||
<span :title="rule.name">{{ rule.name }}</span>
|
||||
</div>
|
||||
<template v-if="!isSelected">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user