mirror of
https://github.com/veops/cmdb.git
synced 2025-09-19 19:39:17 +08:00
Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2e644233bc | ||
|
d9b4082b46 | ||
|
a07f984152 | ||
|
4cab7ef6b0 | ||
|
070c163de6 | ||
|
282a779fb1 | ||
|
cb6b51a84c | ||
|
34bd320e75 | ||
|
1eca5791f6 | ||
|
13b1c9a30c | ||
|
b1a15a85d2 | ||
|
08e5a02caf | ||
|
308827b8fc | ||
|
dc4ccb22b9 | ||
|
c482e7ea43 | ||
|
663c14f763 | ||
|
c6ee227bab | ||
|
cb62cf2410 | ||
|
133f32a6b0 | ||
|
45c48c86fe | ||
|
2321f17dae | ||
|
ddb31a07a2 | ||
|
b474914fbb | ||
|
26099a3d69 | ||
|
62829c885b | ||
|
260aed6462 | ||
|
3841999cca | ||
|
14c03ce5d2 | ||
|
f463ecd6e6 | ||
|
adc0cfd5c5 | ||
|
086481657e | ||
|
d2f84ae3dc | ||
|
9f1b510cb3 | ||
|
61acb2483d | ||
|
0196c8a82c | ||
|
bed2323fc1 | ||
|
be9b308f56 | ||
|
8ba658ea1b | ||
|
0aa668cfa0 | ||
|
e20fd33a53 | ||
|
7462de63de | ||
|
5f9ba069ad | ||
|
5dc0d95ff8 | ||
|
e5536b76e6 | ||
|
8b044efd4e | ||
|
747b5bf494 | ||
|
21067022f6 | ||
|
4102c44fb2 | ||
|
600f95ce18 | ||
|
950fd38044 | ||
|
01085615b5 | ||
|
734f1940f9 | ||
|
c25c1e4e4b | ||
|
826a8306d3 | ||
|
740aae573e | ||
|
17828a7631 | ||
|
02cb497bdc | ||
|
05a7dc41ee | ||
|
459c70ba2d | ||
|
774f42ac34 | ||
|
420029a5e2 | ||
|
ab8acbfd20 | ||
|
4468b6a8de | ||
|
6bf145d085 | ||
|
42b1e47e76 | ||
|
673134003a | ||
|
ef67885571 | ||
|
075bf7217f | ||
|
3b7b8f435c | ||
|
2b7f6aeef3 | ||
|
544fac8aca | ||
|
3d0a56ec8c | ||
|
d2d8482052 | ||
|
a0afae8d2e | ||
|
9f3da68636 | ||
|
24b955c288 | ||
|
a07b2d37ec | ||
|
c86fcb4e7b | ||
|
ca7964f24b | ||
|
c42ac634fb | ||
|
a6fc3341ce | ||
|
fc3f2e25f3 | ||
|
511a5f70c6 | ||
|
f8ff4d5e45 | ||
|
3ab72cceaf | ||
|
4ab7e3c70c | ||
|
a7fe75f7df | ||
|
3474a71a75 | ||
|
6531baff64 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,6 +39,7 @@ pip-log.txt
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
cmdb-api/test-output
|
cmdb-api/test-output
|
||||||
|
cmdb-api/api/uploaded_files
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
47
Makefile
47
Makefile
@@ -1,37 +1,52 @@
|
|||||||
.PHONY: env clean api ui worker
|
MYSQL_ROOT_PASSWORD ?= root
|
||||||
|
MYSQL_PORT ?= 3306
|
||||||
|
REDIS_PORT ?= 6379
|
||||||
|
|
||||||
help:
|
default: help
|
||||||
@echo " env create a development environment using pipenv"
|
help: ## display this help
|
||||||
@echo " deps install dependencies using pip"
|
@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)
|
||||||
@echo " clean remove unwanted files like .pyc's"
|
.PHONY: help
|
||||||
@echo " lint check style with flake8"
|
|
||||||
@echo " api start api server"
|
|
||||||
@echo " ui start ui server"
|
|
||||||
@echo " worker start async tasks worker"
|
|
||||||
|
|
||||||
env:
|
env: ## create a development environment using pipenv
|
||||||
sudo easy_install pip && \
|
sudo easy_install pip && \
|
||||||
pip install pipenv -i https://pypi.douban.com/simple && \
|
pip install pipenv -i https://pypi.douban.com/simple && \
|
||||||
npm install yarn && \
|
npm install yarn && \
|
||||||
make deps
|
make deps
|
||||||
|
.PHONY: env
|
||||||
|
|
||||||
deps:
|
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 && \
|
||||||
pipenv install --dev && \
|
pipenv install --dev && \
|
||||||
pipenv run flask db-setup && \
|
pipenv run flask db-setup && \
|
||||||
pipenv run flask cmdb-init-cache && \
|
pipenv run flask cmdb-init-cache && \
|
||||||
|
cd .. && \
|
||||||
cd cmdb-ui && yarn install && cd ..
|
cd cmdb-ui && yarn install && cd ..
|
||||||
|
.PHONY: deps
|
||||||
|
|
||||||
api:
|
api: ## start api server
|
||||||
cd cmdb-api && pipenv run flask run -h 0.0.0.0
|
cd cmdb-api && pipenv run flask run -h 0.0.0.0
|
||||||
|
.PHONY: api
|
||||||
|
|
||||||
worker:
|
worker: ## start async tasks 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
|
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:
|
ui: ## start ui server
|
||||||
cd cmdb-ui && yarn run serve
|
cd cmdb-ui && yarn run serve
|
||||||
|
.PHONY: ui
|
||||||
|
|
||||||
clean:
|
clean: ## remove unwanted files like .pyc's
|
||||||
pipenv run flask clean
|
pipenv run flask clean
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
lint:
|
lint: ## check style with flake8
|
||||||
flake8 --exclude=env .
|
flake8 --exclude=env .
|
||||||
|
.PHONY: lint
|
||||||
|
@@ -4,8 +4,8 @@
|
|||||||
[](https://github.com/sendya/ant-design-pro-vue)
|
[](https://github.com/sendya/ant-design-pro-vue)
|
||||||
[](https://github.com/pallets/flask)
|
[](https://github.com/pallets/flask)
|
||||||
|
|
||||||
[English](README_en.md) / [中文](README.md)
|
[English](docs/README_en.md) / [中文](README.md)
|
||||||
|
- 产品文档:https://veops.cn/docs/
|
||||||
- 在线体验: <a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
|
- 在线体验: <a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
|
||||||
- username: demo 或者 admin
|
- username: demo 或者 admin
|
||||||
- password: 123456
|
- password: 123456
|
||||||
|
@@ -44,10 +44,12 @@ treelib = "==1.6.1"
|
|||||||
flasgger = "==0.9.5"
|
flasgger = "==0.9.5"
|
||||||
Pillow = "==9.3.0"
|
Pillow = "==9.3.0"
|
||||||
# other
|
# other
|
||||||
six = "==1.12.0"
|
six = "==1.16.0"
|
||||||
bs4 = ">=0.0.1"
|
bs4 = ">=0.0.1"
|
||||||
toposort = ">=1.5"
|
toposort = ">=1.5"
|
||||||
requests = ">=2.22.0"
|
requests = ">=2.22.0"
|
||||||
|
requests_oauthlib = "==1.3.1"
|
||||||
|
markdownify = "==0.11.6"
|
||||||
PyJWT = "==2.4.0"
|
PyJWT = "==2.4.0"
|
||||||
elasticsearch = "==7.17.9"
|
elasticsearch = "==7.17.9"
|
||||||
future = "==0.18.3"
|
future = "==0.18.3"
|
||||||
|
@@ -6,13 +6,14 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from inspect import getmembers
|
from inspect import getmembers
|
||||||
from flask.json.provider import DefaultJSONProvider
|
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import jsonify, make_response
|
from flask import jsonify
|
||||||
|
from flask import make_response
|
||||||
from flask.blueprints import Blueprint
|
from flask.blueprints import Blueprint
|
||||||
from flask.cli import click
|
from flask.cli import click
|
||||||
|
from flask.json.provider import DefaultJSONProvider
|
||||||
|
|
||||||
import api.views.entry
|
import api.views.entry
|
||||||
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||||
@@ -21,7 +22,6 @@ from api.models.acl import User
|
|||||||
|
|
||||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
PROJECT_ROOT = os.path.join(HERE, os.pardir)
|
PROJECT_ROOT = os.path.join(HERE, os.pardir)
|
||||||
API_PACKAGE = "api"
|
|
||||||
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
@@ -174,9 +174,8 @@ def register_commands(app):
|
|||||||
for root, _, files in os.walk(os.path.join(HERE, "commands")):
|
for root, _, files in os.walk(os.path.join(HERE, "commands")):
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if not filename.startswith("_") and filename.endswith("py"):
|
if not filename.startswith("_") and filename.endswith("py"):
|
||||||
module_path = os.path.join(API_PACKAGE, root[root.index("commands"):])
|
if root not in sys.path:
|
||||||
if module_path not in sys.path:
|
sys.path.insert(1, root)
|
||||||
sys.path.insert(1, module_path)
|
|
||||||
command = __import__(os.path.splitext(filename)[0])
|
command = __import__(os.path.splitext(filename)[0])
|
||||||
func_list = [o[0] for o in getmembers(command) if isinstance(o[1], click.core.Command)]
|
func_list = [o[0] for o in getmembers(command) if isinstance(o[1], click.core.Command)]
|
||||||
for func_name in func_list:
|
for func_name in func_list:
|
||||||
|
@@ -9,12 +9,12 @@ import time
|
|||||||
import click
|
import click
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
from flask_login import login_user
|
||||||
|
|
||||||
import api.lib.cmdb.ci
|
import api.lib.cmdb.ci
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.extensions import rd
|
from api.extensions import rd
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.ci_type import CITypeTriggerManager
|
|
||||||
from api.lib.cmdb.const import PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||||
@@ -23,6 +23,7 @@ from api.lib.cmdb.const import RoleEnum
|
|||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
from api.lib.exception import AbortException
|
from api.lib.exception import AbortException
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
|
from api.lib.perm.acl.acl import UserCache
|
||||||
from api.lib.perm.acl.cache import AppCache
|
from api.lib.perm.acl.cache import AppCache
|
||||||
from api.lib.perm.acl.resource import ResourceCRUD
|
from api.lib.perm.acl.resource import ResourceCRUD
|
||||||
from api.lib.perm.acl.resource import ResourceTypeCRUD
|
from api.lib.perm.acl.resource import ResourceTypeCRUD
|
||||||
@@ -207,6 +208,8 @@ def cmdb_counter():
|
|||||||
"""
|
"""
|
||||||
from api.lib.cmdb.cache import CMDBCounterCache
|
from api.lib.cmdb.cache import CMDBCounterCache
|
||||||
|
|
||||||
|
current_app.test_request_context().push()
|
||||||
|
login_user(UserCache.get('worker'))
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
@@ -223,50 +226,60 @@ def cmdb_counter():
|
|||||||
@with_appcontext
|
@with_appcontext
|
||||||
def cmdb_trigger():
|
def cmdb_trigger():
|
||||||
"""
|
"""
|
||||||
Trigger execution
|
Trigger execution for date attribute
|
||||||
"""
|
"""
|
||||||
|
from api.lib.cmdb.ci import CITriggerManager
|
||||||
|
|
||||||
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
|
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
|
||||||
trigger2cis = dict()
|
trigger2cis = dict()
|
||||||
trigger2completed = dict()
|
trigger2completed = dict()
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
db.session.remove()
|
try:
|
||||||
if datetime.datetime.today().strftime("%Y-%m-%d") != current_day:
|
db.session.remove()
|
||||||
trigger2cis = dict()
|
|
||||||
trigger2completed = dict()
|
|
||||||
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
if i == 360 or i == 0:
|
if datetime.datetime.today().strftime("%Y-%m-%d") != current_day:
|
||||||
i = 0
|
trigger2cis = dict()
|
||||||
try:
|
trigger2completed = dict()
|
||||||
triggers = CITypeTrigger.get_by(to_dict=False)
|
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
if i == 3 or i == 0:
|
||||||
|
i = 0
|
||||||
|
triggers = CITypeTrigger.get_by(to_dict=False, __func_isnot__key_attr_id=None)
|
||||||
for trigger in triggers:
|
for trigger in triggers:
|
||||||
ready_cis = CITypeTriggerManager.waiting_cis(trigger)
|
try:
|
||||||
|
ready_cis = CITriggerManager.waiting_cis(trigger)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
continue
|
||||||
|
|
||||||
if trigger.id not in trigger2cis:
|
if trigger.id not in trigger2cis:
|
||||||
trigger2cis[trigger.id] = (trigger, ready_cis)
|
trigger2cis[trigger.id] = (trigger, ready_cis)
|
||||||
else:
|
else:
|
||||||
cur = trigger2cis[trigger.id]
|
cur = trigger2cis[trigger.id]
|
||||||
cur_ci_ids = {i.ci_id for i in cur[1]}
|
cur_ci_ids = {i.ci_id for i in cur[1]}
|
||||||
trigger2cis[trigger.id] = (trigger, cur[1] + [i for i in ready_cis if i.ci_id not in cur_ci_ids
|
trigger2cis[trigger.id] = (
|
||||||
and i.ci_id not in trigger2completed[trigger.id]])
|
trigger, cur[1] + [i for i in ready_cis if i.ci_id not in cur_ci_ids
|
||||||
|
and i.ci_id not in trigger2completed.get(trigger.id, {})])
|
||||||
|
|
||||||
except Exception as e:
|
for tid in trigger2cis:
|
||||||
print(e)
|
trigger, cis = trigger2cis[tid]
|
||||||
|
for ci in copy.deepcopy(cis):
|
||||||
|
if CITriggerManager.trigger_notify(trigger, ci):
|
||||||
|
trigger2completed.setdefault(trigger.id, set()).add(ci.ci_id)
|
||||||
|
|
||||||
for tid in trigger2cis:
|
for _ci in cis:
|
||||||
trigger, cis = trigger2cis[tid]
|
if _ci.ci_id == ci.ci_id:
|
||||||
for ci in copy.deepcopy(cis):
|
cis.remove(_ci)
|
||||||
if CITypeTriggerManager.trigger_notify(trigger, ci):
|
|
||||||
trigger2completed.setdefault(trigger.id, set()).add(ci.ci_id)
|
|
||||||
|
|
||||||
for _ci in cis:
|
i += 1
|
||||||
if _ci.ci_id == ci.ci_id:
|
time.sleep(10)
|
||||||
cis.remove(_ci)
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
i += 1
|
print(traceback.format_exc())
|
||||||
time.sleep(10)
|
current_app.logger.error("cmdb trigger exception: {}".format(e))
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
|
@@ -161,6 +161,55 @@ class InitDepartment(object):
|
|||||||
info = f"update department acl_rid: {acl_rid}"
|
info = f"update department acl_rid: {acl_rid}"
|
||||||
current_app.logger.info(info)
|
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()
|
@click.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
@@ -177,5 +226,63 @@ def init_department():
|
|||||||
"""
|
"""
|
||||||
Department initialization
|
Department initialization
|
||||||
"""
|
"""
|
||||||
InitDepartment().init()
|
cli = InitDepartment()
|
||||||
InitDepartment().create_acl_role_with_department()
|
cli.init_wide_company()
|
||||||
|
cli.create_acl_role_with_department()
|
||||||
|
cli.init_backend_resource()
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@with_appcontext
|
||||||
|
def common_check_new_columns():
|
||||||
|
"""
|
||||||
|
add new columns to tables
|
||||||
|
"""
|
||||||
|
from api.extensions import db
|
||||||
|
from sqlalchemy import inspect, text
|
||||||
|
|
||||||
|
def get_model_by_table_name(table_name):
|
||||||
|
for model in db.Model.registry._class_registry.values():
|
||||||
|
if hasattr(model, '__tablename__') and model.__tablename__ == table_name:
|
||||||
|
return model
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_new_column(table_name, new_column):
|
||||||
|
column_type = new_column.type.compile(engine.dialect)
|
||||||
|
default_value = new_column.default.arg if new_column.default else None
|
||||||
|
|
||||||
|
sql = f"ALTER TABLE {table_name} ADD COLUMN {new_column.name} {column_type} "
|
||||||
|
if new_column.comment:
|
||||||
|
sql += f" comment '{new_column.comment}'"
|
||||||
|
|
||||||
|
if column_type == 'JSON':
|
||||||
|
pass
|
||||||
|
elif default_value:
|
||||||
|
if column_type.startswith('VAR') or column_type.startswith('Text'):
|
||||||
|
if default_value is None or len(default_value) == 0:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
sql += f" DEFAULT {default_value}"
|
||||||
|
|
||||||
|
sql = text(sql)
|
||||||
|
db.session.execute(sql)
|
||||||
|
|
||||||
|
engine = db.get_engine()
|
||||||
|
inspector = inspect(engine)
|
||||||
|
table_names = inspector.get_table_names()
|
||||||
|
for table_name in table_names:
|
||||||
|
existed_columns = inspector.get_columns(table_name)
|
||||||
|
existed_column_name_list = [c['name'] for c in existed_columns]
|
||||||
|
|
||||||
|
model = get_model_by_table_name(table_name)
|
||||||
|
if model is None:
|
||||||
|
continue
|
||||||
|
model_columns = model.__table__.columns._all_columns
|
||||||
|
for column in model_columns:
|
||||||
|
if column.name not in existed_column_name_list:
|
||||||
|
try:
|
||||||
|
add_new_column(table_name, column)
|
||||||
|
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
|
||||||
|
current_app.logger.error(e)
|
@@ -8,9 +8,14 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
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.cache import CITypeCache
|
||||||
|
from api.lib.cmdb.const import BUILTIN_KEYWORDS
|
||||||
from api.lib.cmdb.const import CITypeOperateType
|
from api.lib.cmdb.const import CITypeOperateType
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
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 ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
from api.lib.cmdb.history import CITypeHistoryManager
|
from api.lib.cmdb.history import CITypeHistoryManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
@@ -40,7 +45,7 @@ class AttributeManager(object):
|
|||||||
ret_key = choice_web_hook.get('ret_key')
|
ret_key = choice_web_hook.get('ret_key')
|
||||||
headers = choice_web_hook.get('headers') or {}
|
headers = choice_web_hook.get('headers') or {}
|
||||||
payload = choice_web_hook.get('payload') or {}
|
payload = choice_web_hook.get('payload') or {}
|
||||||
method = choice_web_hook.get('method', 'GET').lower()
|
method = (choice_web_hook.get('method') or 'GET').lower()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
||||||
@@ -55,15 +60,17 @@ class AttributeManager(object):
|
|||||||
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
|
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(str(e))
|
current_app.logger.error("get choice values failed: {}".format(e))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
|
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
|
||||||
if choice_web_hook and isinstance(choice_web_hook, dict) and choice_web_hook_parse:
|
if choice_web_hook:
|
||||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
if choice_web_hook_parse:
|
||||||
elif choice_web_hook and not choice_web_hook_parse:
|
if isinstance(choice_web_hook, dict):
|
||||||
return []
|
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
choice_table = ValueTypeMap.choice.get(value_type)
|
choice_table = ValueTypeMap.choice.get(value_type)
|
||||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||||
@@ -73,34 +80,34 @@ class AttributeManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def add_choice_values(_id, value_type, choice_values):
|
def add_choice_values(_id, value_type, choice_values):
|
||||||
choice_table = ValueTypeMap.choice.get(value_type)
|
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:
|
for v, option in choice_values:
|
||||||
table = choice_table(attr_id=_id, value=v, option=option)
|
choice_table.create(attr_id=_id, value=v, option=option, commit=False)
|
||||||
|
|
||||||
db.session.add(table)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
except:
|
except Exception as e:
|
||||||
|
current_app.logger.warning("add choice values failed: {}".format(e))
|
||||||
return abort(400, ErrFormat.invalid_choice_values)
|
return abort(400, ErrFormat.invalid_choice_values)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _del_choice_values(_id, value_type):
|
def _del_choice_values(_id, value_type):
|
||||||
choice_table = ValueTypeMap.choice.get(value_type)
|
choice_table = ValueTypeMap.choice.get(value_type)
|
||||||
|
|
||||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
choice_table and choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_attributes(cls, name=None, alias=None, page=1, page_size=None):
|
def search_attributes(cls, name=None, alias=None, page=1, page_size=None):
|
||||||
"""
|
"""
|
||||||
:param name:
|
:param name:
|
||||||
:param alias:
|
:param alias:
|
||||||
:param page:
|
:param page:
|
||||||
:param page_size:
|
:param page_size:
|
||||||
:return: attribute, if name is None, then return all attributes
|
:return: attribute, if name is None, then return all attributes
|
||||||
"""
|
"""
|
||||||
if name is not None:
|
if name is not None:
|
||||||
@@ -114,8 +121,8 @@ class AttributeManager(object):
|
|||||||
attrs = attrs[(page - 1) * page_size:][:page_size]
|
attrs = attrs[(page - 1) * page_size:][:page_size]
|
||||||
res = list()
|
res = list()
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values(
|
attr["is_choice"] and attr.update(
|
||||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
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)
|
attr['is_choice'] and attr.pop('choice_web_hook', None)
|
||||||
|
|
||||||
res.append(attr)
|
res.append(attr)
|
||||||
@@ -124,30 +131,31 @@ class AttributeManager(object):
|
|||||||
|
|
||||||
def get_attribute_by_name(self, name):
|
def get_attribute_by_name(self, name):
|
||||||
attr = Attribute.get_by(name=name, first=True)
|
attr = Attribute.get_by(name=name, first=True)
|
||||||
if attr and attr["is_choice"]:
|
if attr.get("is_choice"):
|
||||||
attr.update(dict(choice_value=self.get_choice_values(
|
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def get_attribute_by_alias(self, alias):
|
def get_attribute_by_alias(self, alias):
|
||||||
attr = Attribute.get_by(alias=alias, first=True)
|
attr = Attribute.get_by(alias=alias, first=True)
|
||||||
if attr and attr["is_choice"]:
|
if attr.get("is_choice"):
|
||||||
attr.update(dict(choice_value=self.get_choice_values(
|
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def get_attribute_by_id(self, _id):
|
def get_attribute_by_id(self, _id):
|
||||||
attr = Attribute.get_by_id(_id).to_dict()
|
attr = Attribute.get_by_id(_id).to_dict()
|
||||||
if attr and attr["is_choice"]:
|
if attr.get("is_choice"):
|
||||||
attr.update(dict(choice_value=self.get_choice_values(
|
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def get_attribute(self, key, choice_web_hook_parse=True):
|
def get_attribute(self, key, choice_web_hook_parse=True):
|
||||||
attr = AttributeCache.get(key).to_dict()
|
attr = AttributeCache.get(key).to_dict()
|
||||||
if attr and attr["is_choice"]:
|
if attr.get("is_choice"):
|
||||||
attr.update(dict(choice_value=self.get_choice_values(
|
attr["choice_value"] = self.get_choice_values(
|
||||||
attr["id"], attr["value_type"], attr["choice_web_hook"])), choice_web_hook_parse=choice_web_hook_parse)
|
attr["id"], attr["value_type"], attr["choice_web_hook"], choice_web_hook_parse=choice_web_hook_parse)
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -155,6 +163,19 @@ class AttributeManager(object):
|
|||||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin('cmdb'):
|
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))
|
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
|
@classmethod
|
||||||
@kwargs_required("name")
|
@kwargs_required("name")
|
||||||
def add(cls, **kwargs):
|
def add(cls, **kwargs):
|
||||||
@@ -163,8 +184,9 @@ class AttributeManager(object):
|
|||||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||||
|
|
||||||
name = kwargs.pop("name")
|
name = kwargs.pop("name")
|
||||||
if name in {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}:
|
if name in BUILTIN_KEYWORDS:
|
||||||
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
|
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
|
||||||
|
|
||||||
alias = kwargs.pop("alias", "")
|
alias = kwargs.pop("alias", "")
|
||||||
alias = name if not alias else alias
|
alias = name if not alias else alias
|
||||||
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
||||||
@@ -212,6 +234,11 @@ class AttributeManager(object):
|
|||||||
|
|
||||||
return attr.id
|
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
|
@staticmethod
|
||||||
def _change_index(attr, old, new):
|
def _change_index(attr, old, new):
|
||||||
from api.lib.cmdb.utils import TableMap
|
from api.lib.cmdb.utils import TableMap
|
||||||
@@ -222,11 +249,11 @@ class AttributeManager(object):
|
|||||||
new_table = TableMap(attr=attr, is_index=new).table
|
new_table = TableMap(attr=attr, is_index=new).table
|
||||||
|
|
||||||
ci_ids = []
|
ci_ids = []
|
||||||
for i in db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id):
|
for i in old_table.get_by(attr_id=attr.id, to_dict=False):
|
||||||
new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True)
|
new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True)
|
||||||
ci_ids.append(i.ci_id)
|
ci_ids.append(i.ci_id)
|
||||||
|
|
||||||
db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id).delete()
|
old_table.get_by(attr_id=attr.id, only_query=True).delete()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -291,7 +318,7 @@ class AttributeManager(object):
|
|||||||
|
|
||||||
if is_choice and choice_value:
|
if is_choice and choice_value:
|
||||||
self.add_choice_values(attr.id, attr.value_type, choice_value)
|
self.add_choice_values(attr.id, attr.value_type, choice_value)
|
||||||
elif is_choice:
|
elif existed2['is_choice']:
|
||||||
self._del_choice_values(attr.id, attr.value_type)
|
self._del_choice_values(attr.id, attr.value_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -310,6 +337,8 @@ class AttributeManager(object):
|
|||||||
|
|
||||||
AttributeCache.clean(attr)
|
AttributeCache.clean(attr)
|
||||||
|
|
||||||
|
self._clean_ci_type_attributes_cache(_id)
|
||||||
|
|
||||||
return attr.id
|
return attr.id
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -323,24 +352,25 @@ class AttributeManager(object):
|
|||||||
ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True)
|
ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True)
|
||||||
if ref is not None:
|
if ref is not None:
|
||||||
ci_type = CITypeCache.get(ref.type_id)
|
ci_type = CITypeCache.get(ref.type_id)
|
||||||
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type.alias))
|
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type and ci_type.alias or ref.type_id))
|
||||||
|
|
||||||
if attr.uid != current_user.uid and not is_app_admin('cmdb'):
|
if attr.uid != current_user.uid and not is_app_admin('cmdb'):
|
||||||
return abort(403, ErrFormat.cannot_delete_attribute)
|
return abort(403, ErrFormat.cannot_delete_attribute)
|
||||||
|
|
||||||
if attr.is_choice:
|
if attr.is_choice:
|
||||||
choice_table = ValueTypeMap.choice.get(attr.value_type)
|
choice_table = ValueTypeMap.choice.get(attr.value_type)
|
||||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict
|
choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||||
db.session.flush()
|
|
||||||
|
|
||||||
AttributeCache.clean(attr)
|
|
||||||
|
|
||||||
attr.soft_delete()
|
attr.soft_delete()
|
||||||
|
|
||||||
|
AttributeCache.clean(attr)
|
||||||
|
|
||||||
for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False):
|
for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False):
|
||||||
i.soft_delete()
|
i.soft_delete(commit=False)
|
||||||
|
|
||||||
for i in CITypeAttributeGroupItem.get_by(attr_id=_id, to_dict=False):
|
for i in CITypeAttributeGroupItem.get_by(attr_id=_id, to_dict=False):
|
||||||
i.soft_delete()
|
i.soft_delete(commit=False)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
@@ -240,9 +240,10 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
|||||||
try:
|
try:
|
||||||
response, _, _, _, _, _ = s.search()
|
response, _, _, _, _, _ = s.search()
|
||||||
for i in response:
|
for i in response:
|
||||||
if current_user.username not in (i.get('rd_duty') or []) and current_user.username not in \
|
if (current_user.username not in (i.get('rd_duty') or []) and
|
||||||
(i.get('op_duty') or []) and current_user.nickname 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('op_duty') or []):
|
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(
|
return abort(403, ErrFormat.adt_target_expr_no_permission.format(
|
||||||
i.get("{}_name".format(i.get('ci_type')))))
|
i.get("{}_name".format(i.get('ci_type')))))
|
||||||
except SearchError as e:
|
except SearchError as e:
|
||||||
@@ -453,10 +454,12 @@ class AutoDiscoveryCICRUD(DBMixin):
|
|||||||
|
|
||||||
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
|
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
|
||||||
for r_adt in relation_adts:
|
for r_adt in relation_adts:
|
||||||
if r_adt.relation and ci_id is not None:
|
if not r_adt.relation or ci_id is None:
|
||||||
ad_key, cmdb_key = None, {}
|
continue
|
||||||
for ad_key in r_adt.relation:
|
for ad_key in r_adt.relation:
|
||||||
cmdb_key = r_adt.relation[ad_key]
|
if not adc.instance.get(ad_key):
|
||||||
|
continue
|
||||||
|
cmdb_key = r_adt.relation[ad_key]
|
||||||
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'),
|
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'),
|
||||||
adc.instance.get(ad_key))
|
adc.instance.get(ad_key))
|
||||||
s = search(query)
|
s = search(query)
|
||||||
|
@@ -2,14 +2,11 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import requests
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from api.extensions import cache
|
from api.extensions import cache
|
||||||
from api.extensions import db
|
|
||||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||||
from api.models.cmdb import Attribute
|
from api.models.cmdb import Attribute
|
||||||
from api.models.cmdb import CI
|
|
||||||
from api.models.cmdb import CIType
|
from api.models.cmdb import CIType
|
||||||
from api.models.cmdb import CITypeAttribute
|
from api.models.cmdb import CITypeAttribute
|
||||||
from api.models.cmdb import RelationType
|
from api.models.cmdb import RelationType
|
||||||
@@ -34,6 +31,7 @@ class AttributeCache(object):
|
|||||||
attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
|
attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
|
||||||
if attr is not None:
|
if attr is not None:
|
||||||
cls.set(attr)
|
cls.set(attr)
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -67,6 +65,7 @@ class CITypeCache(object):
|
|||||||
ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
|
ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
|
||||||
if ct is not None:
|
if ct is not None:
|
||||||
cls.set(ct)
|
cls.set(ct)
|
||||||
|
|
||||||
return ct
|
return ct
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -98,6 +97,7 @@ class RelationTypeCache(object):
|
|||||||
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
|
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
|
||||||
if ct is not None:
|
if ct is not None:
|
||||||
cls.set(ct)
|
cls.set(ct)
|
||||||
|
|
||||||
return ct
|
return ct
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -133,12 +133,15 @@ class CITypeAttributesCache(object):
|
|||||||
attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
|
attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
|
||||||
if not attrs:
|
if not attrs:
|
||||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||||
|
|
||||||
if not attrs:
|
if not attrs:
|
||||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||||
if ci_type is not None:
|
if ci_type is not None:
|
||||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||||
|
|
||||||
if attrs is not None:
|
if attrs is not None:
|
||||||
cls.set(key, attrs)
|
cls.set(key, attrs)
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -155,13 +158,16 @@ class CITypeAttributesCache(object):
|
|||||||
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
|
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
|
||||||
if not attrs:
|
if not attrs:
|
||||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||||
|
|
||||||
if not attrs:
|
if not attrs:
|
||||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||||
if ci_type is not None:
|
if ci_type is not None:
|
||||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||||
|
|
||||||
if attrs is not None:
|
if attrs is not None:
|
||||||
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
|
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
|
||||||
cls.set2(key, attrs)
|
cls.set2(key, attrs)
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -201,13 +207,13 @@ class CITypeAttributeCache(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, type_id, attr_id):
|
def get(cls, type_id, attr_id):
|
||||||
|
|
||||||
attr = cache.get(cls.PREFIX_ID.format(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 cache.get(cls.PREFIX_ID.format(type_id, attr_id))
|
||||||
if not attr:
|
attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
||||||
attr = CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
|
||||||
if attr is not None:
|
if attr is not None:
|
||||||
cls.set(type_id, attr_id, attr)
|
cls.set(type_id, attr_id, attr)
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -241,53 +247,72 @@ class CMDBCounterCache(object):
|
|||||||
result = {}
|
result = {}
|
||||||
for custom in customs:
|
for custom in customs:
|
||||||
if custom['category'] == 0:
|
if custom['category'] == 0:
|
||||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
res = cls.sum_counter(custom)
|
||||||
elif custom['category'] == 1:
|
elif custom['category'] == 1:
|
||||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
res = cls.attribute_counter(custom)
|
||||||
elif custom['category'] == 2:
|
else:
|
||||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
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
|
||||||
|
|
||||||
cls.set(result)
|
cls.set(result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, custom):
|
def update(cls, custom, flush=True):
|
||||||
result = cache.get(cls.KEY) or {}
|
result = cache.get(cls.KEY) or {}
|
||||||
if not result:
|
if not result:
|
||||||
result = cls.reset()
|
result = cls.reset()
|
||||||
|
|
||||||
if custom['category'] == 0:
|
if custom['category'] == 0:
|
||||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
res = cls.sum_counter(custom)
|
||||||
elif custom['category'] == 1:
|
elif custom['category'] == 1:
|
||||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
res = cls.attribute_counter(custom)
|
||||||
elif custom['category'] == 2:
|
else:
|
||||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
res = cls.relation_counter(custom.get('type_id'),
|
||||||
|
custom.get('level'),
|
||||||
|
custom.get('options', {}).get('filter', ''),
|
||||||
|
custom.get('options', {}).get('type_ids', ''))
|
||||||
|
|
||||||
cls.set(result)
|
if res and flush:
|
||||||
|
result[custom['id']] = res
|
||||||
|
cls.set(result)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def summary_counter(type_id):
|
def relation_counter(type_id, level, other_filer, type_ids):
|
||||||
return db.session.query(CI.id).filter(CI.deleted.is_(False)).filter(CI.type_id == type_id).count()
|
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
|
||||||
|
|
||||||
@staticmethod
|
query = "_type:{}".format(type_id)
|
||||||
def relation_counter(type_id, level):
|
s = search(query, count=1000000)
|
||||||
|
try:
|
||||||
|
type_names, _, _, _, _, _ = s.search()
|
||||||
|
except SearchError as e:
|
||||||
|
current_app.logger.error(e)
|
||||||
|
return
|
||||||
|
|
||||||
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]
|
type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names]
|
||||||
|
|
||||||
url = "{}/ci_relations/statistics?root_ids={}&level={}".format(
|
s = RelSearch([i[0] for i in type_id_names], level, other_filer or '')
|
||||||
uri, ','.join([i[0] for i in type_id_names]), level)
|
try:
|
||||||
stats = requests.get(url).json()
|
stats = s.statistics(type_ids)
|
||||||
|
except SearchError as e:
|
||||||
|
current_app.logger.error(e)
|
||||||
|
return
|
||||||
|
|
||||||
id2name = dict(type_id_names)
|
id2name = dict(type_id_names)
|
||||||
type_ids = set()
|
type_ids = set()
|
||||||
for i in (stats.get('detail') or []):
|
for i in (stats.get('detail') or []):
|
||||||
for j in stats['detail'][i]:
|
for j in stats['detail'][i]:
|
||||||
type_ids.add(j)
|
type_ids.add(j)
|
||||||
|
|
||||||
for type_id in type_ids:
|
for type_id in type_ids:
|
||||||
_type = CITypeCache.get(type_id)
|
_type = CITypeCache.get(type_id)
|
||||||
id2name[type_id] = _type and _type.alias
|
id2name[type_id] = _type and _type.alias
|
||||||
@@ -307,9 +332,100 @@ class CMDBCounterCache(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attribute_counter(type_id, attr_id):
|
def attribute_counter(custom):
|
||||||
uri = current_app.config.get('CMDB_API')
|
from api.lib.cmdb.search import SearchError
|
||||||
url = "{}/ci/s?q=_type:{}&fl={}&facet={}".format(uri, type_id, attr_id, attr_id)
|
from api.lib.cmdb.search.ci import search
|
||||||
res = requests.get(url).json()
|
from api.lib.cmdb.utils import ValueTypeMap
|
||||||
if res.get('facet'):
|
|
||||||
return dict([i[:2] for i in list(res.get('facet').values())[0]])
|
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])))
|
||||||
|
try:
|
||||||
|
attr2value_type = [AttributeCache.get(i).value_type for i in attr_ids]
|
||||||
|
except AttributeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
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[ValueTypeMap.serialize2[attr2value_type[0]](str(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][ValueTypeMap.serialize2[attr2value_type[1]](str(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][ValueTypeMap.serialize2[attr2value_type[2]](str(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
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@@ -24,28 +25,37 @@ from api.lib.cmdb.const import CMDB_QUEUE
|
|||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import ExistPolicy
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
from api.lib.cmdb.const import OperateType
|
from api.lib.cmdb.const import OperateType
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RetKey
|
from api.lib.cmdb.const import RetKey
|
||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
from api.lib.cmdb.history import CIRelationHistoryManager
|
from api.lib.cmdb.history import CIRelationHistoryManager
|
||||||
|
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.cmdb.utils import TableMap
|
from api.lib.cmdb.utils import TableMap
|
||||||
from api.lib.cmdb.utils import ValueTypeMap
|
from api.lib.cmdb.utils import ValueTypeMap
|
||||||
from api.lib.cmdb.value import AttributeValueManager
|
from api.lib.cmdb.value import AttributeValueManager
|
||||||
from api.lib.decorator import kwargs_required
|
from api.lib.decorator import kwargs_required
|
||||||
|
from api.lib.notify import notify_send
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.lib.perm.acl.acl import validate_permission
|
from api.lib.perm.acl.acl import validate_permission
|
||||||
from api.lib.utils import Lock
|
from api.lib.utils import Lock
|
||||||
from api.lib.utils import handle_arg_list
|
from api.lib.utils import handle_arg_list
|
||||||
|
from api.lib.webhook import webhook_request
|
||||||
|
from api.models.cmdb import AttributeHistory
|
||||||
|
from api.models.cmdb import AutoDiscoveryCI
|
||||||
from api.models.cmdb import CI
|
from api.models.cmdb import CI
|
||||||
from api.models.cmdb import CIRelation
|
from api.models.cmdb import CIRelation
|
||||||
from api.models.cmdb import CITypeAttribute
|
from api.models.cmdb import CITypeAttribute
|
||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.tasks.cmdb import ci_cache
|
from api.tasks.cmdb import ci_cache
|
||||||
from api.tasks.cmdb import ci_delete
|
from api.tasks.cmdb import ci_delete
|
||||||
|
from api.tasks.cmdb import ci_delete_trigger
|
||||||
|
from api.tasks.cmdb import ci_relation_add
|
||||||
from api.tasks.cmdb import ci_relation_cache
|
from api.tasks.cmdb import ci_relation_cache
|
||||||
from api.tasks.cmdb import ci_relation_delete
|
from api.tasks.cmdb import ci_relation_delete
|
||||||
|
|
||||||
@@ -66,11 +76,13 @@ class CIManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_type_name(ci_id):
|
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)))
|
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
|
return CITypeCache.get(ci.type_id).name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_type(ci_id):
|
def get_type(ci_id):
|
||||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(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)
|
return CITypeCache.get(ci.type_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -92,9 +104,7 @@ class CIManager(object):
|
|||||||
|
|
||||||
res = dict()
|
res = dict()
|
||||||
|
|
||||||
if need_children:
|
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
|
||||||
res.update(children)
|
|
||||||
|
|
||||||
ci_type = CITypeCache.get(ci.type_id)
|
ci_type = CITypeCache.get(ci.type_id)
|
||||||
res["ci_type"] = ci_type.name
|
res["ci_type"] = ci_type.name
|
||||||
@@ -161,14 +171,11 @@ class CIManager(object):
|
|||||||
|
|
||||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||||
|
|
||||||
if valid:
|
valid and cls.valid_ci_only_read(ci)
|
||||||
cls.valid_ci_only_read(ci)
|
|
||||||
|
|
||||||
res = dict()
|
res = dict()
|
||||||
|
|
||||||
if need_children:
|
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
|
||||||
res.update(children)
|
|
||||||
|
|
||||||
ci_type = CITypeCache.get(ci.type_id)
|
ci_type = CITypeCache.get(ci.type_id)
|
||||||
res["ci_type"] = ci_type.name
|
res["ci_type"] = ci_type.name
|
||||||
@@ -247,7 +254,7 @@ class CIManager(object):
|
|||||||
for i in unique_constraints:
|
for i in unique_constraints:
|
||||||
attr_ids.extend(i.attr_ids)
|
attr_ids.extend(i.attr_ids)
|
||||||
|
|
||||||
attrs = [AttributeCache.get(i) for i in list(set(attr_ids))]
|
attrs = [AttributeCache.get(i) for i in set(attr_ids)]
|
||||||
id2name = {i.id: i.name for i in attrs if i}
|
id2name = {i.id: i.name for i in attrs if i}
|
||||||
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
|
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
|
||||||
if not_existed_fields and ci_id is not None:
|
if not_existed_fields and ci_id is not None:
|
||||||
@@ -292,7 +299,7 @@ class CIManager(object):
|
|||||||
_is_admin=False,
|
_is_admin=False,
|
||||||
**ci_dict):
|
**ci_dict):
|
||||||
"""
|
"""
|
||||||
|
add ci
|
||||||
:param ci_type_name:
|
:param ci_type_name:
|
||||||
:param exist_policy: replace or reject or need
|
:param exist_policy: replace or reject or need
|
||||||
:param _no_attribute_policy: ignore or reject
|
:param _no_attribute_policy: ignore or reject
|
||||||
@@ -307,9 +314,7 @@ class CIManager(object):
|
|||||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||||
|
|
||||||
unique_value = ci_dict.get(unique_key.name)
|
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||||
unique_value = unique_value or 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))
|
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||||
|
|
||||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
attrs = CITypeAttributesCache.get2(ci_type_name)
|
||||||
@@ -332,10 +337,6 @@ class CIManager(object):
|
|||||||
if exist_policy == ExistPolicy.NEED:
|
if exist_policy == ExistPolicy.NEED:
|
||||||
return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value)))
|
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 {}
|
limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {}
|
||||||
|
|
||||||
if existed is None: # set default
|
if existed is None: # set default
|
||||||
@@ -366,13 +367,18 @@ class CIManager(object):
|
|||||||
|
|
||||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||||
|
|
||||||
|
ref_ci_dict = dict()
|
||||||
for k in ci_dict:
|
for k in ci_dict:
|
||||||
if k not in ci_type_attrs_name and k not in ci_type_attrs_alias and \
|
if k.startswith("$") and "." in k:
|
||||||
_no_attribute_policy == ExistPolicy.REJECT:
|
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):
|
||||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||||
|
|
||||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and \
|
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:
|
ci_type_attrs_alias.get(k) not in limit_attrs):
|
||||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
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}
|
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}
|
||||||
@@ -380,16 +386,20 @@ class CIManager(object):
|
|||||||
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
|
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
|
||||||
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
|
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
|
||||||
|
|
||||||
|
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
|
||||||
try:
|
try:
|
||||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||||
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr)
|
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||||
except BadRequest as e:
|
except BadRequest as e:
|
||||||
if existed is None:
|
if existed is None:
|
||||||
cls.delete(ci.id)
|
cls.delete(ci.id)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if record_id: # has change
|
if record_id: # has change
|
||||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci.id, operate_type, record_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
|
return ci.id
|
||||||
|
|
||||||
@@ -426,20 +436,25 @@ class CIManager(object):
|
|||||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr)
|
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
|
||||||
except BadRequest as e:
|
except BadRequest as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if record_id: # has change
|
if record_id: # has change
|
||||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_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
|
@staticmethod
|
||||||
def update_unique_value(ci_id, unique_name, unique_value):
|
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)))
|
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||||
|
|
||||||
AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci)
|
key2attr = {unique_name: AttributeCache.get(unique_name)}
|
||||||
|
record_id = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr)
|
||||||
|
|
||||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, ci_id):
|
def delete(cls, ci_id):
|
||||||
@@ -450,26 +465,42 @@ class CIManager(object):
|
|||||||
ci_dict = cls.get_cis_by_ids([ci_id])
|
ci_dict = cls.get_cis_by_ids([ci_id])
|
||||||
ci_dict = ci_dict and ci_dict[0]
|
ci_dict = ci_dict and ci_dict[0]
|
||||||
|
|
||||||
|
triggers = CITriggerManager.get(ci_dict['_type'])
|
||||||
|
for trigger in triggers:
|
||||||
|
option = trigger['option']
|
||||||
|
if not option.get('enable') or option.get('action') != OperateType.DELETE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.get('filter') and not CITriggerManager.ci_filter(ci_dict.get('_id'), option['filter']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||||
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
||||||
for attr_name in attr_names:
|
for attr_name in attr_names:
|
||||||
value_table = TableMap(attr_name=attr_name).table
|
value_table = TableMap(attr_name=attr_name).table
|
||||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||||
item.delete()
|
item.delete(commit=False)
|
||||||
|
|
||||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
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)
|
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||||
item.delete()
|
item.delete(commit=False)
|
||||||
|
|
||||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
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)
|
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||||
item.delete()
|
item.delete(commit=False)
|
||||||
|
|
||||||
ci.delete() # TODO: soft 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()
|
||||||
|
|
||||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||||
|
|
||||||
ci_delete.apply_async([ci.id], queue=CMDB_QUEUE)
|
ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
return ci_id
|
return ci_id
|
||||||
|
|
||||||
@@ -480,11 +511,8 @@ class CIManager(object):
|
|||||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||||
value_table = TableMap(attr=unique_key).table
|
value_table = TableMap(attr=unique_key).table
|
||||||
|
|
||||||
v = value_table.get_by(attr_id=unique_key.id,
|
v = (value_table.get_by(attr_id=unique_key.id, value=unique_value, to_dict=False, first=True) or
|
||||||
value=unique_value,
|
abort(404, ErrFormat.not_found))
|
||||||
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)))
|
ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id)))
|
||||||
|
|
||||||
@@ -530,6 +558,7 @@ class CIManager(object):
|
|||||||
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
||||||
heartbeat_dict.get(i.get("_id"))) for i in res
|
heartbeat_dict.get(i.get("_id"))) for i in res
|
||||||
if i.get("private_ip")]
|
if i.get("private_ip")]
|
||||||
|
|
||||||
return numfound, result
|
return numfound, result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -649,6 +678,7 @@ class CIManager(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
current_app.logger.warning("cache not hit...............")
|
current_app.logger.warning("cache not hit...............")
|
||||||
|
|
||||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
|
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
|
||||||
|
|
||||||
|
|
||||||
@@ -674,6 +704,7 @@ class CIRelationManager(object):
|
|||||||
ci_type = CITypeCache.get(type_id)
|
ci_type = CITypeCache.get(type_id)
|
||||||
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
|
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
|
||||||
res[ci_type.name] = children
|
res[ci_type.name] = children
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -745,17 +776,28 @@ class CIRelationManager(object):
|
|||||||
return ci_ids
|
return ci_ids
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_constraint(first_ci_id, second_ci_id, type_relation):
|
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
|
||||||
|
db.session.remove()
|
||||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||||
return
|
return
|
||||||
|
|
||||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id, relation_type_id=type_relation.relation_type_id)
|
first_existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id, relation_type_id=type_relation.relation_type_id)
|
relation_type_id=type_relation.relation_type_id, to_dict=False)
|
||||||
if type_relation.constraint == ConstraintEnum.One2One and (first_existed or second_existed):
|
second_existed = CIRelation.get_by(second_ci_id=second_ci_id,
|
||||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
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"))
|
||||||
|
|
||||||
if type_relation.constraint == ConstraintEnum.One2Many and second_existed:
|
for i in second_existed:
|
||||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
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"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||||
@@ -794,15 +836,17 @@ class CIRelationManager(object):
|
|||||||
else:
|
else:
|
||||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||||
|
|
||||||
cls._check_constraint(first_ci_id, second_ci_id, type_relation)
|
with Lock("ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id), need_lock=True):
|
||||||
|
|
||||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||||
second_ci_id=second_ci_id,
|
|
||||||
relation_type_id=relation_type_id)
|
|
||||||
|
|
||||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||||
|
second_ci_id=second_ci_id,
|
||||||
|
relation_type_id=relation_type_id)
|
||||||
|
|
||||||
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||||
|
|
||||||
|
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
|
||||||
|
|
||||||
if more is not None:
|
if more is not None:
|
||||||
existed.upadte(more=more)
|
existed.upadte(more=more)
|
||||||
@@ -850,12 +894,12 @@ class CIRelationManager(object):
|
|||||||
:param children:
|
:param children:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if parents is not None and isinstance(parents, list):
|
if isinstance(parents, list):
|
||||||
for parent_id in parents:
|
for parent_id in parents:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.add(parent_id, ci_id)
|
cls.add(parent_id, ci_id)
|
||||||
|
|
||||||
if children is not None and isinstance(children, list):
|
if isinstance(children, list):
|
||||||
for child_id in children:
|
for child_id in children:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.add(ci_id, child_id)
|
cls.add(ci_id, child_id)
|
||||||
@@ -869,7 +913,184 @@ class CIRelationManager(object):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if parents is not None and isinstance(parents, list):
|
if isinstance(parents, list):
|
||||||
for parent_id in parents:
|
for parent_id in parents:
|
||||||
for ci_id in ci_ids:
|
for ci_id in ci_ids:
|
||||||
cls.delete_2(parent_id, ci_id)
|
cls.delete_2(parent_id, ci_id)
|
||||||
|
|
||||||
|
|
||||||
|
class CITriggerManager(object):
|
||||||
|
@staticmethod
|
||||||
|
def get(type_id):
|
||||||
|
db.session.remove()
|
||||||
|
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_old_attr_value(record_id, ci_dict):
|
||||||
|
attr_history = AttributeHistory.get_by(record_id=record_id, to_dict=False)
|
||||||
|
attr_dict = dict()
|
||||||
|
for attr_h in attr_history:
|
||||||
|
attr_dict['old_{}'.format(AttributeCache.get(attr_h.attr_id).name)] = attr_h.old
|
||||||
|
|
||||||
|
ci_dict.update({'old_{}'.format(k): ci_dict[k] for k in ci_dict})
|
||||||
|
|
||||||
|
ci_dict.update(attr_dict)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _exec_webhook(cls, operate_type, webhook, ci_dict, trigger_id, trigger_name, record_id, ci_id=None, app=None):
|
||||||
|
app = app or current_app
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
if operate_type == OperateType.UPDATE:
|
||||||
|
cls._update_old_attr_value(record_id, ci_dict)
|
||||||
|
|
||||||
|
if ci_id is not None:
|
||||||
|
ci_dict = CIManager().get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = webhook_request(webhook, ci_dict).text
|
||||||
|
is_ok = True
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning("exec webhook failed: {}".format(e))
|
||||||
|
response = e
|
||||||
|
is_ok = False
|
||||||
|
|
||||||
|
CITriggerHistoryManager.add(operate_type,
|
||||||
|
record_id,
|
||||||
|
ci_dict.get('_id'),
|
||||||
|
trigger_id,
|
||||||
|
trigger_name,
|
||||||
|
is_ok=is_ok,
|
||||||
|
webhook=response)
|
||||||
|
|
||||||
|
return is_ok
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _exec_notify(cls, operate_type, notify, ci_dict, trigger_id, trigger_name, record_id, ci_id=None, app=None):
|
||||||
|
app = app or current_app
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
|
||||||
|
if ci_id is not None:
|
||||||
|
ci_dict = CIManager().get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||||
|
|
||||||
|
if operate_type == OperateType.UPDATE:
|
||||||
|
cls._update_old_attr_value(record_id, ci_dict)
|
||||||
|
|
||||||
|
is_ok = True
|
||||||
|
response = ''
|
||||||
|
for method in (notify.get('method') or []):
|
||||||
|
try:
|
||||||
|
res = notify_send(notify.get('subject'), notify.get('body'), [method],
|
||||||
|
notify.get('tos'), ci_dict)
|
||||||
|
response = "{}\n{}".format(response, res)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning("send notify failed: {}".format(e))
|
||||||
|
response = "{}\n{}".format(response, e)
|
||||||
|
is_ok = False
|
||||||
|
|
||||||
|
CITriggerHistoryManager.add(operate_type,
|
||||||
|
record_id,
|
||||||
|
ci_dict.get('_id'),
|
||||||
|
trigger_id,
|
||||||
|
trigger_name,
|
||||||
|
is_ok=is_ok,
|
||||||
|
notify=response.strip())
|
||||||
|
|
||||||
|
return is_ok
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ci_filter(ci_id, other_filter):
|
||||||
|
from api.lib.cmdb.search import SearchError
|
||||||
|
from api.lib.cmdb.search.ci import search
|
||||||
|
|
||||||
|
query = "{},_id:{}".format(other_filter, ci_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, _, _, _, numfound, _ = search(query).search()
|
||||||
|
return numfound
|
||||||
|
except SearchError as e:
|
||||||
|
current_app.logger.warning("ci search failed: {}".format(e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fire(cls, operate_type, ci_dict, record_id):
|
||||||
|
type_id = ci_dict.get('_type')
|
||||||
|
triggers = cls.get(type_id) or []
|
||||||
|
|
||||||
|
for trigger in triggers:
|
||||||
|
option = trigger['option']
|
||||||
|
if not option.get('enable'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.get('filter') and not cls.ci_filter(ci_dict.get('_id'), option['filter']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.get('attr_ids') and isinstance(option['attr_ids'], list):
|
||||||
|
if not (set(option['attr_ids']) &
|
||||||
|
set([i.attr_id for i in AttributeHistory.get_by(record_id=record_id, to_dict=False)])):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.get('action') == operate_type:
|
||||||
|
cls.fire_by_trigger(trigger, operate_type, ci_dict, record_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fire_by_trigger(cls, trigger, operate_type, ci_dict, record_id=None):
|
||||||
|
option = trigger['option']
|
||||||
|
|
||||||
|
if option.get('webhooks'):
|
||||||
|
cls._exec_webhook(operate_type, option['webhooks'], ci_dict, trigger['id'],
|
||||||
|
option.get('name'), record_id)
|
||||||
|
|
||||||
|
elif option.get('notifies'):
|
||||||
|
cls._exec_notify(operate_type, option['notifies'], ci_dict, trigger['id'],
|
||||||
|
option.get('name'), record_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def waiting_cis(cls, trigger):
|
||||||
|
now = datetime.datetime.today()
|
||||||
|
|
||||||
|
config = trigger.option.get('notifies') or {}
|
||||||
|
|
||||||
|
delta_time = datetime.timedelta(days=(config.get('before_days', 0) or 0))
|
||||||
|
|
||||||
|
attr = AttributeCache.get(trigger.attr_id)
|
||||||
|
|
||||||
|
value_table = TableMap(attr=attr).table
|
||||||
|
|
||||||
|
values = value_table.get_by(attr_id=attr.id, to_dict=False)
|
||||||
|
|
||||||
|
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 trigger.option.get('filter') and not cls.ci_filter(v.ci_id, trigger.option['filter']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(v)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def trigger_notify(cls, trigger, ci):
|
||||||
|
"""
|
||||||
|
only for date attribute
|
||||||
|
:param trigger:
|
||||||
|
:param ci:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if (trigger.option.get('notifies', {}).get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
|
||||||
|
not trigger.option.get('notifies', {}).get('notify_at')):
|
||||||
|
|
||||||
|
if trigger.option.get('webhooks'):
|
||||||
|
threading.Thread(target=cls._exec_webhook, args=(
|
||||||
|
None, trigger.option['webhooks'], None, trigger.id, trigger.option.get('name'), None, ci.ci_id,
|
||||||
|
current_app._get_current_object())).start()
|
||||||
|
elif trigger.option.get('notifies'):
|
||||||
|
threading.Thread(target=cls._exec_notify, args=(
|
||||||
|
None, trigger.option['notifies'], None, trigger.id, trigger.option.get('name'), None, ci.ci_id,
|
||||||
|
current_app._get_current_object())).start()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
import toposort
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from toposort import toposort_flatten
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.cmdb.attribute import AttributeManager
|
from api.lib.cmdb.attribute import AttributeManager
|
||||||
@@ -16,7 +18,9 @@ from api.lib.cmdb.cache import CITypeCache
|
|||||||
from api.lib.cmdb.const import CITypeOperateType
|
from api.lib.cmdb.const import CITypeOperateType
|
||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
from api.lib.cmdb.const import CMDB_QUEUE
|
||||||
from api.lib.cmdb.const import ConstraintEnum
|
from api.lib.cmdb.const import ConstraintEnum
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
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 ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
from api.lib.cmdb.history import CITypeHistoryManager
|
from api.lib.cmdb.history import CITypeHistoryManager
|
||||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||||
@@ -27,7 +31,10 @@ from api.lib.decorator import kwargs_required
|
|||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
from api.lib.perm.acl.acl import is_app_admin
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.models.cmdb import Attribute
|
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 CI
|
||||||
|
from api.models.cmdb import CIFilterPerms
|
||||||
from api.models.cmdb import CIType
|
from api.models.cmdb import CIType
|
||||||
from api.models.cmdb import CITypeAttribute
|
from api.models.cmdb import CITypeAttribute
|
||||||
from api.models.cmdb import CITypeAttributeGroup
|
from api.models.cmdb import CITypeAttributeGroup
|
||||||
@@ -37,7 +44,9 @@ from api.models.cmdb import CITypeGroupItem
|
|||||||
from api.models.cmdb import CITypeRelation
|
from api.models.cmdb import CITypeRelation
|
||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
|
from api.models.cmdb import CustomDashboard
|
||||||
from api.models.cmdb import PreferenceRelationView
|
from api.models.cmdb import PreferenceRelationView
|
||||||
|
from api.models.cmdb import PreferenceSearchOption
|
||||||
from api.models.cmdb import PreferenceShowAttributes
|
from api.models.cmdb import PreferenceShowAttributes
|
||||||
from api.models.cmdb import PreferenceTreeView
|
from api.models.cmdb import PreferenceTreeView
|
||||||
from api.models.cmdb import RelationType
|
from api.models.cmdb import RelationType
|
||||||
@@ -55,6 +64,7 @@ class CITypeManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_name_by_id(type_id):
|
def get_name_by_id(type_id):
|
||||||
ci_type = CITypeCache.get(type_id)
|
ci_type = CITypeCache.get(type_id)
|
||||||
|
|
||||||
return ci_type and ci_type.name
|
return ci_type and ci_type.name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -66,7 +76,7 @@ class CITypeManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ci_types(type_name=None):
|
def get_ci_types(type_name=None):
|
||||||
resources = None
|
resources = None
|
||||||
if current_app.config.get('USE_ACL') and not is_app_admin():
|
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
|
||||||
resources = set([i.get('name') for i in ACLManager().get_resources("CIType")])
|
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)
|
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||||
@@ -105,11 +115,8 @@ class CITypeManager(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@kwargs_required("name")
|
@kwargs_required("name")
|
||||||
def add(cls, **kwargs):
|
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)
|
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
|
||||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
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"]
|
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
||||||
@@ -179,6 +186,7 @@ class CITypeManager(object):
|
|||||||
def set_enabled(cls, type_id, enabled=True):
|
def set_enabled(cls, type_id, enabled=True):
|
||||||
ci_type = cls.check_is_existed(type_id)
|
ci_type = cls.check_is_existed(type_id)
|
||||||
ci_type.update(enabled=enabled)
|
ci_type.update(enabled=enabled)
|
||||||
|
|
||||||
return type_id
|
return type_id
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -198,19 +206,21 @@ class CITypeManager(object):
|
|||||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
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):
|
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
||||||
item.soft_delete()
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
|
for item in CITypeRelation.get_by(child_id=type_id, to_dict=False):
|
||||||
item.soft_delete()
|
item.soft_delete(commit=False)
|
||||||
|
|
||||||
for item in PreferenceTreeView.get_by(type_id=type_id, to_dict=False):
|
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||||
item.soft_delete()
|
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 PreferenceShowAttributes.get_by(type_id=type_id, to_dict=False):
|
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||||
item.soft_delete()
|
item.delete(commit=False)
|
||||||
|
|
||||||
for item in CITypeGroupItem.get_by(type_id=type_id, to_dict=False):
|
db.session.commit()
|
||||||
item.soft_delete()
|
|
||||||
|
|
||||||
ci_type.soft_delete()
|
ci_type.soft_delete()
|
||||||
|
|
||||||
@@ -261,16 +271,17 @@ class CITypeGroupManager(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def add(name):
|
def add(name):
|
||||||
CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name))
|
CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name))
|
||||||
|
|
||||||
return CITypeGroup.create(name=name)
|
return CITypeGroup.create(name=name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update(gid, name, type_ids):
|
def update(gid, name, type_ids):
|
||||||
"""
|
"""
|
||||||
update part
|
update part
|
||||||
:param gid:
|
:param gid:
|
||||||
:param name:
|
:param name:
|
||||||
:param type_ids:
|
:param type_ids:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
existed = CITypeGroup.get_by_id(gid) or abort(
|
existed = CITypeGroup.get_by_id(gid) or abort(
|
||||||
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
|
||||||
@@ -327,6 +338,17 @@ class CITypeAttributeManager(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
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
|
@staticmethod
|
||||||
def get_attr_names_by_type_id(type_id):
|
def get_attr_names_by_type_id(type_id):
|
||||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||||
@@ -347,8 +369,19 @@ class CITypeAttributeManager(object):
|
|||||||
attr_dict.pop('choice_web_hook', None)
|
attr_dict.pop('choice_web_hook', None)
|
||||||
|
|
||||||
result.append(attr_dict)
|
result.append(attr_dict)
|
||||||
|
|
||||||
return result
|
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
|
@staticmethod
|
||||||
def _check(type_id, attr_ids):
|
def _check(type_id, attr_ids):
|
||||||
ci_type = CITypeManager.check_is_existed(type_id)
|
ci_type = CITypeManager.check_is_existed(type_id)
|
||||||
@@ -365,10 +398,10 @@ class CITypeAttributeManager(object):
|
|||||||
def add(cls, type_id, attr_ids=None, **kwargs):
|
def add(cls, type_id, attr_ids=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
add attributes to CIType
|
add attributes to CIType
|
||||||
:param type_id:
|
:param type_id:
|
||||||
:param attr_ids: list
|
:param attr_ids: list
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
attr_ids = list(set(attr_ids))
|
attr_ids = list(set(attr_ids))
|
||||||
|
|
||||||
@@ -395,9 +428,9 @@ class CITypeAttributeManager(object):
|
|||||||
def update(cls, type_id, attributes):
|
def update(cls, type_id, attributes):
|
||||||
"""
|
"""
|
||||||
update attributes to CIType
|
update attributes to CIType
|
||||||
:param type_id:
|
:param type_id:
|
||||||
:param attributes: list
|
:param attributes: list
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
cls._check(type_id, [i.get('attr_id') for i in attributes])
|
cls._check(type_id, [i.get('attr_id') for i in attributes])
|
||||||
|
|
||||||
@@ -425,9 +458,9 @@ class CITypeAttributeManager(object):
|
|||||||
def delete(cls, type_id, attr_ids=None):
|
def delete(cls, type_id, attr_ids=None):
|
||||||
"""
|
"""
|
||||||
delete attributes from CIType
|
delete attributes from CIType
|
||||||
:param type_id:
|
:param type_id:
|
||||||
:param attr_ids: list
|
:param attr_ids: list
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
from api.tasks.cmdb import ci_cache
|
from api.tasks.cmdb import ci_cache
|
||||||
|
|
||||||
@@ -534,6 +567,7 @@ class CITypeRelationManager(object):
|
|||||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
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["relation_type"] = relation_inst.relation_type.name
|
||||||
ci_type_dict["constraint"] = relation_inst.constraint
|
ci_type_dict["constraint"] = relation_inst.constraint
|
||||||
|
|
||||||
return ci_type_dict
|
return ci_type_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -542,6 +576,23 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
return [cls._wrap_relation_type_dict(child.child_id, child) for child in children]
|
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)
|
||||||
|
if children:
|
||||||
|
result.setdefault(level + 1, []).extend([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
|
@classmethod
|
||||||
def get_parents(cls, child_id):
|
def get_parents(cls, child_id):
|
||||||
parents = CITypeRelation.get_by(child_id=child_id, to_dict=False)
|
parents = CITypeRelation.get_by(child_id=child_id, to_dict=False)
|
||||||
@@ -564,6 +615,17 @@ class CITypeRelationManager(object):
|
|||||||
p = CITypeManager.check_is_existed(parent)
|
p = CITypeManager.check_is_existed(parent)
|
||||||
c = CITypeManager.check_is_existed(child)
|
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)
|
existed = cls._get(p.id, c.id)
|
||||||
if existed is not None:
|
if existed is not None:
|
||||||
existed.update(relation_type_id=relation_type_id,
|
existed.update(relation_type_id=relation_type_id,
|
||||||
@@ -592,8 +654,8 @@ class CITypeRelationManager(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, _id):
|
def delete(cls, _id):
|
||||||
ctr = CITypeRelation.get_by_id(_id) or \
|
ctr = (CITypeRelation.get_by_id(_id) or
|
||||||
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id)))
|
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id))))
|
||||||
ctr.soft_delete()
|
ctr.soft_delete()
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id,
|
CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id,
|
||||||
@@ -647,6 +709,7 @@ class CITypeAttributeGroupManager(object):
|
|||||||
:param name:
|
:param name:
|
||||||
:param group_order: group order
|
:param group_order: group order
|
||||||
:param attr_order:
|
:param attr_order:
|
||||||
|
:param is_update:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False)
|
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False)
|
||||||
@@ -687,8 +750,8 @@ class CITypeAttributeGroupManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(group_id):
|
def delete(group_id):
|
||||||
group = CITypeAttributeGroup.get_by_id(group_id) \
|
group = (CITypeAttributeGroup.get_by_id(group_id) or
|
||||||
or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id)))
|
abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id))))
|
||||||
group.soft_delete()
|
group.soft_delete()
|
||||||
|
|
||||||
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
|
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
|
||||||
@@ -800,6 +863,12 @@ class CITypeTemplateManager(object):
|
|||||||
for added_id in set(id2obj_dicts.keys()) - set(existed_ids):
|
for added_id in set(id2obj_dicts.keys()) - set(existed_ids):
|
||||||
if cls == CIType:
|
if cls == CIType:
|
||||||
CITypeManager.add(**id2obj_dicts[added_id])
|
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:
|
else:
|
||||||
cls.create(flush=True, **id2obj_dicts[added_id])
|
cls.create(flush=True, **id2obj_dicts[added_id])
|
||||||
|
|
||||||
@@ -957,8 +1026,8 @@ class CITypeTemplateManager(object):
|
|||||||
rule['uid'] = current_user.uid
|
rule['uid'] = current_user.uid
|
||||||
try:
|
try:
|
||||||
AutoDiscoveryCITypeCRUD.add(**rule)
|
AutoDiscoveryCITypeCRUD.add(**rule)
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||||
|
|
||||||
def import_template(self, tpt):
|
def import_template(self, tpt):
|
||||||
import time
|
import time
|
||||||
@@ -1097,16 +1166,18 @@ class CITypeUniqueConstraintManager(object):
|
|||||||
|
|
||||||
class CITypeTriggerManager(object):
|
class CITypeTriggerManager(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(type_id):
|
def get(type_id, to_dict=True):
|
||||||
return CITypeTrigger.get_by(type_id=type_id, to_dict=True)
|
return CITypeTrigger.get_by(type_id=type_id, to_dict=to_dict)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add(type_id, attr_id, notify):
|
def add(type_id, attr_id, option):
|
||||||
CITypeTrigger.get_by(type_id=type_id, attr_id=attr_id) and abort(400, ErrFormat.ci_type_trigger_duplicate)
|
for i in CITypeTrigger.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
|
||||||
|
if i.option == option:
|
||||||
|
return abort(400, ErrFormat.ci_type_trigger_duplicate)
|
||||||
|
|
||||||
not isinstance(notify, dict) and abort(400, ErrFormat.argument_invalid.format("notify"))
|
not isinstance(option, dict) and abort(400, ErrFormat.argument_invalid.format("option"))
|
||||||
|
|
||||||
trigger = CITypeTrigger.create(type_id=type_id, attr_id=attr_id, notify=notify)
|
trigger = CITypeTrigger.create(type_id=type_id, attr_id=attr_id, option=option)
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.ADD_TRIGGER,
|
CITypeHistoryManager.add(CITypeOperateType.ADD_TRIGGER,
|
||||||
type_id,
|
type_id,
|
||||||
@@ -1116,12 +1187,12 @@ class CITypeTriggerManager(object):
|
|||||||
return trigger.to_dict()
|
return trigger.to_dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update(_id, notify):
|
def update(_id, attr_id, option):
|
||||||
existed = CITypeTrigger.get_by_id(_id) or \
|
existed = (CITypeTrigger.get_by_id(_id) or
|
||||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||||
|
|
||||||
existed2 = existed.to_dict()
|
existed2 = existed.to_dict()
|
||||||
new = existed.update(notify=notify)
|
new = existed.update(attr_id=attr_id or None, option=option, filter_none=False)
|
||||||
|
|
||||||
CITypeHistoryManager.add(CITypeOperateType.UPDATE_TRIGGER,
|
CITypeHistoryManager.add(CITypeOperateType.UPDATE_TRIGGER,
|
||||||
existed.type_id,
|
existed.type_id,
|
||||||
@@ -1132,8 +1203,8 @@ class CITypeTriggerManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(_id):
|
def delete(_id):
|
||||||
existed = CITypeTrigger.get_by_id(_id) or \
|
existed = (CITypeTrigger.get_by_id(_id) or
|
||||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||||
|
|
||||||
existed.soft_delete()
|
existed.soft_delete()
|
||||||
|
|
||||||
@@ -1141,35 +1212,3 @@ class CITypeTriggerManager(object):
|
|||||||
existed.type_id,
|
existed.type_id,
|
||||||
trigger_id=_id,
|
trigger_id=_id,
|
||||||
change=existed.to_dict())
|
change=existed.to_dict())
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def waiting_cis(trigger):
|
|
||||||
now = datetime.datetime.today()
|
|
||||||
|
|
||||||
delta_time = datetime.timedelta(days=(trigger.notify.get('before_days', 0) or 0))
|
|
||||||
|
|
||||||
attr = AttributeCache.get(trigger.attr_id)
|
|
||||||
|
|
||||||
value_table = TableMap(attr=attr).table
|
|
||||||
|
|
||||||
values = value_table.get_by(attr_id=attr.id, to_dict=False)
|
|
||||||
|
|
||||||
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"):
|
|
||||||
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'):
|
|
||||||
from api.tasks.cmdb import trigger_notify
|
|
||||||
|
|
||||||
trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
@@ -99,5 +99,7 @@ CMDB_QUEUE = "one_cmdb_async"
|
|||||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||||
|
|
||||||
|
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||||
|
|
||||||
L_TYPE = None
|
L_TYPE = None
|
||||||
L_CI = None
|
L_CI = None
|
||||||
|
@@ -14,6 +14,14 @@ class CustomDashboardManager(object):
|
|||||||
def get():
|
def get():
|
||||||
return sorted(CustomDashboard.get_by(to_dict=True), key=lambda x: (x["category"], x['order']))
|
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
|
@staticmethod
|
||||||
def add(**kwargs):
|
def add(**kwargs):
|
||||||
from api.lib.cmdb.cache import CMDBCounterCache
|
from api.lib.cmdb.cache import CMDBCounterCache
|
||||||
@@ -23,9 +31,9 @@ class CustomDashboardManager(object):
|
|||||||
|
|
||||||
new = CustomDashboard.create(**kwargs)
|
new = CustomDashboard.create(**kwargs)
|
||||||
|
|
||||||
CMDBCounterCache.update(new.to_dict())
|
res = CMDBCounterCache.update(new.to_dict())
|
||||||
|
|
||||||
return new
|
return new, res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update(_id, **kwargs):
|
def update(_id, **kwargs):
|
||||||
@@ -35,9 +43,9 @@ class CustomDashboardManager(object):
|
|||||||
|
|
||||||
new = existed.update(**kwargs)
|
new = existed.update(**kwargs)
|
||||||
|
|
||||||
CMDBCounterCache.update(new.to_dict())
|
res = CMDBCounterCache.update(new.to_dict())
|
||||||
|
|
||||||
return new
|
return new, res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def batch_update(id2options):
|
def batch_update(id2options):
|
||||||
|
@@ -16,6 +16,7 @@ from api.lib.perm.acl.cache import UserCache
|
|||||||
from api.models.cmdb import Attribute
|
from api.models.cmdb import Attribute
|
||||||
from api.models.cmdb import AttributeHistory
|
from api.models.cmdb import AttributeHistory
|
||||||
from api.models.cmdb import CIRelationHistory
|
from api.models.cmdb import CIRelationHistory
|
||||||
|
from api.models.cmdb import CITriggerHistory
|
||||||
from api.models.cmdb import CITypeHistory
|
from api.models.cmdb import CITypeHistory
|
||||||
from api.models.cmdb import CITypeTrigger
|
from api.models.cmdb import CITypeTrigger
|
||||||
from api.models.cmdb import CITypeUniqueConstraint
|
from api.models.cmdb import CITypeUniqueConstraint
|
||||||
@@ -176,8 +177,8 @@ class AttributeHistoryManger(object):
|
|||||||
def get_record_detail(record_id):
|
def get_record_detail(record_id):
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
|
||||||
record = OperationRecord.get_by_id(record_id) or \
|
record = (OperationRecord.get_by_id(record_id) or
|
||||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id)))
|
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id))))
|
||||||
|
|
||||||
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
||||||
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
@@ -286,3 +287,68 @@ class CITypeHistoryManager(object):
|
|||||||
change=change)
|
change=change)
|
||||||
|
|
||||||
CITypeHistory.create(**payload)
|
CITypeHistory.create(**payload)
|
||||||
|
|
||||||
|
|
||||||
|
class CITriggerHistoryManager(object):
|
||||||
|
@staticmethod
|
||||||
|
def get(page, page_size, type_id=None, trigger_id=None, operate_type=None):
|
||||||
|
query = CITriggerHistory.get_by(only_query=True)
|
||||||
|
if type_id:
|
||||||
|
query = query.filter(CITriggerHistory.type_id == type_id)
|
||||||
|
|
||||||
|
if trigger_id:
|
||||||
|
query = query.filter(CITriggerHistory.trigger_id == trigger_id)
|
||||||
|
|
||||||
|
if operate_type:
|
||||||
|
query = query.filter(CITriggerHistory.operate_type == operate_type)
|
||||||
|
|
||||||
|
numfound = query.count()
|
||||||
|
|
||||||
|
query = query.order_by(CITriggerHistory.id.desc())
|
||||||
|
result = query.offset((page - 1) * page_size).limit(page_size)
|
||||||
|
result = [i.to_dict() for i in result]
|
||||||
|
for res in result:
|
||||||
|
if res.get('trigger_id'):
|
||||||
|
trigger = CITypeTrigger.get_by_id(res['trigger_id'])
|
||||||
|
res['trigger'] = trigger and trigger.to_dict()
|
||||||
|
|
||||||
|
return numfound, result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_by_ci_id(ci_id):
|
||||||
|
res = db.session.query(CITriggerHistory, CITypeTrigger).join(
|
||||||
|
CITypeTrigger, CITypeTrigger.id == CITriggerHistory.trigger_id).filter(
|
||||||
|
CITriggerHistory.ci_id == ci_id).order_by(CITriggerHistory.id.desc())
|
||||||
|
|
||||||
|
result = []
|
||||||
|
id2trigger = dict()
|
||||||
|
for i in res:
|
||||||
|
hist = i.CITriggerHistory
|
||||||
|
item = dict(is_ok=hist.is_ok,
|
||||||
|
operate_type=hist.operate_type,
|
||||||
|
notify=hist.notify,
|
||||||
|
trigger_id=hist.trigger_id,
|
||||||
|
trigger_name=hist.trigger_name,
|
||||||
|
webhook=hist.webhook,
|
||||||
|
created_at=hist.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
record_id=hist.record_id,
|
||||||
|
hid=hist.id
|
||||||
|
)
|
||||||
|
if i.CITypeTrigger.id not in id2trigger:
|
||||||
|
id2trigger[i.CITypeTrigger.id] = i.CITypeTrigger.to_dict()
|
||||||
|
|
||||||
|
result.append(item)
|
||||||
|
|
||||||
|
return dict(items=result, id2trigger=id2trigger)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add(operate_type, record_id, ci_id, trigger_id, trigger_name, is_ok=False, notify=None, webhook=None):
|
||||||
|
|
||||||
|
CITriggerHistory.create(operate_type=operate_type,
|
||||||
|
record_id=record_id,
|
||||||
|
ci_id=ci_id,
|
||||||
|
trigger_id=trigger_id,
|
||||||
|
trigger_name=trigger_name,
|
||||||
|
is_ok=is_ok,
|
||||||
|
notify=notify,
|
||||||
|
webhook=webhook)
|
||||||
|
@@ -37,10 +37,12 @@ class PreferenceManager(object):
|
|||||||
def get_types(instance=False, tree=False):
|
def get_types(instance=False, tree=False):
|
||||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||||
PreferenceShowAttributes.deleted.is_(False)).group_by(PreferenceShowAttributes.type_id).all() \
|
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||||
if instance else []
|
PreferenceShowAttributes.type_id).all() if instance else []
|
||||||
|
|
||||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||||
type_ids = list(set([i.type_id for i in types + tree_types]))
|
type_ids = set([i.type_id for i in types + tree_types])
|
||||||
|
|
||||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@@ -42,7 +42,7 @@ FACET_QUERY1 = """
|
|||||||
|
|
||||||
FACET_QUERY = """
|
FACET_QUERY = """
|
||||||
SELECT {0}.value,
|
SELECT {0}.value,
|
||||||
count({0}.ci_id)
|
count(distinct({0}.ci_id))
|
||||||
FROM {0}
|
FROM {0}
|
||||||
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
||||||
WHERE {0}.attr_id={2:d}
|
WHERE {0}.attr_id={2:d}
|
||||||
|
@@ -24,21 +24,21 @@ class RelationTypeManager(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add(name):
|
def add(name):
|
||||||
RelationType.get_by(name=name, first=True, to_dict=False) and \
|
RelationType.get_by(name=name, first=True, to_dict=False) and abort(
|
||||||
abort(400, ErrFormat.relation_type_exists.format(name))
|
400, ErrFormat.relation_type_exists.format(name))
|
||||||
|
|
||||||
return RelationType.create(name=name)
|
return RelationType.create(name=name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update(rel_id, name):
|
def update(rel_id, name):
|
||||||
existed = RelationType.get_by_id(rel_id) or \
|
existed = RelationType.get_by_id(rel_id) or abort(
|
||||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||||
|
|
||||||
return existed.update(name=name)
|
return existed.update(name=name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(rel_id):
|
def delete(rel_id):
|
||||||
existed = RelationType.get_by_id(rel_id) or \
|
existed = RelationType.get_by_id(rel_id) or abort(
|
||||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||||
|
|
||||||
existed.soft_delete()
|
existed.soft_delete()
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
@@ -141,6 +141,10 @@ class Search(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _in_query_handler(attr, v, is_not):
|
def _in_query_handler(attr, v, is_not):
|
||||||
new_v = v[1:-1].split(";")
|
new_v = v[1:-1].split(";")
|
||||||
|
|
||||||
|
if attr.value_type == ValueTypeEnum.DATE:
|
||||||
|
new_v = ["{} 00:00:00".format(i) for i in new_v if len(i) == 10]
|
||||||
|
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
in_query = " OR {0}.value ".format(table_name).join(['{0} "{1}"'.format(
|
in_query = " OR {0}.value ".format(table_name).join(['{0} "{1}"'.format(
|
||||||
"NOT LIKE" if is_not else "LIKE",
|
"NOT LIKE" if is_not else "LIKE",
|
||||||
@@ -151,6 +155,11 @@ class Search(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _range_query_handler(attr, v, is_not):
|
def _range_query_handler(attr, v, is_not):
|
||||||
start, end = [x.strip() for x in v[1:-1].split("_TO_")]
|
start, end = [x.strip() for x in v[1:-1].split("_TO_")]
|
||||||
|
|
||||||
|
if attr.value_type == ValueTypeEnum.DATE:
|
||||||
|
start = "{} 00:00:00".format(start) if len(start) == 10 else start
|
||||||
|
end = "{} 00:00:00".format(end) if len(end) == 10 else end
|
||||||
|
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
range_query = "{0} '{1}' AND '{2}'".format(
|
range_query = "{0} '{1}' AND '{2}'".format(
|
||||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||||
@@ -162,8 +171,14 @@ class Search(object):
|
|||||||
def _comparison_query_handler(attr, v):
|
def _comparison_query_handler(attr, v):
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
if v.startswith(">=") or v.startswith("<="):
|
if v.startswith(">=") or v.startswith("<="):
|
||||||
|
if attr.value_type == ValueTypeEnum.DATE and len(v[2:]) == 10:
|
||||||
|
v = "{} 00:00:00".format(v)
|
||||||
|
|
||||||
comparison_query = "{0} '{1}'".format(v[:2], v[2:].replace("*", "%"))
|
comparison_query = "{0} '{1}'".format(v[:2], v[2:].replace("*", "%"))
|
||||||
else:
|
else:
|
||||||
|
if attr.value_type == ValueTypeEnum.DATE and len(v[1:]) == 10:
|
||||||
|
v = "{} 00:00:00".format(v)
|
||||||
|
|
||||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||||
return _query_sql
|
return _query_sql
|
||||||
@@ -239,16 +254,14 @@ class Search(object):
|
|||||||
attr_id = attr.id
|
attr_id = attr.id
|
||||||
|
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
||||||
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
||||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
||||||
new_table = _v_query_sql
|
new_table = _v_query_sql
|
||||||
|
|
||||||
if self.only_type_query or not self.type_id_list:
|
if self.only_type_query or not self.type_id_list:
|
||||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \
|
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
||||||
"FROM ({0}) AS C " \
|
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
||||||
"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:
|
elif self.type_id_list:
|
||||||
self.query_sql = """SELECT C.ci_id
|
self.query_sql = """SELECT C.ci_id
|
||||||
@@ -287,7 +300,7 @@ class Search(object):
|
|||||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
||||||
|
|
||||||
elif operator == "~":
|
elif operator == "~":
|
||||||
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
||||||
WHERE {3}.ci_id is NULL""".format(query_sql, alias, _query_sql, alias + "A")
|
WHERE {3}.ci_id is NULL""".format(query_sql, alias, _query_sql, alias + "A")
|
||||||
|
|
||||||
return query_sql
|
return query_sql
|
||||||
@@ -297,7 +310,7 @@ class Search(object):
|
|||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
execute = db.session.execute
|
execute = db.session.execute
|
||||||
current_app.logger.debug(v_query_sql)
|
# current_app.logger.debug(v_query_sql)
|
||||||
res = execute(v_query_sql).fetchall()
|
res = execute(v_query_sql).fetchall()
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
current_app.logger.debug("query ci ids time is: {0}".format(end_time - start))
|
current_app.logger.debug("query ci ids time is: {0}".format(end_time - start))
|
||||||
@@ -393,6 +406,9 @@ class Search(object):
|
|||||||
|
|
||||||
is_not = True if operator == "|~" else False
|
is_not = True if operator == "|~" else False
|
||||||
|
|
||||||
|
if field_type == ValueTypeEnum.DATE and len(v) == 10:
|
||||||
|
v = "{} 00:00:00".format(v)
|
||||||
|
|
||||||
# in query
|
# in query
|
||||||
if v.startswith("(") and v.endswith(")"):
|
if v.startswith("(") and v.endswith(")"):
|
||||||
_query_sql = self._in_query_handler(attr, v, is_not)
|
_query_sql = self._in_query_handler(attr, v, is_not)
|
||||||
@@ -508,7 +524,7 @@ class Search(object):
|
|||||||
if k:
|
if k:
|
||||||
table_name = TableMap(attr=attr).table_name
|
table_name = TableMap(attr=attr).table_name
|
||||||
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
|
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
|
||||||
# current_app.logger.debug(query_sql)
|
# current_app.logger.warning(query_sql)
|
||||||
result = db.session.execute(query_sql).fetchall()
|
result = db.session.execute(query_sql).fetchall()
|
||||||
facet[k] = result
|
facet[k] = result
|
||||||
|
|
||||||
|
@@ -297,8 +297,8 @@ class Search(object):
|
|||||||
if not attr:
|
if not attr:
|
||||||
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
||||||
|
|
||||||
sort_by = "{0}.keyword".format(field) \
|
sort_by = ("{0}.keyword".format(field)
|
||||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field
|
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field)
|
||||||
sorts.append({sort_by: {"order": sort_type}})
|
sorts.append({sort_by: {"order": sort_type}})
|
||||||
|
|
||||||
self.query.update(dict(sort=sorts))
|
self.query.update(dict(sort=sorts))
|
||||||
|
@@ -7,7 +7,6 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from markupsafe import escape
|
|
||||||
|
|
||||||
import api.models.cmdb as model
|
import api.models.cmdb as model
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
@@ -33,8 +32,8 @@ class ValueTypeMap(object):
|
|||||||
deserialize = {
|
deserialize = {
|
||||||
ValueTypeEnum.INT: string2int,
|
ValueTypeEnum.INT: string2int,
|
||||||
ValueTypeEnum.FLOAT: float,
|
ValueTypeEnum.FLOAT: float,
|
||||||
ValueTypeEnum.TEXT: lambda x: escape(x).encode('utf-8').decode('utf-8'),
|
ValueTypeEnum.TEXT: lambda x: x,
|
||||||
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(escape(x).encode('utf-8').decode('utf-8'))[0],
|
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
|
||||||
ValueTypeEnum.DATETIME: str2datetime,
|
ValueTypeEnum.DATETIME: str2datetime,
|
||||||
ValueTypeEnum.DATE: str2datetime,
|
ValueTypeEnum.DATE: str2datetime,
|
||||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||||
|
@@ -18,7 +18,6 @@ from api.extensions import db
|
|||||||
from api.lib.cmdb.attribute import AttributeManager
|
from api.lib.cmdb.attribute import AttributeManager
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||||
from api.lib.cmdb.const import ExistPolicy
|
|
||||||
from api.lib.cmdb.const import OperateType
|
from api.lib.cmdb.const import OperateType
|
||||||
from api.lib.cmdb.const import ValueTypeEnum
|
from api.lib.cmdb.const import ValueTypeEnum
|
||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
@@ -80,9 +79,10 @@ class AttributeValueManager(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __deserialize_value(value_type, value):
|
def _deserialize_value(value_type, value):
|
||||||
if not value:
|
if not value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
deserialize = ValueTypeMap.deserialize[value_type]
|
deserialize = ValueTypeMap.deserialize[value_type]
|
||||||
try:
|
try:
|
||||||
v = deserialize(value)
|
v = deserialize(value)
|
||||||
@@ -91,13 +91,13 @@ class AttributeValueManager(object):
|
|||||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
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])):
|
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))
|
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||||
|
|
||||||
@staticmethod
|
@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(
|
existed = db.session.query(value_table.attr_id).join(CI, CI.id == value_table.ci_id).filter(
|
||||||
CI.type_id == type_id).filter(
|
CI.type_id == type_id).filter(
|
||||||
value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter(
|
value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter(
|
||||||
@@ -106,20 +106,20 @@ class AttributeValueManager(object):
|
|||||||
existed and abort(400, ErrFormat.attribute_value_unique_required.format(attr.alias, value))
|
existed and abort(400, ErrFormat.attribute_value_unique_required.format(attr.alias, value))
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
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:
|
if type_attr and type_attr.is_required and not value and value != 0:
|
||||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
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):
|
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||||
ci = ci or {}
|
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_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||||
attr.is_unique and self.__check_is_unique(
|
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)
|
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,):
|
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||||
v = None
|
v = None
|
||||||
@@ -139,12 +139,13 @@ class AttributeValueManager(object):
|
|||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
current_app.logger.error("write change failed: {}".format(str(e)))
|
current_app.logger.error("write change failed: {}".format(str(e)))
|
||||||
|
|
||||||
return record_id
|
return record_id
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
t = jinja2.Template(expr).render(ci_dict)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -154,7 +155,7 @@ class AttributeValueManager(object):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
@staticmethod
|
@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 = jinja2.Template(script).render(ci_dict)
|
||||||
|
|
||||||
script_f = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
|
script_f = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
|
||||||
@@ -183,22 +184,22 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
return [var for var in schema.get("properties")]
|
return [var for var in schema.get("properties")]
|
||||||
|
|
||||||
def _compute_attr_value(self, attr, payload, ci):
|
def _compute_attr_value(self, attr, payload, ci_id):
|
||||||
attrs = self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr') else \
|
attrs = (self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr')
|
||||||
self._jinja2_parse(attr['compute_script'])
|
else self._jinja2_parse(attr['compute_script']))
|
||||||
not_existed = [i for i in attrs if i not in payload]
|
not_existed = [i for i in attrs if i not in payload]
|
||||||
if ci is not None:
|
if ci_id is not None:
|
||||||
payload.update(self.get_attr_values(not_existed, ci.id))
|
payload.update(self.get_attr_values(not_existed, ci_id))
|
||||||
|
|
||||||
if attr['compute_expr']:
|
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']:
|
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):
|
def handle_ci_compute_attributes(self, ci_dict, computed_attrs, ci):
|
||||||
payload = copy.deepcopy(ci_dict)
|
payload = copy.deepcopy(ci_dict)
|
||||||
for attr in computed_attrs:
|
for attr in computed_attrs:
|
||||||
computed_value = self._compute_attr_value(attr, payload, ci)
|
computed_value = self._compute_attr_value(attr, payload, ci and ci.id)
|
||||||
if computed_value is not None:
|
if computed_value is not None:
|
||||||
ci_dict[attr['name']] = computed_value
|
ci_dict[attr['name']] = computed_value
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ class AttributeValueManager(object):
|
|||||||
for i in handle_arg_list(value)]
|
for i in handle_arg_list(value)]
|
||||||
ci_dict[key] = value_list
|
ci_dict[key] = value_list
|
||||||
if not value_list:
|
if not value_list:
|
||||||
self.__check_is_required(type_id, attr, '')
|
self._check_is_required(type_id, attr, '')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||||
@@ -234,7 +235,7 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
return key2attr
|
return key2attr
|
||||||
|
|
||||||
def create_or_update_attr_value2(self, ci, ci_dict, key2attr):
|
def create_or_update_attr_value(self, ci, ci_dict, key2attr):
|
||||||
"""
|
"""
|
||||||
add or update attribute value, then write history
|
add or update attribute value, then write history
|
||||||
:param ci: instance object
|
:param ci: instance object
|
||||||
@@ -287,66 +288,6 @@ class AttributeValueManager(object):
|
|||||||
|
|
||||||
return self._write_change2(changed)
|
return self._write_change2(changed)
|
||||||
|
|
||||||
def create_or_update_attr_value(self, key, value, ci, _no_attribute_policy=ExistPolicy.IGNORE, record_id=None):
|
|
||||||
"""
|
|
||||||
add or update attribute value, then write history
|
|
||||||
:param key: id, name or alias
|
|
||||||
:param value:
|
|
||||||
:param ci: instance object
|
|
||||||
:param _no_attribute_policy: ignore or reject
|
|
||||||
:param record_id: op record
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
attr = self._get_attr(key)
|
|
||||||
if attr is None:
|
|
||||||
if _no_attribute_policy == ExistPolicy.IGNORE:
|
|
||||||
return
|
|
||||||
if _no_attribute_policy == ExistPolicy.REJECT:
|
|
||||||
return abort(400, ErrFormat.attribute_not_found.format(key))
|
|
||||||
|
|
||||||
value_table = TableMap(attr=attr).table
|
|
||||||
|
|
||||||
try:
|
|
||||||
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, '')
|
|
||||||
|
|
||||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
|
||||||
existed_values = [i.value for i in existed_attrs]
|
|
||||||
added = set(value_list) - set(existed_values)
|
|
||||||
deleted = set(existed_values) - set(value_list)
|
|
||||||
for v in added:
|
|
||||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v)
|
|
||||||
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, v, record_id, ci.type_id)
|
|
||||||
|
|
||||||
for v in deleted:
|
|
||||||
existed_attr = existed_attrs[existed_values.index(v)]
|
|
||||||
existed_attr.delete()
|
|
||||||
record_id = self._write_change(ci.id, attr.id, OperateType.DELETE, v, None, record_id, ci.type_id)
|
|
||||||
else:
|
|
||||||
value = self._validate(attr, value, value_table, ci)
|
|
||||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
|
||||||
existed_value = existed_attr and existed_attr.value
|
|
||||||
if existed_value is None and value is not None:
|
|
||||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value)
|
|
||||||
|
|
||||||
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, value, record_id, ci.type_id)
|
|
||||||
else:
|
|
||||||
if existed_value != value:
|
|
||||||
if value is None:
|
|
||||||
existed_attr.delete()
|
|
||||||
else:
|
|
||||||
existed_attr.update(value=value)
|
|
||||||
|
|
||||||
record_id = self._write_change(ci.id, attr.id, OperateType.UPDATE,
|
|
||||||
existed_value, value, record_id, ci.type_id)
|
|
||||||
|
|
||||||
return record_id
|
|
||||||
except Exception as e:
|
|
||||||
current_app.logger.warning(str(e))
|
|
||||||
return abort(400, ErrFormat.attribute_value_invalid2.format("{}({})".format(attr.alias, attr.name), value))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_attr_value(attr_id, ci_id):
|
def delete_attr_value(attr_id, ci_id):
|
||||||
attr = AttributeCache.get(attr_id)
|
attr = AttributeCache.get(attr_id)
|
||||||
|
@@ -6,6 +6,7 @@ from api.lib.common_setting.resp_format import ErrFormat
|
|||||||
from api.lib.perm.acl.cache import RoleCache, AppCache
|
from api.lib.perm.acl.cache import RoleCache, AppCache
|
||||||
from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
|
||||||
from api.lib.perm.acl.user import UserCRUD
|
from api.lib.perm.acl.user import UserCRUD
|
||||||
|
from api.lib.perm.acl.resource import ResourceTypeCRUD, ResourceCRUD
|
||||||
|
|
||||||
|
|
||||||
class ACLManager(object):
|
class ACLManager(object):
|
||||||
@@ -94,3 +95,22 @@ class ACLManager(object):
|
|||||||
avatar=user_info.get('avatar'))
|
avatar=user_info.get('avatar'))
|
||||||
|
|
||||||
return result
|
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()
|
||||||
|
46
cmdb-api/api/lib/common_setting/common_data.py
Normal file
46
cmdb-api/api/lib/common_setting/common_data.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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))
|
@@ -474,6 +474,29 @@ class EmployeeCRUD(object):
|
|||||||
|
|
||||||
return [r.to_dict() for r in results]
|
return [r.to_dict() for r in results]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_employee_notice_by_ids(employee_ids):
|
||||||
|
criterion = [
|
||||||
|
Employee.employee_id.in_(employee_ids),
|
||||||
|
Employee.deleted == 0,
|
||||||
|
]
|
||||||
|
direct_columns = ['email', 'mobile']
|
||||||
|
employees = Employee.query.filter(
|
||||||
|
*criterion
|
||||||
|
).all()
|
||||||
|
results = []
|
||||||
|
for employee in employees:
|
||||||
|
d = employee.to_dict()
|
||||||
|
tmp = dict(
|
||||||
|
employee_id=employee.employee_id,
|
||||||
|
)
|
||||||
|
for column in direct_columns:
|
||||||
|
tmp[column] = d.get(column, '')
|
||||||
|
notice_info = d.get('notice_info', {})
|
||||||
|
tmp.update(**notice_info)
|
||||||
|
results.append(tmp)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def get_user_map(key='uid', acl=None):
|
def get_user_map(key='uid', acl=None):
|
||||||
"""
|
"""
|
||||||
|
94
cmdb-api/api/lib/common_setting/notice_config.py
Normal file
94
cmdb-api/api/lib/common_setting/notice_config.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from api.models.common_setting import NoticeConfig
|
||||||
|
from wtforms import Form
|
||||||
|
from wtforms import StringField
|
||||||
|
from wtforms import validators
|
||||||
|
from flask import abort
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.utils import formataddr
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigCRUD(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_notice_config(**kwargs):
|
||||||
|
NoticeConfigCRUD.check_platform(kwargs.get('platform'))
|
||||||
|
try:
|
||||||
|
return NoticeConfig.create(
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return abort(400, str(e))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_platform(platform):
|
||||||
|
NoticeConfig.get_by(first=True, to_dict=False, platform=platform) and abort(400, f"{platform} 已存在!")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def edit_notice_config(_id, **kwargs):
|
||||||
|
existed = NoticeConfigCRUD.get_notice_config_by_id(_id)
|
||||||
|
try:
|
||||||
|
return existed.update(**kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return abort(400, str(e))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_notice_config_by_id(_id):
|
||||||
|
return NoticeConfig.get_by(first=True, to_dict=False, id=_id) or abort(400, f"{_id} 配置项不存在!")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all():
|
||||||
|
return NoticeConfig.get_by(to_dict=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_send_email(receive_address, **kwargs):
|
||||||
|
# 设置发送方和接收方的电子邮件地址
|
||||||
|
sender_email = 'test@test.com'
|
||||||
|
sender_name = 'Test Sender'
|
||||||
|
recipient_email = receive_address
|
||||||
|
recipient_name = receive_address
|
||||||
|
|
||||||
|
subject = 'Test Email'
|
||||||
|
body = 'This is a test email'
|
||||||
|
|
||||||
|
message = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
message['From'] = formataddr((sender_name, sender_email))
|
||||||
|
message['To'] = formataddr((recipient_name, recipient_email))
|
||||||
|
message['Subject'] = subject
|
||||||
|
|
||||||
|
smtp_server = kwargs.get('server')
|
||||||
|
smtp_port = kwargs.get('port')
|
||||||
|
smtp_username = kwargs.get('username')
|
||||||
|
smtp_password = kwargs.get('password')
|
||||||
|
|
||||||
|
if kwargs.get('mail_type') == 'SMTP':
|
||||||
|
smtp_connection = smtplib.SMTP(smtp_server, smtp_port)
|
||||||
|
else:
|
||||||
|
smtp_connection = smtplib.SMTP_SSL(smtp_server, smtp_port)
|
||||||
|
|
||||||
|
if kwargs.get('is_login'):
|
||||||
|
smtp_connection.login(smtp_username, smtp_password)
|
||||||
|
|
||||||
|
smtp_connection.sendmail(sender_email, recipient_email, message.as_string())
|
||||||
|
smtp_connection.quit()
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigForm(Form):
|
||||||
|
platform = StringField(validators=[
|
||||||
|
validators.DataRequired(message="平台 不能为空"),
|
||||||
|
validators.Length(max=255),
|
||||||
|
])
|
||||||
|
info = StringField(validators=[
|
||||||
|
validators.DataRequired(message="信息 不能为空"),
|
||||||
|
validators.Length(max=255),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigUpdateForm(Form):
|
||||||
|
info = StringField(validators=[
|
||||||
|
validators.DataRequired(message="信息 不能为空"),
|
||||||
|
validators.Length(max=255),
|
||||||
|
])
|
@@ -53,4 +53,6 @@ class ErrFormat(CommonErrFormat):
|
|||||||
username_is_required = "username不能为空"
|
username_is_required = "username不能为空"
|
||||||
email_is_required = "邮箱不能为空"
|
email_is_required = "邮箱不能为空"
|
||||||
email_format_error = "邮箱格式错误"
|
email_format_error = "邮箱格式错误"
|
||||||
|
email_send_timeout = "邮件发送超时"
|
||||||
|
|
||||||
|
common_data_not_found = "ID {} 找不到记录"
|
||||||
|
@@ -4,8 +4,7 @@ from api.lib.common_setting.utils import get_cur_time_str
|
|||||||
|
|
||||||
|
|
||||||
def allowed_file(filename, allowed_extensions):
|
def allowed_file(filename, allowed_extensions):
|
||||||
return '.' in filename and \
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||||
filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
|
||||||
|
|
||||||
|
|
||||||
def generate_new_file_name(name):
|
def generate_new_file_name(name):
|
||||||
@@ -13,4 +12,5 @@ def generate_new_file_name(name):
|
|||||||
prev_name = ''.join(name.split(f".{ext}")[:-1])
|
prev_name = ''.join(name.split(f".{ext}")[:-1])
|
||||||
uid = str(uuid.uuid4())
|
uid = str(uuid.uuid4())
|
||||||
cur_str = get_cur_time_str('_')
|
cur_str = get_cur_time_str('_')
|
||||||
|
|
||||||
return f"{prev_name}_{cur_str}_{uid}.{ext}"
|
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:
|
if exclude_args and arg in exclude_args:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if attr.type.python_type == str and attr.type.length and \
|
if attr.type.python_type == str and attr.type.length and (
|
||||||
len(request.values[arg] or '') > attr.type.length:
|
len(request.values[arg] or '') > attr.type.length):
|
||||||
|
|
||||||
return abort(400, CommonErrFormat.argument_str_length_limit.format(arg, 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]:
|
elif attr.type.python_type in (int, float) and request.values[arg]:
|
||||||
|
@@ -19,6 +19,7 @@ def build_api_key(path, params):
|
|||||||
_secret = "".join([path, secret, values]).encode("utf-8")
|
_secret = "".join([path, secret, values]).encode("utf-8")
|
||||||
params["_secret"] = hashlib.sha1(_secret).hexdigest()
|
params["_secret"] = hashlib.sha1(_secret).hexdigest()
|
||||||
params["_key"] = key
|
params["_key"] = key
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
55
cmdb-api/api/lib/notify.py
Normal file
55
cmdb-api/api/lib/notify.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from flask import current_app
|
||||||
|
from jinja2 import Template
|
||||||
|
from markdownify import markdownify as md
|
||||||
|
|
||||||
|
from api.lib.mail import send_mail
|
||||||
|
|
||||||
|
|
||||||
|
def _request_messenger(subject, body, tos, sender, payload):
|
||||||
|
params = dict(sender=sender, title=subject,
|
||||||
|
tos=[to[sender] for to in tos if to.get(sender)])
|
||||||
|
|
||||||
|
if not params['tos']:
|
||||||
|
raise Exception("no receivers")
|
||||||
|
|
||||||
|
params['tos'] = [Template(i).render(payload) for i in params['tos'] if i.strip()]
|
||||||
|
|
||||||
|
if sender == "email":
|
||||||
|
params['msgtype'] = 'text/html'
|
||||||
|
params['content'] = body
|
||||||
|
else:
|
||||||
|
params['msgtype'] = 'markdown'
|
||||||
|
try:
|
||||||
|
content = md("{}\n{}".format(subject or '', body or ''))
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning("html2markdown failed: {}".format(e))
|
||||||
|
content = "{}\n{}".format(subject or '', body or '')
|
||||||
|
|
||||||
|
params['content'] = json.dumps(dict(content=content))
|
||||||
|
|
||||||
|
resp = requests.post(current_app.config.get('MESSENGER_URL'), json=params)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise Exception(resp.text)
|
||||||
|
|
||||||
|
return resp.text
|
||||||
|
|
||||||
|
|
||||||
|
def notify_send(subject, body, methods, tos, payload=None):
|
||||||
|
payload = payload or {}
|
||||||
|
payload = {k: v or '' for k, v in payload.items()}
|
||||||
|
subject = Template(subject).render(payload)
|
||||||
|
body = Template(body).render(payload)
|
||||||
|
|
||||||
|
res = ''
|
||||||
|
for method in methods:
|
||||||
|
if method == "email" and not current_app.config.get('USE_MESSENGER', True):
|
||||||
|
send_mail(None, [Template(to.get('email')).render(payload) for to in tos], subject, body)
|
||||||
|
|
||||||
|
res += (_request_messenger(subject, body, tos, method, payload) + "\n")
|
||||||
|
|
||||||
|
return res
|
@@ -5,8 +5,10 @@ import hashlib
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import six
|
import six
|
||||||
from flask import abort, session
|
from flask import abort
|
||||||
from flask import current_app, request
|
from flask import current_app
|
||||||
|
from flask import request
|
||||||
|
from flask import session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from api.extensions import cache
|
from api.extensions import cache
|
||||||
@@ -85,8 +87,8 @@ class ACLManager(object):
|
|||||||
if user:
|
if user:
|
||||||
return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False)
|
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 \
|
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)
|
Role.get_by(name=name, first=True, to_dict=False))
|
||||||
|
|
||||||
def add_resource(self, name, resource_type_name=None):
|
def add_resource(self, name, resource_type_name=None):
|
||||||
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
||||||
|
@@ -8,7 +8,9 @@ from flask import abort
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
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.resp_format import ErrFormat
|
from api.lib.perm.acl.resp_format import ErrFormat
|
||||||
from api.models.acl import App
|
from api.models.acl import App
|
||||||
|
|
||||||
|
@@ -4,13 +4,21 @@ import json
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from flask import g, has_request_context, request
|
from flask import has_request_context, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from api.lib.perm.acl import AppCache
|
from api.lib.perm.acl import AppCache
|
||||||
from api.models.acl import AuditPermissionLog, AuditResourceLog, AuditRoleLog, AuditTriggerLog, Permission, Resource, \
|
from api.models.acl import AuditPermissionLog
|
||||||
ResourceGroup, ResourceType, Role, RolePermission
|
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
|
||||||
|
|
||||||
|
|
||||||
class AuditScope(str, Enum):
|
class AuditScope(str, Enum):
|
||||||
@@ -49,7 +57,7 @@ class AuditCRUD(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_current_operate_uid(uid=None):
|
def get_current_operate_uid(uid=None):
|
||||||
user_id = uid or getattr(current_user, 'uid', None)
|
user_id = uid or (getattr(current_user, 'uid', None)) or getattr(current_user, 'user_id', None)
|
||||||
|
|
||||||
if has_request_context() and request.headers.get('X-User-Id'):
|
if has_request_context() and request.headers.get('X-User-Id'):
|
||||||
_user_id = request.headers['X-User-Id']
|
_user_id = request.headers['X-User-Id']
|
||||||
@@ -91,11 +99,8 @@ class AuditCRUD(object):
|
|||||||
criterion.append(AuditPermissionLog.operate_type == v)
|
criterion.append(AuditPermissionLog.operate_type == v)
|
||||||
|
|
||||||
records = AuditPermissionLog.query.filter(
|
records = AuditPermissionLog.query.filter(
|
||||||
AuditPermissionLog.deleted == 0,
|
AuditPermissionLog.deleted == 0, *criterion).order_by(
|
||||||
*criterion) \
|
AuditPermissionLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
.order_by(AuditPermissionLog.id.desc()) \
|
|
||||||
.offset((page - 1) * page_size) \
|
|
||||||
.limit(page_size).all()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'data': [r.to_dict() for r in records],
|
'data': [r.to_dict() for r in records],
|
||||||
@@ -158,10 +163,8 @@ class AuditCRUD(object):
|
|||||||
elif k == 'operate_type':
|
elif k == 'operate_type':
|
||||||
criterion.append(AuditRoleLog.operate_type == v)
|
criterion.append(AuditRoleLog.operate_type == v)
|
||||||
|
|
||||||
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion) \
|
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion).order_by(
|
||||||
.order_by(AuditRoleLog.id.desc()) \
|
AuditRoleLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
.offset((page - 1) * page_size) \
|
|
||||||
.limit(page_size).all()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'data': [r.to_dict() for r in records],
|
'data': [r.to_dict() for r in records],
|
||||||
@@ -223,11 +226,8 @@ class AuditCRUD(object):
|
|||||||
criterion.append(AuditResourceLog.operate_type == v)
|
criterion.append(AuditResourceLog.operate_type == v)
|
||||||
|
|
||||||
records = AuditResourceLog.query.filter(
|
records = AuditResourceLog.query.filter(
|
||||||
AuditResourceLog.deleted == 0,
|
AuditResourceLog.deleted == 0, *criterion).order_by(
|
||||||
*criterion) \
|
AuditResourceLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
.order_by(AuditResourceLog.id.desc()) \
|
|
||||||
.offset((page - 1) * page_size) \
|
|
||||||
.limit(page_size).all()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'data': [r.to_dict() for r in records],
|
'data': [r.to_dict() for r in records],
|
||||||
@@ -257,11 +257,8 @@ class AuditCRUD(object):
|
|||||||
criterion.append(AuditTriggerLog.operate_type == v)
|
criterion.append(AuditTriggerLog.operate_type == v)
|
||||||
|
|
||||||
records = AuditTriggerLog.query.filter(
|
records = AuditTriggerLog.query.filter(
|
||||||
AuditTriggerLog.deleted == 0,
|
AuditTriggerLog.deleted == 0, *criterion).order_by(
|
||||||
*criterion) \
|
AuditTriggerLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
.order_by(AuditTriggerLog.id.desc()) \
|
|
||||||
.offset((page - 1) * page_size) \
|
|
||||||
.limit(page_size).all()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'data': [r.to_dict() for r in records],
|
'data': [r.to_dict() for r in records],
|
||||||
|
@@ -60,15 +60,15 @@ class UserCache(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, key):
|
def get(cls, key):
|
||||||
user = cache.get(cls.PREFIX_ID.format(key)) or \
|
user = (cache.get(cls.PREFIX_ID.format(key)) or
|
||||||
cache.get(cls.PREFIX_NAME.format(key)) or \
|
cache.get(cls.PREFIX_NAME.format(key)) or
|
||||||
cache.get(cls.PREFIX_NICK.format(key)) or \
|
cache.get(cls.PREFIX_NICK.format(key)) or
|
||||||
cache.get(cls.PREFIX_WXID.format(key))
|
cache.get(cls.PREFIX_WXID.format(key)))
|
||||||
if not user:
|
if not user:
|
||||||
user = User.query.get(key) or \
|
user = (User.query.get(key) or
|
||||||
User.query.get_by_username(key) or \
|
User.query.get_by_username(key) or
|
||||||
User.query.get_by_nickname(key) or \
|
User.query.get_by_nickname(key) or
|
||||||
User.query.get_by_wxid(key)
|
User.query.get_by_wxid(key))
|
||||||
if user:
|
if user:
|
||||||
cls.set(user)
|
cls.set(user)
|
||||||
|
|
||||||
|
@@ -4,7 +4,9 @@ import datetime
|
|||||||
from flask import abort
|
from flask import abort
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource
|
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 PermissionCache
|
from api.lib.perm.acl.cache import PermissionCache
|
||||||
from api.lib.perm.acl.cache import RoleCache
|
from api.lib.perm.acl.cache import RoleCache
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
@@ -97,8 +99,8 @@ class PermissionCRUD(object):
|
|||||||
elif group_id is not None:
|
elif group_id is not None:
|
||||||
from api.models.acl import ResourceGroup
|
from api.models.acl import ResourceGroup
|
||||||
|
|
||||||
group = ResourceGroup.get_by_id(group_id) or \
|
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||||
app_id = group.app_id
|
app_id = group.app_id
|
||||||
rt_id = group.resource_type_id
|
rt_id = group.resource_type_id
|
||||||
if not perms:
|
if not perms:
|
||||||
@@ -206,8 +208,8 @@ class PermissionCRUD(object):
|
|||||||
if resource_id is not None:
|
if resource_id is not None:
|
||||||
from api.models.acl import Resource
|
from api.models.acl import Resource
|
||||||
|
|
||||||
resource = Resource.get_by_id(resource_id) or \
|
resource = Resource.get_by_id(resource_id) or abort(
|
||||||
abort(404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
||||||
app_id = resource.app_id
|
app_id = resource.app_id
|
||||||
rt_id = resource.resource_type_id
|
rt_id = resource.resource_type_id
|
||||||
if not perms:
|
if not perms:
|
||||||
@@ -216,8 +218,8 @@ class PermissionCRUD(object):
|
|||||||
elif group_id is not None:
|
elif group_id is not None:
|
||||||
from api.models.acl import ResourceGroup
|
from api.models.acl import ResourceGroup
|
||||||
|
|
||||||
group = ResourceGroup.get_by_id(group_id) or \
|
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||||
app_id = group.app_id
|
app_id = group.app_id
|
||||||
|
|
||||||
rt_id = group.resource_type_id
|
rt_id = group.resource_type_id
|
||||||
|
@@ -5,7 +5,9 @@ from flask import abort
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
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.cache import ResourceCache
|
from api.lib.perm.acl.cache import ResourceCache
|
||||||
from api.lib.perm.acl.cache import ResourceGroupCache
|
from api.lib.perm.acl.cache import ResourceGroupCache
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
@@ -102,8 +104,8 @@ class ResourceTypeCRUD(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, rt_id):
|
def delete(cls, rt_id):
|
||||||
rt = ResourceType.get_by_id(rt_id) or \
|
rt = ResourceType.get_by_id(rt_id) or abort(
|
||||||
abort(404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
|
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)
|
Resource.get_by(resource_type_id=rt_id) and abort(400, ErrFormat.resource_type_cannot_delete)
|
||||||
|
|
||||||
@@ -165,8 +167,8 @@ class ResourceGroupCRUD(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add(name, type_id, app_id, uid=None):
|
def add(name, type_id, app_id, uid=None):
|
||||||
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \
|
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||||
abort(400, ErrFormat.resource_group_exists.format(name))
|
400, ErrFormat.resource_group_exists.format(name))
|
||||||
rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||||
|
|
||||||
AuditCRUD.add_resource_log(app_id, AuditOperateType.create,
|
AuditCRUD.add_resource_log(app_id, AuditOperateType.create,
|
||||||
@@ -175,8 +177,8 @@ class ResourceGroupCRUD(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update(rg_id, items):
|
def update(rg_id, items):
|
||||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||||
|
|
||||||
existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False)
|
existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False)
|
||||||
existed_ids = [i.resource_id for i in existed]
|
existed_ids = [i.resource_id for i in existed]
|
||||||
@@ -196,8 +198,8 @@ class ResourceGroupCRUD(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(rg_id):
|
def delete(rg_id):
|
||||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||||
|
|
||||||
origin = rg.to_dict()
|
origin = rg.to_dict()
|
||||||
rg.soft_delete()
|
rg.soft_delete()
|
||||||
@@ -266,8 +268,8 @@ class ResourceCRUD(object):
|
|||||||
def add(cls, name, type_id, app_id, uid=None):
|
def add(cls, name, type_id, app_id, uid=None):
|
||||||
type_id = cls._parse_resource_type_id(type_id, app_id)
|
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 \
|
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||||
abort(400, ErrFormat.resource_exists.format(name))
|
400, ErrFormat.resource_exists.format(name))
|
||||||
|
|
||||||
r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||||
|
|
||||||
|
@@ -10,7 +10,9 @@ from sqlalchemy import or_
|
|||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.app import AppCRUD
|
from api.lib.perm.acl.app import AppCRUD
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
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.cache import AppCache
|
from api.lib.perm.acl.cache import AppCache
|
||||||
from api.lib.perm.acl.cache import HasResourceRoleCache
|
from api.lib.perm.acl.cache import HasResourceRoleCache
|
||||||
from api.lib.perm.acl.cache import RoleCache
|
from api.lib.perm.acl.cache import RoleCache
|
||||||
@@ -69,16 +71,16 @@ class RoleRelationCRUD(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_parent_ids(rid, app_id):
|
def get_parent_ids(rid, app_id):
|
||||||
if app_id is not None:
|
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)] + \
|
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)]
|
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)])
|
||||||
else:
|
else:
|
||||||
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)]
|
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_child_ids(rid, app_id):
|
def get_child_ids(rid, app_id):
|
||||||
if app_id is not None:
|
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)] + \
|
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)]
|
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)])
|
||||||
else:
|
else:
|
||||||
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)]
|
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)]
|
||||||
|
|
||||||
|
@@ -6,9 +6,10 @@ import json
|
|||||||
import re
|
import re
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
from flask import abort, current_app
|
from flask import abort
|
||||||
|
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType
|
from api.lib.perm.acl.audit import AuditCRUD
|
||||||
|
from api.lib.perm.acl.audit import AuditOperateType
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.perm.acl.const import ACL_QUEUE
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
from api.lib.perm.acl.resp_format import ErrFormat
|
from api.lib.perm.acl.resp_format import ErrFormat
|
||||||
|
@@ -9,7 +9,9 @@ from flask import abort
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
|
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.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.perm.acl.resp_format import ErrFormat
|
from api.lib.perm.acl.resp_format import ErrFormat
|
||||||
from api.lib.perm.acl.role import RoleCRUD
|
from api.lib.perm.acl.role import RoleCRUD
|
||||||
@@ -49,11 +51,9 @@ class UserCRUD(object):
|
|||||||
kwargs['block'] = 0
|
kwargs['block'] = 0
|
||||||
kwargs['key'], kwargs['secret'] = cls.gen_key_secret()
|
kwargs['key'], kwargs['secret'] = cls.gen_key_secret()
|
||||||
|
|
||||||
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(
|
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(User.employee_id.desc()).first()
|
||||||
User.employee_id.desc()).first()
|
|
||||||
|
|
||||||
biggest_employee_id = int(float(user_employee.employee_id)) \
|
biggest_employee_id = int(float(user_employee.employee_id)) if user_employee is not None else 0
|
||||||
if user_employee is not None else 0
|
|
||||||
|
|
||||||
kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1)
|
kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1)
|
||||||
user = User.create(**kwargs)
|
user = User.create(**kwargs)
|
||||||
|
@@ -9,6 +9,8 @@ class CommonErrFormat(object):
|
|||||||
|
|
||||||
not_found = "不存在"
|
not_found = "不存在"
|
||||||
|
|
||||||
|
circular_dependency_error = "存在循环依赖!"
|
||||||
|
|
||||||
unknown_search_error = "未知搜索错误"
|
unknown_search_error = "未知搜索错误"
|
||||||
|
|
||||||
invalid_json = "json格式似乎不正确了, 请仔细确认一下!"
|
invalid_json = "json格式似乎不正确了, 请仔细确认一下!"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Set
|
from typing import Set
|
||||||
@@ -113,7 +112,7 @@ class RedisHandler(object):
|
|||||||
try:
|
try:
|
||||||
ret = self.r.hdel(prefix, key_id)
|
ret = self.r.hdel(prefix, key_id)
|
||||||
if not ret:
|
if not ret:
|
||||||
current_app.logger.warn("[{0}] is not in redis".format(key_id))
|
current_app.logger.warning("[{0}] is not in redis".format(key_id))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
current_app.logger.error("delete redis key error, {0}".format(str(e)))
|
||||||
|
|
||||||
@@ -204,9 +203,9 @@ class ESHandler(object):
|
|||||||
|
|
||||||
res = self.es.search(index=self.index, body=query, filter_path=filter_path)
|
res = self.es.search(index=self.index, body=query, filter_path=filter_path)
|
||||||
if res['hits'].get('hits'):
|
if res['hits'].get('hits'):
|
||||||
return res['hits']['total']['value'], \
|
return (res['hits']['total']['value'],
|
||||||
[i['_source'] for i in res['hits']['hits']], \
|
[i['_source'] for i in res['hits']['hits']],
|
||||||
res.get("aggregations", {})
|
res.get("aggregations", {}))
|
||||||
else:
|
else:
|
||||||
return 0, [], {}
|
return 0, [], {}
|
||||||
|
|
||||||
@@ -257,93 +256,10 @@ class Lock(object):
|
|||||||
self.release()
|
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):
|
class AESCrypto(object):
|
||||||
BLOCK_SIZE = 16 # Bytes
|
BLOCK_SIZE = 16 # Bytes
|
||||||
pad = lambda s: s + (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)
|
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE))
|
||||||
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
|
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
|
||||||
|
|
||||||
iv = '0102030405060708'
|
iv = '0102030405060708'
|
||||||
@@ -352,7 +268,7 @@ class AESCrypto(object):
|
|||||||
def key():
|
def key():
|
||||||
key = current_app.config.get("SECRET_KEY")[:16]
|
key = current_app.config.get("SECRET_KEY")[:16]
|
||||||
if len(key) < 16:
|
if len(key) < 16:
|
||||||
key = "{}{}".format(key, (16 - len(key) * "x"))
|
key = "{}{}".format(key, (16 - len(key)) * "x")
|
||||||
|
|
||||||
return key.encode('utf8')
|
return key.encode('utf8')
|
||||||
|
|
||||||
|
109
cmdb-api/api/lib/webhook.py
Normal file
109
cmdb-api/api/lib/webhook.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from jinja2 import Template
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
from requests_oauthlib import OAuth2Session
|
||||||
|
|
||||||
|
|
||||||
|
class BearerAuth(requests.auth.AuthBase):
|
||||||
|
def __init__(self, token):
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
r.headers["authorization"] = "Bearer {}".format(self.token)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_auth(**kwargs):
|
||||||
|
auth_type = (kwargs.get('type') or "").lower()
|
||||||
|
if auth_type == "basicauth":
|
||||||
|
return HTTPBasicAuth(kwargs.get('username'), kwargs.get('password'))
|
||||||
|
|
||||||
|
elif auth_type == "bearer":
|
||||||
|
return BearerAuth(kwargs.get('token'))
|
||||||
|
|
||||||
|
elif auth_type == 'oauth2.0':
|
||||||
|
client_id = kwargs.get('client_id')
|
||||||
|
client_secret = kwargs.get('client_secret')
|
||||||
|
authorization_base_url = kwargs.get('authorization_base_url')
|
||||||
|
token_url = kwargs.get('token_url')
|
||||||
|
redirect_url = kwargs.get('redirect_url')
|
||||||
|
scope = kwargs.get('scope')
|
||||||
|
|
||||||
|
oauth2_session = OAuth2Session(client_id, scope=scope or None)
|
||||||
|
oauth2_session.authorization_url(authorization_base_url)
|
||||||
|
|
||||||
|
oauth2_session.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_url)
|
||||||
|
|
||||||
|
return oauth2_session
|
||||||
|
|
||||||
|
elif auth_type == "apikey":
|
||||||
|
return HTTPBasicAuth(kwargs.get('key'), kwargs.get('value'))
|
||||||
|
|
||||||
|
|
||||||
|
def webhook_request(webhook, payload):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param webhook:
|
||||||
|
{
|
||||||
|
"url": "https://veops.cn"
|
||||||
|
"method": "GET|POST|PUT|DELETE"
|
||||||
|
"body": {},
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "Application/json"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"key": "value"
|
||||||
|
},
|
||||||
|
"authorization": {
|
||||||
|
"type": "BasicAuth|Bearer|OAuth2.0|APIKey",
|
||||||
|
"password": "mmmm", # BasicAuth
|
||||||
|
"username": "bbb", # BasicAuth
|
||||||
|
|
||||||
|
"token": "xxx", # Bearer
|
||||||
|
|
||||||
|
"key": "xxx", # APIKey
|
||||||
|
"value": "xxx", # APIKey
|
||||||
|
|
||||||
|
"client_id": "xxx", # OAuth2.0
|
||||||
|
"client_secret": "xxx", # OAuth2.0
|
||||||
|
"authorization_base_url": "xxx", # OAuth2.0
|
||||||
|
"token_url": "xxx", # OAuth2.0
|
||||||
|
"redirect_url": "xxx", # OAuth2.0
|
||||||
|
"scope": "xxx" # OAuth2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:param payload:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
assert webhook.get('url') is not None
|
||||||
|
|
||||||
|
payload = {k: v or '' for k, v in payload.items()}
|
||||||
|
|
||||||
|
url = Template(webhook['url']).render(payload)
|
||||||
|
|
||||||
|
params = webhook.get('parameters') or None
|
||||||
|
if isinstance(params, dict):
|
||||||
|
params = json.loads(Template(json.dumps(params)).render(payload))
|
||||||
|
|
||||||
|
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
|
||||||
|
|
||||||
|
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
|
||||||
|
auth = _wrap_auth(**webhook.get('authorization', {}))
|
||||||
|
|
||||||
|
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
|
||||||
|
request = getattr(auth, webhook.get('method', 'GET').lower())
|
||||||
|
else:
|
||||||
|
request = partial(requests.request, webhook.get('method', 'GET'))
|
||||||
|
|
||||||
|
return request(
|
||||||
|
url,
|
||||||
|
params=params,
|
||||||
|
headers=headers or None,
|
||||||
|
data=data,
|
||||||
|
auth=auth
|
||||||
|
)
|
@@ -125,16 +125,27 @@ class CITypeAttributeGroupItem(Model):
|
|||||||
|
|
||||||
|
|
||||||
class CITypeTrigger(Model):
|
class CITypeTrigger(Model):
|
||||||
# __tablename__ = "c_ci_type_triggers"
|
|
||||||
__tablename__ = "c_c_t_t"
|
__tablename__ = "c_c_t_t"
|
||||||
|
|
||||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
|
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
|
||||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||||
notify = db.Column(db.JSON) # {subject: x, body: x, wx_to: [], mail_to: [], before_days: 0, notify_at: 08:00}
|
option = db.Column('notify', db.JSON)
|
||||||
|
|
||||||
|
|
||||||
|
class CITriggerHistory(Model):
|
||||||
|
__tablename__ = "c_ci_trigger_histories"
|
||||||
|
|
||||||
|
operate_type = db.Column(db.Enum(*OperateType.all(), name="operate_type"))
|
||||||
|
record_id = db.Column(db.Integer, db.ForeignKey("c_records.id"))
|
||||||
|
ci_id = db.Column(db.Integer, index=True, nullable=False)
|
||||||
|
trigger_id = db.Column(db.Integer, db.ForeignKey("c_c_t_t.id"))
|
||||||
|
trigger_name = db.Column(db.String(64))
|
||||||
|
is_ok = db.Column(db.Boolean, default=False)
|
||||||
|
notify = db.Column(db.Text)
|
||||||
|
webhook = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
class CITypeUniqueConstraint(Model):
|
class CITypeUniqueConstraint(Model):
|
||||||
# __tablename__ = "c_ci_type_unique_constraints"
|
|
||||||
__tablename__ = "c_c_t_u_c"
|
__tablename__ = "c_c_t_u_c"
|
||||||
|
|
||||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
|
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
|
||||||
@@ -363,7 +374,6 @@ class CITypeHistory(Model):
|
|||||||
|
|
||||||
# preference
|
# preference
|
||||||
class PreferenceShowAttributes(Model):
|
class PreferenceShowAttributes(Model):
|
||||||
# __tablename__ = "c_preference_show_attributes"
|
|
||||||
__tablename__ = "c_psa"
|
__tablename__ = "c_psa"
|
||||||
|
|
||||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||||
@@ -377,7 +387,6 @@ class PreferenceShowAttributes(Model):
|
|||||||
|
|
||||||
|
|
||||||
class PreferenceTreeView(Model):
|
class PreferenceTreeView(Model):
|
||||||
# __tablename__ = "c_preference_tree_views"
|
|
||||||
__tablename__ = "c_ptv"
|
__tablename__ = "c_ptv"
|
||||||
|
|
||||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||||
@@ -386,7 +395,6 @@ class PreferenceTreeView(Model):
|
|||||||
|
|
||||||
|
|
||||||
class PreferenceRelationView(Model):
|
class PreferenceRelationView(Model):
|
||||||
# __tablename__ = "c_preference_relation_views"
|
|
||||||
__tablename__ = "c_prv"
|
__tablename__ = "c_prv"
|
||||||
|
|
||||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||||
|
@@ -47,6 +47,8 @@ class Employee(ModelWithoutPK):
|
|||||||
last_login = db.Column(db.TIMESTAMP, nullable=True)
|
last_login = db.Column(db.TIMESTAMP, nullable=True)
|
||||||
block = db.Column(db.Integer, default=0)
|
block = db.Column(db.Integer, default=0)
|
||||||
|
|
||||||
|
notice_info = db.Column(db.JSON, default={})
|
||||||
|
|
||||||
_department = db.relationship(
|
_department = db.relationship(
|
||||||
'Department', backref='common_employee.department_id',
|
'Department', backref='common_employee.department_id',
|
||||||
lazy='joined'
|
lazy='joined'
|
||||||
@@ -80,3 +82,17 @@ class InternalMessage(Model):
|
|||||||
category = db.Column(db.VARCHAR(128), nullable=False)
|
category = db.Column(db.VARCHAR(128), nullable=False)
|
||||||
message_data = db.Column(db.JSON, nullable=True)
|
message_data = db.Column(db.JSON, nullable=True)
|
||||||
employee_id = db.Column(db.Integer, db.ForeignKey('common_employee.employee_id'), comment='ID')
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfig(Model):
|
||||||
|
__tablename__ = "common_notice_config"
|
||||||
|
|
||||||
|
platform = db.Column(db.VARCHAR(255), nullable=False)
|
||||||
|
info = db.Column(db.JSON)
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from inspect import getmembers, isclass
|
from inspect import getmembers
|
||||||
|
from inspect import isclass
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
@@ -27,16 +28,15 @@ class APIView(Resource):
|
|||||||
return send_file(*args, **kwargs)
|
return send_file(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
API_PACKAGE = "api"
|
API_PACKAGE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
def register_resources(resource_path, rest_api):
|
def register_resources(resource_path, rest_api):
|
||||||
for root, _, files in os.walk(os.path.join(resource_path)):
|
for root, _, files in os.walk(os.path.join(resource_path)):
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if not filename.startswith("_") and filename.endswith("py"):
|
if not filename.startswith("_") and filename.endswith("py"):
|
||||||
module_path = os.path.join(API_PACKAGE, root[root.index("views"):])
|
if root not in sys.path:
|
||||||
if module_path not in sys.path:
|
sys.path.insert(1, root)
|
||||||
sys.path.insert(1, module_path)
|
|
||||||
view = __import__(os.path.splitext(filename)[0])
|
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 = [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"]
|
resource_list = [i for i in resource_list if i != "APIView"]
|
||||||
|
@@ -5,17 +5,20 @@ import re
|
|||||||
|
|
||||||
from celery_once import QueueOnce
|
from celery_once import QueueOnce
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from api.extensions import celery
|
from api.extensions import celery
|
||||||
from api.extensions import db
|
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 AppCache
|
||||||
from api.lib.perm.acl.cache import RoleCache
|
from api.lib.perm.acl.cache import RoleCache
|
||||||
from api.lib.perm.acl.cache import RoleRelationCache
|
from api.lib.perm.acl.cache import RoleRelationCache
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.perm.acl.cache import UserCache
|
||||||
from api.lib.perm.acl.const import ACL_QUEUE
|
from api.lib.perm.acl.const import ACL_QUEUE
|
||||||
from api.lib.perm.acl.record import OperateRecordCRUD
|
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 Resource
|
||||||
from api.models.acl import Role
|
from api.models.acl import Role
|
||||||
from api.models.acl import Trigger
|
from api.models.acl import Trigger
|
||||||
|
@@ -4,9 +4,8 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import requests
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from flask_login import login_user
|
||||||
|
|
||||||
import api.lib.cmdb.ci
|
import api.lib.cmdb.ci
|
||||||
from api.extensions import celery
|
from api.extensions import celery
|
||||||
@@ -17,13 +16,18 @@ from api.lib.cmdb.cache import CITypeAttributesCache
|
|||||||
from api.lib.cmdb.const import CMDB_QUEUE
|
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
|
||||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
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 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 CIRelation
|
||||||
|
from api.models.cmdb import CITypeAttribute
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
||||||
def ci_cache(ci_id):
|
def ci_cache(ci_id, operate_type, record_id):
|
||||||
|
from api.lib.cmdb.ci import CITriggerManager
|
||||||
|
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
@@ -37,9 +41,14 @@ def ci_cache(ci_id):
|
|||||||
|
|
||||||
current_app.logger.info("{0} flush..........".format(ci_id))
|
current_app.logger.info("{0} flush..........".format(ci_id))
|
||||||
|
|
||||||
|
current_app.test_request_context().push()
|
||||||
|
login_user(UserCache.get('worker'))
|
||||||
|
|
||||||
|
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||||
def batch_ci_cache(ci_ids):
|
def batch_ci_cache(ci_ids, ): # only for attribute change index
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
@@ -67,6 +76,17 @@ def ci_delete(ci_id):
|
|||||||
current_app.logger.info("{0} delete..........".format(ci_id))
|
current_app.logger.info("{0} delete..........".format(ci_id))
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task(name="cmdb.ci_delete_trigger", queue=CMDB_QUEUE)
|
||||||
|
def ci_delete_trigger(trigger, operate_type, ci_dict):
|
||||||
|
current_app.logger.info('delete ci {} trigger'.format(ci_dict['_id']))
|
||||||
|
from api.lib.cmdb.ci import CITriggerManager
|
||||||
|
|
||||||
|
current_app.test_request_context().push()
|
||||||
|
login_user(UserCache.get('worker'))
|
||||||
|
|
||||||
|
CITriggerManager.fire_by_trigger(trigger, operate_type, ci_dict)
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
|
||||||
def ci_relation_cache(parent_id, child_id):
|
def ci_relation_cache(parent_id, child_id):
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
@@ -84,6 +104,51 @@ def ci_relation_cache(parent_id, child_id):
|
|||||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(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)
|
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||||
def ci_relation_delete(parent_id, child_id):
|
def ci_relation_delete(parent_id, child_id):
|
||||||
with Lock("CIRelation_{}".format(parent_id)):
|
with Lock("CIRelation_{}".format(parent_id)):
|
||||||
@@ -118,41 +183,17 @@ def ci_type_attribute_order_rebuild(type_id):
|
|||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
|
|
||||||
@celery.task(name='cmdb.trigger_notify', queue=CMDB_QUEUE)
|
@celery.task(name="cmdb.calc_computed_attribute", queue=CMDB_QUEUE)
|
||||||
def trigger_notify(notify, ci_id):
|
def calc_computed_attribute(attr_id, uid):
|
||||||
from api.lib.perm.acl.cache import UserCache
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
|
||||||
def _wrap_mail(mail_to):
|
|
||||||
if "@" not in mail_to:
|
|
||||||
user = UserCache.get(mail_to)
|
|
||||||
if user:
|
|
||||||
return user.email
|
|
||||||
|
|
||||||
return mail_to
|
|
||||||
|
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
m = api.lib.cmdb.ci.CIManager()
|
current_app.test_request_context().push()
|
||||||
ci_dict = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
login_user(UserCache.get(uid))
|
||||||
|
|
||||||
subject = jinja2.Template(notify.get('subject') or "").render(ci_dict)
|
cim = CIManager()
|
||||||
body = jinja2.Template(notify.get('body') or "").render(ci_dict)
|
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)
|
||||||
if notify.get('wx_to'):
|
for ci in cis:
|
||||||
to_user = jinja2.Template('|'.join(notify['wx_to'])).render(ci_dict)
|
cim.update(ci.id, {})
|
||||||
url = current_app.config.get("WX_URI")
|
|
||||||
data = {"to_user": to_user, "content": subject}
|
|
||||||
try:
|
|
||||||
requests.post(url, data=data)
|
|
||||||
except Exception as e:
|
|
||||||
current_app.logger.error(str(e))
|
|
||||||
|
|
||||||
if notify.get('mail_to'):
|
|
||||||
try:
|
|
||||||
if len(subject) > 700:
|
|
||||||
subject = subject[:600] + "..." + subject[-100:]
|
|
||||||
|
|
||||||
send_mail("", [_wrap_mail(jinja2.Template(i).render(ci_dict))
|
|
||||||
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)))
|
|
||||||
|
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import six
|
|
||||||
import jwt
|
import jwt
|
||||||
|
import six
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import login_user, logout_user
|
from flask_login import login_user
|
||||||
|
from flask_login import logout_user
|
||||||
|
|
||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.perm.acl.cache import User
|
from api.lib.perm.acl.cache import User
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
from flask import g
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
@@ -104,7 +103,7 @@ class ResourceView(APIView):
|
|||||||
type_id = request.values.get('type_id')
|
type_id = request.values.get('type_id')
|
||||||
app_id = request.values.get('app_id')
|
app_id = request.values.get('app_id')
|
||||||
uid = request.values.get('uid')
|
uid = request.values.get('uid')
|
||||||
if not uid and hasattr(g, "user") and hasattr(current_user, "uid"):
|
if not uid and hasattr(current_user, "uid"):
|
||||||
uid = current_user.uid
|
uid = current_user.uid
|
||||||
|
|
||||||
resource = ResourceCRUD.add(name, type_id, app_id, uid)
|
resource = ResourceCRUD.add(name, type_id, app_id, uid)
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
import requests
|
import requests
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import g
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import session
|
from flask import session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -161,7 +160,7 @@ class UserResetPasswordView(APIView):
|
|||||||
if app.name not in ('cas-server', 'acl'):
|
if app.name not in ('cas-server', 'acl'):
|
||||||
return abort(403, ErrFormat.invalid_request)
|
return abort(403, ErrFormat.invalid_request)
|
||||||
|
|
||||||
elif hasattr(g, 'user'):
|
elif hasattr(current_user, 'username'):
|
||||||
if current_user.username != request.values['username']:
|
if current_user.username != request.values['username']:
|
||||||
return abort(403, ErrFormat.invalid_request)
|
return abort(403, ErrFormat.invalid_request)
|
||||||
|
|
||||||
|
@@ -33,7 +33,8 @@ class AttributeSearchView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class AttributeView(APIView):
|
class AttributeView(APIView):
|
||||||
url_prefix = ("/attributes", "/attributes/<string:attr_name>", "/attributes/<int:attr_id>")
|
url_prefix = ("/attributes", "/attributes/<string:attr_name>", "/attributes/<int:attr_id>",
|
||||||
|
"/attributes/<int:attr_id>/calc_computed_attribute")
|
||||||
|
|
||||||
def get(self, attr_name=None, attr_id=None):
|
def get(self, attr_name=None, attr_id=None):
|
||||||
attr_manager = AttributeManager()
|
attr_manager = AttributeManager()
|
||||||
@@ -68,6 +69,11 @@ class AttributeView(APIView):
|
|||||||
|
|
||||||
@args_validate(AttributeManager.cls)
|
@args_validate(AttributeManager.cls)
|
||||||
def put(self, attr_id):
|
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"))
|
choice_value = handle_arg_list(request.values.get("choice_value"))
|
||||||
params = request.values
|
params = request.values
|
||||||
params["choice_value"] = choice_value
|
params["choice_value"] = choice_value
|
||||||
|
@@ -11,7 +11,8 @@ from api.lib.cmdb.cache import CITypeCache
|
|||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
from api.lib.cmdb.ci import CIRelationManager
|
from api.lib.cmdb.ci import CIRelationManager
|
||||||
from api.lib.cmdb.const import ExistPolicy
|
from api.lib.cmdb.const import ExistPolicy
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
from api.lib.cmdb.const import RetKey
|
from api.lib.cmdb.const import RetKey
|
||||||
from api.lib.cmdb.perms import has_perm_for_ci
|
from api.lib.cmdb.perms import has_perm_for_ci
|
||||||
from api.lib.cmdb.search import SearchError
|
from api.lib.cmdb.search import SearchError
|
||||||
@@ -106,6 +107,7 @@ class CIView(APIView):
|
|||||||
_is_admin=request.values.pop('__is_admin', False),
|
_is_admin=request.values.pop('__is_admin', False),
|
||||||
**ci_dict)
|
**ci_dict)
|
||||||
else:
|
else:
|
||||||
|
request.values.pop('exist_policy', None)
|
||||||
ci_id = manager.add(ci_type,
|
ci_id = manager.add(ci_type,
|
||||||
exist_policy=ExistPolicy.REPLACE,
|
exist_policy=ExistPolicy.REPLACE,
|
||||||
_no_attribute_policy=_no_attribute_policy,
|
_no_attribute_policy=_no_attribute_policy,
|
||||||
@@ -183,8 +185,8 @@ class CIUnique(APIView):
|
|||||||
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name)
|
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name)
|
||||||
def put(self, ci_id):
|
def put(self, ci_id):
|
||||||
params = request.values
|
params = request.values
|
||||||
unique_name = params.keys()[0]
|
unique_name = list(params.keys())[0]
|
||||||
unique_value = params.values()[0]
|
unique_value = list(params.values())[0]
|
||||||
|
|
||||||
CIManager.update_unique_value(ci_id, unique_name, unique_value)
|
CIManager.update_unique_value(ci_id, unique_name, unique_value)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -154,9 +154,15 @@ class EnableCITypeView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class CITypeAttributeView(APIView):
|
class CITypeAttributeView(APIView):
|
||||||
url_prefix = ("/ci_types/<int:type_id>/attributes", "/ci_types/<string:type_name>/attributes")
|
url_prefix = ("/ci_types/<int:type_id>/attributes", "/ci_types/<string:type_name>/attributes",
|
||||||
|
"/ci_types/common_attributes")
|
||||||
|
|
||||||
def get(self, type_id=None, type_name=None):
|
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)
|
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found)
|
||||||
type_id = t.id
|
type_id = t.id
|
||||||
unique_id = t.unique_id
|
unique_id = t.unique_id
|
||||||
@@ -413,22 +419,22 @@ class CITypeTriggerView(APIView):
|
|||||||
return self.jsonify(CITypeTriggerManager.get(type_id))
|
return self.jsonify(CITypeTriggerManager.get(type_id))
|
||||||
|
|
||||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
@args_required("attr_id")
|
@args_required("option")
|
||||||
@args_required("notify")
|
|
||||||
def post(self, type_id):
|
def post(self, type_id):
|
||||||
attr_id = request.values.get('attr_id')
|
attr_id = request.values.get('attr_id') or None
|
||||||
notify = request.values.get('notify')
|
option = request.values.get('option')
|
||||||
|
|
||||||
return self.jsonify(CITypeTriggerManager().add(type_id, attr_id, notify))
|
return self.jsonify(CITypeTriggerManager().add(type_id, attr_id, option))
|
||||||
|
|
||||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
@args_required("notify")
|
@args_required("option")
|
||||||
def put(self, type_id, _id):
|
def put(self, type_id, _id):
|
||||||
assert type_id is not None
|
assert type_id is not None
|
||||||
|
|
||||||
notify = request.values.get('notify')
|
option = request.values.get('option')
|
||||||
|
attr_id = request.values.get('attr_id')
|
||||||
|
|
||||||
return self.jsonify(CITypeTriggerManager().update(_id, notify))
|
return self.jsonify(CITypeTriggerManager().update(_id, attr_id, option))
|
||||||
|
|
||||||
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
|
||||||
def delete(self, type_id, _id):
|
def delete(self, type_id, _id):
|
||||||
@@ -500,3 +506,4 @@ class CITypeFilterPermissionView(APIView):
|
|||||||
@auth_with_app_token
|
@auth_with_app_token
|
||||||
def get(self, type_id):
|
def get(self, type_id):
|
||||||
return self.jsonify(CIFilterPermsCRUD().get(type_id))
|
return self.jsonify(CIFilterPermsCRUD().get(type_id))
|
||||||
|
|
||||||
|
@@ -6,7 +6,9 @@ from flask import request
|
|||||||
|
|
||||||
from api.lib.cmdb.ci_type import CITypeManager
|
from api.lib.cmdb.ci_type import CITypeManager
|
||||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
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.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.decorator import args_required
|
from api.lib.decorator import args_required
|
||||||
from api.lib.perm.acl.acl import ACLManager
|
from api.lib.perm.acl.acl import ACLManager
|
||||||
@@ -17,9 +19,14 @@ from api.resource import APIView
|
|||||||
|
|
||||||
|
|
||||||
class GetChildrenView(APIView):
|
class GetChildrenView(APIView):
|
||||||
url_prefix = "/ci_type_relations/<int:parent_id>/children"
|
url_prefix = ("/ci_type_relations/<int:parent_id>/children",
|
||||||
|
"/ci_type_relations/<int:parent_id>/recursive_level2children",
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, parent_id):
|
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))
|
return self.jsonify(children=CITypeRelationManager.get_children(parent_id))
|
||||||
|
|
||||||
|
|
||||||
|
@@ -13,7 +13,8 @@ from api.resource import APIView
|
|||||||
|
|
||||||
|
|
||||||
class CustomDashboardApiView(APIView):
|
class CustomDashboardApiView(APIView):
|
||||||
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch")
|
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
|
||||||
|
"/custom_dashboard/preview")
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return self.jsonify(CustomDashboardManager.get())
|
return self.jsonify(CustomDashboardManager.get())
|
||||||
@@ -21,17 +22,26 @@ class CustomDashboardApiView(APIView):
|
|||||||
@role_required(RoleEnum.CONFIG)
|
@role_required(RoleEnum.CONFIG)
|
||||||
@args_validate(CustomDashboardManager.cls)
|
@args_validate(CustomDashboardManager.cls)
|
||||||
def post(self):
|
def post(self):
|
||||||
cm = CustomDashboardManager.add(**request.values)
|
if request.url.endswith("/preview"):
|
||||||
|
return self.jsonify(counter=CustomDashboardManager.preview(**request.values))
|
||||||
|
|
||||||
return self.jsonify(cm.to_dict())
|
cm, counter = CustomDashboardManager.add(**request.values)
|
||||||
|
|
||||||
|
res = cm.to_dict()
|
||||||
|
res.update(counter=counter)
|
||||||
|
|
||||||
|
return self.jsonify(res)
|
||||||
|
|
||||||
@role_required(RoleEnum.CONFIG)
|
@role_required(RoleEnum.CONFIG)
|
||||||
@args_validate(CustomDashboardManager.cls)
|
@args_validate(CustomDashboardManager.cls)
|
||||||
def put(self, _id=None):
|
def put(self, _id=None):
|
||||||
if _id is not None:
|
if _id is not None:
|
||||||
cm = CustomDashboardManager.update(_id, **request.values)
|
cm, counter = CustomDashboardManager.update(_id, **request.values)
|
||||||
|
|
||||||
return self.jsonify(cm.to_dict())
|
res = cm.to_dict()
|
||||||
|
res.update(counter=counter)
|
||||||
|
|
||||||
|
return self.jsonify(res)
|
||||||
|
|
||||||
CustomDashboardManager.batch_update(request.values.get("id2options"))
|
CustomDashboardManager.batch_update(request.values.get("id2options"))
|
||||||
|
|
||||||
|
@@ -5,14 +5,18 @@ import datetime
|
|||||||
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import request
|
from flask import request
|
||||||
|
from flask import session
|
||||||
|
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
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 RoleEnum
|
||||||
from api.lib.cmdb.history import AttributeHistoryManger
|
from api.lib.cmdb.history import AttributeHistoryManger
|
||||||
|
from api.lib.cmdb.history import CITriggerHistoryManager
|
||||||
from api.lib.cmdb.history import CITypeHistoryManager
|
from api.lib.cmdb.history import CITypeHistoryManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
from api.lib.perm.acl.acl import has_perm_from_args
|
from api.lib.perm.acl.acl import has_perm_from_args
|
||||||
|
from api.lib.perm.acl.acl import is_app_admin
|
||||||
from api.lib.perm.acl.acl import role_required
|
from api.lib.perm.acl.acl import role_required
|
||||||
from api.lib.utils import get_page
|
from api.lib.utils import get_page
|
||||||
from api.lib.utils import get_page_size
|
from api.lib.utils import get_page_size
|
||||||
@@ -75,6 +79,39 @@ class CIHistoryView(APIView):
|
|||||||
return self.jsonify(result)
|
return self.jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
class CITriggerHistoryView(APIView):
|
||||||
|
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
|
||||||
|
|
||||||
|
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
|
||||||
|
def get(self, ci_id=None):
|
||||||
|
if ci_id is not None:
|
||||||
|
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
|
||||||
|
|
||||||
|
return self.jsonify(result)
|
||||||
|
|
||||||
|
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
|
||||||
|
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||||
|
|
||||||
|
type_id = request.values.get("type_id")
|
||||||
|
trigger_id = request.values.get("trigger_id")
|
||||||
|
operate_type = request.values.get("operate_type")
|
||||||
|
|
||||||
|
page = get_page(request.values.get('page', 1))
|
||||||
|
page_size = get_page_size(request.values.get('page_size', 1))
|
||||||
|
|
||||||
|
numfound, result = CITriggerHistoryManager.get(page,
|
||||||
|
page_size,
|
||||||
|
type_id=type_id,
|
||||||
|
trigger_id=trigger_id,
|
||||||
|
operate_type=operate_type)
|
||||||
|
|
||||||
|
return self.jsonify(page=page,
|
||||||
|
page_size=page_size,
|
||||||
|
numfound=numfound,
|
||||||
|
total=len(result),
|
||||||
|
result=result)
|
||||||
|
|
||||||
|
|
||||||
class CITypeHistoryView(APIView):
|
class CITypeHistoryView(APIView):
|
||||||
url_prefix = "/history/ci_types"
|
url_prefix = "/history/ci_types"
|
||||||
|
|
||||||
|
@@ -5,7 +5,9 @@ from flask import abort
|
|||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from api.lib.cmdb.ci_type import CITypeManager
|
from api.lib.cmdb.ci_type import CITypeManager
|
||||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
from api.lib.cmdb.const import PermEnum
|
||||||
|
from api.lib.cmdb.const import ResourceTypeEnum
|
||||||
|
from api.lib.cmdb.const import RoleEnum
|
||||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||||
from api.lib.cmdb.preference import PreferenceManager
|
from api.lib.cmdb.preference import PreferenceManager
|
||||||
from api.lib.cmdb.resp_format import ErrFormat
|
from api.lib.cmdb.resp_format import ErrFormat
|
||||||
|
35
cmdb-api/api/views/common_setting/common_data.py
Normal file
35
cmdb-api/api/views/common_setting/common_data.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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,15 +16,16 @@ class CompanyInfoView(APIView):
|
|||||||
return self.jsonify(CompanyInfoCRUD.get())
|
return self.jsonify(CompanyInfoCRUD.get())
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
info = CompanyInfoCRUD.get()
|
|
||||||
if info:
|
|
||||||
abort(400, ErrFormat.company_info_is_already_existed)
|
|
||||||
data = {
|
data = {
|
||||||
'info': {
|
'info': {
|
||||||
**request.values
|
**request.values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d = CompanyInfoCRUD.create(**data)
|
info = CompanyInfoCRUD.get()
|
||||||
|
if info:
|
||||||
|
d = CompanyInfoCRUD.update(info.get('id'), **data)
|
||||||
|
else:
|
||||||
|
d = CompanyInfoCRUD.create(**data)
|
||||||
res = d.to_dict()
|
res = d.to_dict()
|
||||||
return self.jsonify(res)
|
return self.jsonify(res)
|
||||||
|
|
||||||
|
@@ -145,3 +145,14 @@ class EmployeePositionView(APIView):
|
|||||||
result = EmployeeCRUD.get_all_position()
|
result = EmployeeCRUD.get_all_position()
|
||||||
return self.jsonify(result)
|
return self.jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
class GetEmployeeNoticeByIds(APIView):
|
||||||
|
url_prefix = (f'{prefix}/get_notice_by_ids',)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
employee_ids = request.json.get('employee_ids', [])
|
||||||
|
if not employee_ids:
|
||||||
|
result = []
|
||||||
|
else:
|
||||||
|
result = EmployeeCRUD.get_employee_notice_by_ids(employee_ids)
|
||||||
|
return self.jsonify(result)
|
||||||
|
@@ -11,7 +11,7 @@ from api.resource import APIView
|
|||||||
prefix = '/file'
|
prefix = '/file'
|
||||||
|
|
||||||
ALLOWED_EXTENSIONS = {
|
ALLOWED_EXTENSIONS = {
|
||||||
'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx', 'csv'
|
'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx', 'csv', 'svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
71
cmdb-api/api/views/common_setting/notice_config.py
Normal file
71
cmdb-api/api/views/common_setting/notice_config.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from flask import request, abort, current_app
|
||||||
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
|
from api.lib.perm.auth import auth_with_app_token
|
||||||
|
from api.models.common_setting import NoticeConfig
|
||||||
|
from api.resource import APIView
|
||||||
|
from api.lib.common_setting.notice_config import NoticeConfigForm, NoticeConfigUpdateForm, NoticeConfigCRUD
|
||||||
|
from api.lib.decorator import args_required
|
||||||
|
from api.lib.common_setting.resp_format import ErrFormat
|
||||||
|
|
||||||
|
prefix = '/notice_config'
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigView(APIView):
|
||||||
|
url_prefix = (f'{prefix}',)
|
||||||
|
|
||||||
|
@args_required('platform')
|
||||||
|
@auth_with_app_token
|
||||||
|
def get(self):
|
||||||
|
platform = request.args.get('platform')
|
||||||
|
res = NoticeConfig.get_by(first=True, to_dict=True, platform=platform) or {}
|
||||||
|
return self.jsonify(res)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
form = NoticeConfigForm(MultiDict(request.json))
|
||||||
|
if not form.validate():
|
||||||
|
abort(400, ','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
|
||||||
|
|
||||||
|
data = NoticeConfigCRUD.add_notice_config(**form.data)
|
||||||
|
return self.jsonify(data.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigUpdateView(APIView):
|
||||||
|
url_prefix = (f'{prefix}/<int:_id>',)
|
||||||
|
|
||||||
|
def put(self, _id):
|
||||||
|
form = NoticeConfigUpdateForm(MultiDict(request.json))
|
||||||
|
if not form.validate():
|
||||||
|
abort(400, ','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
|
||||||
|
|
||||||
|
data = NoticeConfigCRUD.edit_notice_config(_id, **form.data)
|
||||||
|
return self.jsonify(data.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class CheckEmailServer(APIView):
|
||||||
|
url_prefix = (f'{prefix}/send_test_email',)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
receive_address = request.args.get('receive_address')
|
||||||
|
info = request.values.get('info')
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
result = NoticeConfigCRUD.test_send_email(receive_address, **info)
|
||||||
|
return self.jsonify(result=result)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error('test_send_email err:')
|
||||||
|
current_app.logger.error(e)
|
||||||
|
if 'Timed Out' in str(e):
|
||||||
|
abort(400, ErrFormat.email_send_timeout)
|
||||||
|
abort(400, f"{str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfigGetView(APIView):
|
||||||
|
method_decorators = []
|
||||||
|
url_prefix = (f'{prefix}/all',)
|
||||||
|
|
||||||
|
@auth_with_app_token
|
||||||
|
def get(self):
|
||||||
|
res = NoticeConfigCRUD.get_all()
|
||||||
|
return self.jsonify(res)
|
@@ -6,7 +6,9 @@ from flask import Blueprint
|
|||||||
from flask_restful import Api
|
from flask_restful import Api
|
||||||
|
|
||||||
from api.resource import register_resources
|
from api.resource import register_resources
|
||||||
from .account import LoginView, LogoutView, AuthWithKeyView
|
from .account import AuthWithKeyView
|
||||||
|
from .account import LoginView
|
||||||
|
from .account import LogoutView
|
||||||
|
|
||||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@@ -36,11 +36,13 @@ python-ldap==3.4.0
|
|||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
redis==4.6.0
|
redis==4.6.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
six==1.12.0
|
requests_oauthlib==1.3.1
|
||||||
|
markdownify==0.11.6
|
||||||
|
six==1.16.0
|
||||||
SQLAlchemy==1.4.49
|
SQLAlchemy==1.4.49
|
||||||
supervisor==4.0.3
|
supervisor==4.0.3
|
||||||
timeout-decorator==0.5.0
|
timeout-decorator==0.5.0
|
||||||
toposort==1.10
|
toposort==1.10
|
||||||
treelib==1.6.1
|
treelib==1.6.1
|
||||||
Werkzeug==2.3.6
|
Werkzeug==2.3.6
|
||||||
WTForms==3.0.0
|
WTForms==3.0.0
|
||||||
|
@@ -35,6 +35,7 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
|||||||
CACHE_TYPE = "redis"
|
CACHE_TYPE = "redis"
|
||||||
CACHE_REDIS_HOST = "127.0.0.1"
|
CACHE_REDIS_HOST = "127.0.0.1"
|
||||||
CACHE_REDIS_PORT = 6379
|
CACHE_REDIS_PORT = 6379
|
||||||
|
CACHE_REDIS_PASSWORD = ""
|
||||||
CACHE_KEY_PREFIX = "CMDB::"
|
CACHE_KEY_PREFIX = "CMDB::"
|
||||||
CACHE_DEFAULT_TIMEOUT = 3000
|
CACHE_DEFAULT_TIMEOUT = 3000
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ DEFAULT_PAGE_COUNT = 50
|
|||||||
|
|
||||||
# # permission
|
# # permission
|
||||||
WHITE_LIST = ["127.0.0.1"]
|
WHITE_LIST = ["127.0.0.1"]
|
||||||
USE_ACL = False
|
USE_ACL = True
|
||||||
|
|
||||||
# # elastic search
|
# # elastic search
|
||||||
ES_HOST = '127.0.0.1'
|
ES_HOST = '127.0.0.1'
|
||||||
@@ -94,4 +95,6 @@ USE_ES = False
|
|||||||
|
|
||||||
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
|
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
|
||||||
|
|
||||||
CMDB_API = "http://127.0.0.1:5000/api/v0.1"
|
# # messenger
|
||||||
|
USE_MESSENGER = True
|
||||||
|
MESSENGER_URL = "http://{messenger_url}/v1/message"
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""provide some sample data in database"""
|
"""provide some sample data in database"""
|
||||||
import uuid
|
|
||||||
import random
|
import random
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
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 (
|
from api.models.cmdb import (
|
||||||
Attribute,
|
Attribute,
|
||||||
CIType,
|
CIType,
|
||||||
@@ -12,16 +14,12 @@ from api.models.cmdb import (
|
|||||||
CITypeRelation,
|
CITypeRelation,
|
||||||
RelationType
|
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():
|
def force_add_user():
|
||||||
from flask import g
|
from flask_login import current_user, login_user
|
||||||
if not getattr(g, "user", None):
|
if not getattr(current_user, "username", None):
|
||||||
g.user = User.query.first()
|
login_user(User.query.first())
|
||||||
|
|
||||||
|
|
||||||
def init_attributes(num=1):
|
def init_attributes(num=1):
|
||||||
@@ -78,12 +76,12 @@ def init_relation_type(num=1):
|
|||||||
|
|
||||||
def init_ci_type_relation(num=1):
|
def init_ci_type_relation(num=1):
|
||||||
result = []
|
result = []
|
||||||
ci_types = init_ci_types(num+1)
|
ci_types = init_ci_types(num + 1)
|
||||||
relation_types = init_relation_type(num)
|
relation_types = init_relation_type(num)
|
||||||
for i in range(num):
|
for i in range(num):
|
||||||
result.append(CITypeRelation.create(
|
result.append(CITypeRelation.create(
|
||||||
parent_id=ci_types[i].id,
|
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
|
relation_type_id=relation_types[i].id
|
||||||
))
|
))
|
||||||
return result
|
return result
|
||||||
|
@@ -17,6 +17,8 @@
|
|||||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||||
"@riophae/vue-treeselect": "^0.4.0",
|
"@riophae/vue-treeselect": "^0.4.0",
|
||||||
"@vue/composition-api": "^1.7.1",
|
"@vue/composition-api": "^1.7.1",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^1.0.0",
|
||||||
"ant-design-vue": "^1.6.5",
|
"ant-design-vue": "^1.6.5",
|
||||||
"axios": "0.18.0",
|
"axios": "0.18.0",
|
||||||
"babel-eslint": "^8.2.2",
|
"babel-eslint": "^8.2.2",
|
||||||
@@ -37,6 +39,7 @@
|
|||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"relation-graph": "^1.1.0",
|
"relation-graph": "^1.1.0",
|
||||||
|
"snabbdom": "^3.5.1",
|
||||||
"sortablejs": "1.9.0",
|
"sortablejs": "1.9.0",
|
||||||
"viser-vue": "^2.4.8",
|
"viser-vue": "^2.4.8",
|
||||||
"vue": "2.6.11",
|
"vue": "2.6.11",
|
||||||
@@ -53,7 +56,7 @@
|
|||||||
"vuedraggable": "^2.23.0",
|
"vuedraggable": "^2.23.0",
|
||||||
"vuex": "^3.1.1",
|
"vuex": "^3.1.1",
|
||||||
"vxe-table": "3.6.9",
|
"vxe-table": "3.6.9",
|
||||||
"vxe-table-plugin-export-xlsx": "^3.0.4",
|
"vxe-table-plugin-export-xlsx": "2.0.0",
|
||||||
"xe-utils": "3",
|
"xe-utils": "3",
|
||||||
"xlsx": "0.15.0",
|
"xlsx": "0.15.0",
|
||||||
"xlsx-js-style": "^1.2.0"
|
"xlsx-js-style": "^1.2.0"
|
||||||
|
@@ -54,6 +54,84 @@
|
|||||||
<div class="content unicode" style="display: block;">
|
<div class="content unicode" style="display: block;">
|
||||||
<ul class="icon_lists dib-box">
|
<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">
|
<li class="dib">
|
||||||
<span class="icon iconfont"></span>
|
<span class="icon iconfont"></span>
|
||||||
<div class="name">rule_100</div>
|
<div class="name">rule_100</div>
|
||||||
@@ -3876,9 +3954,9 @@
|
|||||||
<pre><code class="language-css"
|
<pre><code class="language-css"
|
||||||
>@font-face {
|
>@font-face {
|
||||||
font-family: 'iconfont';
|
font-family: 'iconfont';
|
||||||
src: url('iconfont.woff2?t=1688550067963') format('woff2'),
|
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
|
||||||
url('iconfont.woff?t=1688550067963') format('woff'),
|
url('iconfont.woff?t=1694508259411') format('woff'),
|
||||||
url('iconfont.ttf?t=1688550067963') format('truetype');
|
url('iconfont.ttf?t=1694508259411') format('truetype');
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||||
@@ -3904,6 +3982,123 @@
|
|||||||
<div class="content font-class">
|
<div class="content font-class">
|
||||||
<ul class="icon_lists dib-box">
|
<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">
|
<li class="dib">
|
||||||
<span class="icon iconfont rule_100"></span>
|
<span class="icon iconfont rule_100"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
@@ -5759,11 +5954,11 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont itsm-node-strat"></span>
|
<span class="icon iconfont itsm-node-start"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
itsm-node-strat
|
itsm-node-strat
|
||||||
</div>
|
</div>
|
||||||
<div class="code-name">.itsm-node-strat
|
<div class="code-name">.itsm-node-start
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@@ -9637,6 +9832,110 @@
|
|||||||
<div class="content symbol">
|
<div class="content symbol">
|
||||||
<ul class="icon_lists dib-box">
|
<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">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#rule_100"></use>
|
<use xlink:href="#rule_100"></use>
|
||||||
@@ -11287,10 +11586,10 @@
|
|||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#itsm-node-strat"></use>
|
<use xlink:href="#itsm-node-start"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="name">itsm-node-strat</div>
|
<div class="name">itsm-node-strat</div>
|
||||||
<div class="code-name">#itsm-node-strat</div>
|
<div class="code-name">#itsm-node-start</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3857903 */
|
font-family: "iconfont"; /* Project id 3857903 */
|
||||||
src: url('iconfont.woff2?t=1688550067963') format('woff2'),
|
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
|
||||||
url('iconfont.woff?t=1688550067963') format('woff'),
|
url('iconfont.woff?t=1694508259411') format('woff'),
|
||||||
url('iconfont.ttf?t=1688550067963') format('truetype');
|
url('iconfont.ttf?t=1694508259411') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,58 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-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 {
|
.rule_100:before {
|
||||||
content: "\e87a";
|
content: "\e87a";
|
||||||
}
|
}
|
||||||
@@ -837,7 +889,7 @@
|
|||||||
content: "\e7ad";
|
content: "\e7ad";
|
||||||
}
|
}
|
||||||
|
|
||||||
.itsm-node-strat:before {
|
.itsm-node-start:before {
|
||||||
content: "\e7ae";
|
content: "\e7ae";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,97 @@
|
|||||||
"css_prefix_text": "",
|
"css_prefix_text": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"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",
|
"icon_id": "36304673",
|
||||||
"name": "rule_100",
|
"name": "rule_100",
|
||||||
@@ -1450,7 +1541,7 @@
|
|||||||
{
|
{
|
||||||
"icon_id": "35024980",
|
"icon_id": "35024980",
|
||||||
"name": "itsm-node-strat",
|
"name": "itsm-node-strat",
|
||||||
"font_class": "itsm-node-strat",
|
"font_class": "itsm-node-start",
|
||||||
"unicode": "e7ae",
|
"unicode": "e7ae",
|
||||||
"unicode_decimal": 59310
|
"unicode_decimal": 59310
|
||||||
},
|
},
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,6 +12,9 @@ import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
|
|||||||
import { AppDeviceEnquire } from '@/utils/mixin'
|
import { AppDeviceEnquire } from '@/utils/mixin'
|
||||||
import { debounce } from './utils/util'
|
import { debounce } from './utils/util'
|
||||||
|
|
||||||
|
import { h } from 'snabbdom'
|
||||||
|
import { DomEditor, Boot } from '@wangeditor/editor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [AppDeviceEnquire],
|
mixins: [AppDeviceEnquire],
|
||||||
provide() {
|
provide() {
|
||||||
@@ -47,6 +50,134 @@ export default {
|
|||||||
this.$store.dispatch('setWindowSize')
|
this.$store.dispatch('setWindowSize')
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 注册富文本自定义元素
|
||||||
|
const resume = {
|
||||||
|
type: 'attachment',
|
||||||
|
attachmentLabel: '',
|
||||||
|
attachmentValue: '',
|
||||||
|
children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||||
|
}
|
||||||
|
|
||||||
|
function withAttachment(editor) {
|
||||||
|
// JS 语法
|
||||||
|
const { isInline, isVoid } = editor
|
||||||
|
const newEditor = editor
|
||||||
|
|
||||||
|
newEditor.isInline = (elem) => {
|
||||||
|
const type = DomEditor.getNodeType(elem)
|
||||||
|
if (type === 'attachment') return true // 针对 type: attachment ,设置为 inline
|
||||||
|
return isInline(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
newEditor.isVoid = (elem) => {
|
||||||
|
const type = DomEditor.getNodeType(elem)
|
||||||
|
if (type === 'attachment') return true // 针对 type: attachment ,设置为 void
|
||||||
|
return isVoid(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEditor // 返回 newEditor ,重要!!!
|
||||||
|
}
|
||||||
|
Boot.registerPlugin(withAttachment)
|
||||||
|
/**
|
||||||
|
* 渲染“附件”元素到编辑器
|
||||||
|
* @param elem 附件元素,即上文的 myResume
|
||||||
|
* @param children 元素子节点,void 元素可忽略
|
||||||
|
* @param editor 编辑器实例
|
||||||
|
* @returns vnode 节点(通过 snabbdom.js 的 h 函数生成)
|
||||||
|
*/
|
||||||
|
function renderAttachment(elem, children, editor) {
|
||||||
|
// JS 语法
|
||||||
|
|
||||||
|
// 获取“附件”的数据,参考上文 myResume 数据结构
|
||||||
|
const { attachmentLabel = '', attachmentValue = '' } = elem
|
||||||
|
|
||||||
|
// 附件元素 vnode
|
||||||
|
const attachVnode = h(
|
||||||
|
// HTML tag
|
||||||
|
'span',
|
||||||
|
// HTML 属性、样式、事件
|
||||||
|
{
|
||||||
|
props: { contentEditable: false }, // HTML 属性,驼峰式写法
|
||||||
|
style: {
|
||||||
|
display: 'inline-block',
|
||||||
|
margin: '0 3px',
|
||||||
|
padding: '0 3px',
|
||||||
|
backgroundColor: '#e6f7ff',
|
||||||
|
border: '1px solid #91d5ff',
|
||||||
|
borderRadius: '2px',
|
||||||
|
color: '#1890ff',
|
||||||
|
}, // style ,驼峰式写法
|
||||||
|
on: {
|
||||||
|
click() {
|
||||||
|
console.log('clicked', attachmentValue)
|
||||||
|
} /* 其他... */,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 子节点
|
||||||
|
[attachmentLabel]
|
||||||
|
)
|
||||||
|
|
||||||
|
return attachVnode
|
||||||
|
}
|
||||||
|
const renderElemConf = {
|
||||||
|
type: 'attachment', // 新元素 type ,重要!!!
|
||||||
|
renderElem: renderAttachment,
|
||||||
|
}
|
||||||
|
Boot.registerRenderElem(renderElemConf)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成“附件”元素的 HTML
|
||||||
|
* @param elem 附件元素,即上文的 myResume
|
||||||
|
* @param childrenHtml 子节点的 HTML 代码,void 元素可忽略
|
||||||
|
* @returns “附件”元素的 HTML 字符串
|
||||||
|
*/
|
||||||
|
function attachmentToHtml(elem, childrenHtml) {
|
||||||
|
// JS 语法
|
||||||
|
|
||||||
|
// 获取附件元素的数据
|
||||||
|
const { attachmentValue = '', attachmentLabel = '' } = elem
|
||||||
|
|
||||||
|
// 生成 HTML 代码
|
||||||
|
const html = `<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline data-attachmentValue="${attachmentValue}" data-attachmentLabel="${attachmentLabel}">${attachmentLabel}</span>`
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
const elemToHtmlConf = {
|
||||||
|
type: 'attachment', // 新元素的 type ,重要!!!
|
||||||
|
elemToHtml: attachmentToHtml,
|
||||||
|
}
|
||||||
|
Boot.registerElemToHtml(elemToHtmlConf)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 HTML 字符串,生成“附件”元素
|
||||||
|
* @param domElem HTML 对应的 DOM Element
|
||||||
|
* @param children 子节点
|
||||||
|
* @param editor editor 实例
|
||||||
|
* @returns “附件”元素,如上文的 myResume
|
||||||
|
*/
|
||||||
|
function parseAttachmentHtml(domElem, children, editor) {
|
||||||
|
// JS 语法
|
||||||
|
|
||||||
|
// 从 DOM element 中获取“附件”的信息
|
||||||
|
const attachmentValue = domElem.getAttribute('data-attachmentValue') || ''
|
||||||
|
const attachmentLabel = domElem.getAttribute('data-attachmentLabel') || ''
|
||||||
|
|
||||||
|
// 生成“附件”元素(按照此前约定的数据结构)
|
||||||
|
const myResume = {
|
||||||
|
type: 'attachment',
|
||||||
|
attachmentValue,
|
||||||
|
attachmentLabel,
|
||||||
|
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!!
|
||||||
|
}
|
||||||
|
|
||||||
|
return myResume
|
||||||
|
}
|
||||||
|
const parseHtmlConf = {
|
||||||
|
selector: 'span[data-w-e-type="attachment"]', // CSS 选择器,匹配特定的 HTML 标签
|
||||||
|
parseElemHtml: parseAttachmentHtml,
|
||||||
|
}
|
||||||
|
Boot.registerParseElemHtml(parseHtmlConf)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
clearInterval(this.timer)
|
clearInterval(this.timer)
|
||||||
|
@@ -20,13 +20,7 @@ export function putCompanyInfo(id, parameter) {
|
|||||||
data: parameter,
|
data: parameter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function postImageFile(parameter) {
|
|
||||||
return axios({
|
|
||||||
url: '/common-setting/v1/file',
|
|
||||||
method: 'post',
|
|
||||||
data: parameter,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export function getDepartmentList(params) {
|
export function getDepartmentList(params) {
|
||||||
// ?department_parent_id=-1 查询第一级部门,下面的id根据实际的传
|
// ?department_parent_id=-1 查询第一级部门,下面的id根据实际的传
|
||||||
return axios({
|
return axios({
|
||||||
|
@@ -117,3 +117,11 @@ export function getEmployeeListByFilter(data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNoticeByEmployeeIds(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/common-setting/v1/employee/get_notice_by_ids',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
31
cmdb-ui/src/api/file.js
Normal file
31
cmdb-ui/src/api/file.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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,7 +68,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
visibleChange(open) {
|
visibleChange(open, isInitOne = true) {
|
||||||
|
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||||
@@ -151,15 +152,20 @@ export default {
|
|||||||
})
|
})
|
||||||
this.ruleList = [...expArray]
|
this.ruleList = [...expArray]
|
||||||
} else if (open) {
|
} else if (open) {
|
||||||
this.ruleList = [
|
this.ruleList = isInitOne
|
||||||
{
|
? [
|
||||||
id: uuidv4(),
|
{
|
||||||
type: 'and',
|
id: uuidv4(),
|
||||||
property: this.canSearchPreferenceAttrList[0].name,
|
type: 'and',
|
||||||
exp: 'is',
|
property:
|
||||||
value: null,
|
this.canSearchPreferenceAttrList && this.canSearchPreferenceAttrList.length
|
||||||
},
|
? this.canSearchPreferenceAttrList[0].name
|
||||||
]
|
: undefined,
|
||||||
|
exp: 'is',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleClear() {
|
handleClear() {
|
||||||
|
@@ -15,31 +15,117 @@
|
|||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</div>
|
</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>
|
||||||
<div class="custom-icon-select-popover-content">
|
<div class="custom-icon-select-popover-content">
|
||||||
<div v-for="category in iconList" :key="category.value">
|
<template v-if="iconList && iconList.length">
|
||||||
<h4 class="category">{{ category.label }}</h4>
|
<template v-if="currentIconType !== '4'">
|
||||||
<div class="custom-icon-select-popover-content-wrapper">
|
<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
|
<div
|
||||||
v-for="name in category.list"
|
v-for="icon in iconList"
|
||||||
:key="name.value"
|
:key="icon.id"
|
||||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
:class="`custom-icon-select-popover-item ${value.id === icon.id ? 'selected' : ''}`"
|
||||||
@click="clickIcon(name.value)"
|
@click="clickCustomIcon(icon)"
|
||||||
>
|
>
|
||||||
<ops-icon :type="name.value" />
|
<div class="custom-icon-select-popover-content-img-box">
|
||||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="currentIconType !== '0' && currentIconType !== '3'">
|
<template v-if="!['0', '3', '4'].includes(currentIconType)">
|
||||||
<a-divider :style="{ margin: '5px 0' }" />
|
<a-divider :style="{ margin: '5px 0' }" />
|
||||||
<el-color-picker size="mini" v-model="value.color"> </el-color-picker>
|
<el-color-picker size="mini" v-model="value.color"> </el-color-picker>
|
||||||
</template>
|
</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>
|
||||||
|
|
||||||
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect">
|
<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
|
<ops-icon
|
||||||
|
v-else
|
||||||
:type="value.name"
|
:type="value.name"
|
||||||
:style="{ color: value.name && value.name.startsWith('icon-') ? value.color || '' : '' }"
|
:style="{ color: value.name && value.name.startsWith('icon-') ? value.color || '' : '' }"
|
||||||
/>
|
/>
|
||||||
@@ -56,6 +142,8 @@ import {
|
|||||||
fillIconList,
|
fillIconList,
|
||||||
multicolorIconList,
|
multicolorIconList,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
import { postImageFile, getFileData, addFileData, deleteFileData } from '@/api/file'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CustomIconSelect',
|
name: 'CustomIconSelect',
|
||||||
components: { ElColorPicker: ColorPicker },
|
components: { ElColorPicker: ColorPicker },
|
||||||
@@ -77,13 +165,18 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
form: this.$form.createForm(this),
|
||||||
iconTypeList,
|
iconTypeList,
|
||||||
commonIconList,
|
commonIconList,
|
||||||
linearIconList,
|
linearIconList,
|
||||||
fillIconList,
|
fillIconList,
|
||||||
multicolorIconList,
|
multicolorIconList,
|
||||||
visible: false,
|
visible: false,
|
||||||
currentIconType: '1',
|
currentIconType: '3',
|
||||||
|
customIconList: [],
|
||||||
|
formVisible: false,
|
||||||
|
formImg: null,
|
||||||
|
file: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -97,18 +190,30 @@ export default {
|
|||||||
return this.fillIconList
|
return this.fillIconList
|
||||||
case '3': // 多色
|
case '3': // 多色
|
||||||
return this.multicolorIconList
|
return this.multicolorIconList
|
||||||
|
case '4': // 自定义
|
||||||
|
return this.customIconList
|
||||||
default:
|
default:
|
||||||
return this.linearIconList
|
return this.linearIconList
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fileName() {
|
||||||
|
const splitFileName = this.file.name.split('.')
|
||||||
|
return splitFileName.splice(0, splitFileName.length - 1).join('')
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('click', this.eventListener)
|
document.addEventListener('click', this.eventListener)
|
||||||
|
this.getFileData()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('click', this.eventListener)
|
document.removeEventListener('click', this.eventListener)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getFileData() {
|
||||||
|
getFileData('ops-custom-icon').then((res) => {
|
||||||
|
this.customIconList = res
|
||||||
|
})
|
||||||
|
},
|
||||||
eventListener(e) {
|
eventListener(e) {
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
const dom = document.getElementById(`custom-icon-select-popover`)
|
const dom = document.getElementById(`custom-icon-select-popover`)
|
||||||
@@ -137,25 +242,87 @@ 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() {
|
showSelect() {
|
||||||
this.visible = true
|
this.visible = true
|
||||||
|
console.log(this.value)
|
||||||
if (!this.value.name) {
|
if (!this.value.name) {
|
||||||
this.currentIconType = '1'
|
this.currentIconType = '3'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// changyong已废弃
|
||||||
if (this.value.name.startsWith('changyong-')) {
|
if (this.value.name.startsWith('changyong-')) {
|
||||||
this.currentIconType = '0'
|
this.currentIconType = '0'
|
||||||
} else if (this.value.name.startsWith('icon-xianxing')) {
|
} else if (this.value.name.startsWith('icon-xianxing')) {
|
||||||
this.currentIconType = '1'
|
this.currentIconType = '1'
|
||||||
} else if (this.value.name.startsWith('icon-shidi')) {
|
} else if (this.value.name.startsWith('icon-shidi')) {
|
||||||
this.currentIconType = '2'
|
this.currentIconType = '2'
|
||||||
} else {
|
} else if (this.value.name.startsWith('caise')) {
|
||||||
this.currentIconType = '3'
|
this.currentIconType = '3'
|
||||||
|
} else {
|
||||||
|
this.currentIconType = '4'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleChangeIconType(value) {
|
handleChangeIconType(value) {
|
||||||
this.currentIconType = 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>
|
</script>
|
||||||
@@ -176,7 +343,7 @@ export default {
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
}
|
}
|
||||||
.custom-icon-select-popover-content {
|
.custom-icon-select-popover-content {
|
||||||
max-height: 400px;
|
height: 400px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
.category {
|
.category {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -197,12 +364,43 @@ export default {
|
|||||||
padding: 5px 5px 2px 5px;
|
padding: 5px 5px 2px 5px;
|
||||||
margin: 0 2px 6px;
|
margin: 0 2px 6px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
position: relative;
|
||||||
.custom-icon-select-popover-item-label {
|
.custom-icon-select-popover-item-label {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #eeeeee;
|
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 {
|
.selected {
|
||||||
@@ -212,6 +410,8 @@ export default {
|
|||||||
}
|
}
|
||||||
.custom-icon-select-popover-icon-type {
|
.custom-icon-select-popover-icon-type {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
> div {
|
> div {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -224,6 +424,16 @@ export default {
|
|||||||
.selected {
|
.selected {
|
||||||
border-color: #2f54eb;
|
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>
|
</style>
|
||||||
@@ -234,15 +444,39 @@ export default {
|
|||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #eeeeee;
|
border: 1px solid #d9d9d9;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
> i {
|
> i,
|
||||||
|
> img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
> img {
|
||||||
|
max-width: 26px;
|
||||||
|
max-height: 26px;
|
||||||
|
}
|
||||||
|
> i {
|
||||||
font-size: 18px;
|
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>
|
</style>
|
||||||
|
@@ -222,6 +222,9 @@ export default {
|
|||||||
renderIcon({ icon, selectedIcon, customIcon = undefined, name = undefined, typeId = undefined, routeName }) {
|
renderIcon({ icon, selectedIcon, customIcon = undefined, name = undefined, typeId = undefined, routeName }) {
|
||||||
if (typeId) {
|
if (typeId) {
|
||||||
if (customIcon) {
|
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
|
return <ops-icon
|
||||||
style={{
|
style={{
|
||||||
color: customIcon.split('$$')[1],
|
color: customIcon.split('$$')[1],
|
||||||
|
@@ -1,207 +1,215 @@
|
|||||||
import { axios } from '@/utils/request'
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 所有的 ci_types
|
* 获取 所有的 ci_types
|
||||||
* @param parameter
|
* @param parameter
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function getCITypes(parameter) {
|
export function getCITypes(parameter) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_types',
|
url: '/v0.1/ci_types',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: parameter
|
params: parameter
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 某个 ci_types
|
* 获取 某个 ci_types
|
||||||
* @param CITypeName
|
* @param CITypeName
|
||||||
* @param parameter
|
* @param parameter
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function getCIType(CITypeName, parameter) {
|
export function getCIType(CITypeName, parameter) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${CITypeName}`,
|
url: `/v0.1/ci_types/${CITypeName}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: parameter
|
params: parameter
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 ci_type
|
* 创建 ci_type
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function createCIType(data) {
|
export function createCIType(data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: '/v0.1/ci_types',
|
url: '/v0.1/ci_types',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新 ci_type
|
* 更新 ci_type
|
||||||
* @param CITypeId
|
* @param CITypeId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function updateCIType(CITypeId, data) {
|
export function updateCIType(CITypeId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${CITypeId}`,
|
url: `/v0.1/ci_types/${CITypeId}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除 ci_type
|
* 删除 ci_type
|
||||||
* @param CITypeId
|
* @param CITypeId
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function deleteCIType(CITypeId) {
|
export function deleteCIType(CITypeId) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${CITypeId}`,
|
url: `/v0.1/ci_types/${CITypeId}`,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 某个 ci_type 的分组
|
* 获取 某个 ci_type 的分组
|
||||||
* @param CITypeId
|
* @param CITypeId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function getCITypeGroupById(CITypeId, data) {
|
export function getCITypeGroupById(CITypeId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: data
|
params: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存 某个 ci_type 的分组
|
* 保存 某个 ci_type 的分组
|
||||||
* @param CITypeId
|
* @param CITypeId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function createCITypeGroupById(CITypeId, data) {
|
export function createCITypeGroupById(CITypeId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改 某个 ci_type 的分组
|
* 修改 某个 ci_type 的分组
|
||||||
* @param groupId
|
* @param groupId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function updateCITypeGroupById(groupId, data) {
|
export function updateCITypeGroupById(groupId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除 某个 ci_type 的分组
|
* 删除 某个 ci_type 的分组
|
||||||
* @param groupId
|
* @param groupId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {AxiosPromise}
|
* @returns {AxiosPromise}
|
||||||
*/
|
*/
|
||||||
export function deleteCITypeGroupById(groupId, data) {
|
export function deleteCITypeGroupById(groupId, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUniqueConstraintList(type_id) {
|
export function getUniqueConstraintList(type_id) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addUniqueConstraint(type_id, data) {
|
export function addUniqueConstraint(type_id, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUniqueConstraint(type_id, id, data) {
|
export function updateUniqueConstraint(type_id, id, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUniqueConstraint(type_id, id) {
|
export function deleteUniqueConstraint(type_id, id) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTriggerList(type_id) {
|
export function getTriggerList(type_id) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addTrigger(type_id, data) {
|
export function addTrigger(type_id, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTrigger(type_id, id, data) {
|
export function updateTrigger(type_id, id, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteTrigger(type_id, id) {
|
export function deleteTrigger(type_id, id) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CMDB的模型和实例的授权接口
|
// CMDB的模型和实例的授权接口
|
||||||
export function grantCiType(type_id, rid, data) {
|
export function grantCiType(type_id, rid, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
|
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// CMDB的模型和实例的删除授权接口
|
// CMDB的模型和实例的删除授权接口
|
||||||
export function revokeCiType(type_id, rid, data) {
|
export function revokeCiType(type_id, rid, data) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
|
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// CMDB的模型和实例的过滤的权限
|
// CMDB的模型和实例的过滤的权限
|
||||||
export function ciTypeFilterPermissions(type_id) {
|
export function ciTypeFilterPermissions(type_id) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/ci_types/${type_id}/filters/permissions`,
|
url: `/v0.1/ci_types/${type_id}/filters/permissions`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAllDagsName(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v1/dag/all_names',
|
||||||
|
method: 'GET',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -77,6 +77,14 @@ export function getCITypeAttributesByTypeIds(params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCITypeCommonAttributesByTypeIds(params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_types/common_attributes`,
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除属性
|
* 删除属性
|
||||||
* @param attrId
|
* @param attrId
|
||||||
@@ -153,3 +161,10 @@ export function canDefineComputed() {
|
|||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calcComputedAttribute(attr_id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/attributes/${attr_id}/calc_computed_attribute`,
|
||||||
|
method: 'PUT',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -61,3 +61,10 @@ export function revokeTypeRelation(first_type_id, second_type_id, rid, data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRecursive_level2children(type_id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_type_relations/${type_id}/recursive_level2children`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -37,3 +37,11 @@ export function batchUpdateCustomDashboard(data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postCustomDashboardPreview(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/custom_dashboard/preview',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -1,40 +1,56 @@
|
|||||||
import { axios } from '@/utils/request'
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
export function getCIHistory (ciId) {
|
export function getCIHistory(ciId) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/history/ci/${ciId}`,
|
url: `/v0.1/history/ci/${ciId}`,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCIHistoryTable (params) {
|
export function getCIHistoryTable(params) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/history/records/attribute`,
|
url: `/v0.1/history/records/attribute`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRelationTable (params) {
|
export function getRelationTable(params) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/history/records/relation`,
|
url: `/v0.1/history/records/relation`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCITypesTable (params) {
|
export function getCITypesTable(params) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v0.1/history/ci_types`,
|
url: `/v0.1/history/ci_types`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsers (params) {
|
export function getUsers(params) {
|
||||||
return axios({
|
return axios({
|
||||||
url: `/v1/acl/users/employee`,
|
url: `/v1/acl/users/employee`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCiTriggers(params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/history/ci_triggers`,
|
||||||
|
method: 'GET',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCiTriggersByCiId(ci_id, params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/history/ci_triggers/${ci_id}`,
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BIN
cmdb-ui/src/modules/cmdb/assets/dashboard_empty.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/dashboard_empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
@@ -0,0 +1,2 @@
|
|||||||
|
import NoticeContent from './index.vue'
|
||||||
|
export default NoticeContent
|
199
cmdb-ui/src/modules/cmdb/components/noticeContent/index.vue
Normal file
199
cmdb-ui/src/modules/cmdb/components/noticeContent/index.vue
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<div class="notice-content">
|
||||||
|
<div class="notice-content-main">
|
||||||
|
<Toolbar
|
||||||
|
:editor="editor"
|
||||||
|
:defaultConfig="{
|
||||||
|
excludeKeys: [
|
||||||
|
'emotion',
|
||||||
|
'group-image',
|
||||||
|
'group-video',
|
||||||
|
'insertTable',
|
||||||
|
'codeBlock',
|
||||||
|
'blockquote',
|
||||||
|
'fullScreen',
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
mode="default"
|
||||||
|
/>
|
||||||
|
<Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" />
|
||||||
|
<div class="notice-content-sidebar">
|
||||||
|
<template v-if="needOld">
|
||||||
|
<div class="notice-content-sidebar-divider">变更前</div>
|
||||||
|
<div
|
||||||
|
@dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)"
|
||||||
|
class="notice-content-sidebar-item"
|
||||||
|
v-for="attr in attrList"
|
||||||
|
:key="`old_${attr.id}`"
|
||||||
|
:title="attr.alias || attr.name"
|
||||||
|
>
|
||||||
|
{{ attr.alias || attr.name }}
|
||||||
|
</div>
|
||||||
|
<div class="notice-content-sidebar-divider">变更后</div>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
@dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)"
|
||||||
|
class="notice-content-sidebar-item"
|
||||||
|
v-for="attr in attrList"
|
||||||
|
:key="attr.id"
|
||||||
|
:title="attr.alias || attr.name"
|
||||||
|
>
|
||||||
|
{{ attr.alias || attr.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import '@wangeditor/editor/dist/css/style.css'
|
||||||
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
|
export default {
|
||||||
|
name: 'NoticeContent',
|
||||||
|
components: { Editor, Toolbar },
|
||||||
|
props: {
|
||||||
|
attrList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
needOld: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
editorConfig: { placeholder: '请输入通知内容', readOnly: this.readOnly },
|
||||||
|
content: '',
|
||||||
|
defaultParams: [],
|
||||||
|
value2LabelMap: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
const editor = this.editor
|
||||||
|
if (editor == null) return
|
||||||
|
editor.destroy() // 组件销毁时,及时销毁编辑器
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCreated(editor) {
|
||||||
|
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
|
||||||
|
},
|
||||||
|
getContent() {
|
||||||
|
const html = _.cloneDeep(this.editor.getHtml())
|
||||||
|
const _html = html.replace(
|
||||||
|
/<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline.*?<\/span>/gm,
|
||||||
|
(value) => {
|
||||||
|
const _match = value.match(/(?<=data-attachmentValue=").*?(?=")/)
|
||||||
|
return `{{${_match}}}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return { body_html: html, body: _html }
|
||||||
|
},
|
||||||
|
setContent(html) {
|
||||||
|
this.editor.setHtml(html)
|
||||||
|
},
|
||||||
|
dblclickSidebar(value, label) {
|
||||||
|
if (!this.readOnly) {
|
||||||
|
this.editor.restoreSelection()
|
||||||
|
|
||||||
|
const node = {
|
||||||
|
type: 'attachment',
|
||||||
|
attachmentValue: value,
|
||||||
|
attachmentLabel: `${label}`,
|
||||||
|
children: [{ text: '' }],
|
||||||
|
}
|
||||||
|
this.editor.insertNode(node)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
.notice-content {
|
||||||
|
width: 100%;
|
||||||
|
& &-main {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
position: relative;
|
||||||
|
.notice-content-editor {
|
||||||
|
height: 300px;
|
||||||
|
width: 75%;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-top: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.notice-content-sidebar {
|
||||||
|
width: 25%;
|
||||||
|
position: absolute;
|
||||||
|
height: 300px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-top: none;
|
||||||
|
border-right: none;
|
||||||
|
overflow: auto;
|
||||||
|
.notice-content-sidebar-divider {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #afafaf;
|
||||||
|
background-color: #fff;
|
||||||
|
line-height: 20px;
|
||||||
|
padding-left: 12px;
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-top: 1px solid #d1d1d1;
|
||||||
|
top: 50%;
|
||||||
|
transition: translateY(-50%);
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
left: 3px;
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
right: 3px;
|
||||||
|
width: 78px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.notice-content-sidebar-item:first-child {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.notice-content-sidebar-item {
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 4px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
&:hover {
|
||||||
|
background-color: #custom_colors[color_2];
|
||||||
|
color: #custom_colors[color_1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import '~@/style/static.less';
|
||||||
|
|
||||||
|
.notice-content {
|
||||||
|
.w-e-bar {
|
||||||
|
background-color: #custom_colors[color_2];
|
||||||
|
}
|
||||||
|
.w-e-text-placeholder {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
144
cmdb-ui/src/modules/cmdb/components/webhook/authorization.vue
Normal file
144
cmdb-ui/src/modules/cmdb/components/webhook/authorization.vue
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div class="authorization-wrapper">
|
||||||
|
<div class="authorization-header">
|
||||||
|
<a-space>
|
||||||
|
<span>Authorization Type</span>
|
||||||
|
<a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true">
|
||||||
|
<a-select-option value="none">
|
||||||
|
None
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="BasicAuth">
|
||||||
|
Basic Auth
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="Bearer">
|
||||||
|
Bearer
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="APIKey">
|
||||||
|
APIKey
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="OAuth2.0">
|
||||||
|
OAuth2.0
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:10px">
|
||||||
|
<table v-if="authorizationType === 'BasicAuth'">
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="BasicAuth.username" placeholder="用户名" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="BasicAuth.password" placeholder="密码" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table v-else-if="authorizationType === 'Bearer'">
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table v-else-if="authorizationType === 'APIKey'">
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table v-else-if="authorizationType === 'OAuth2.0'">
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a-input
|
||||||
|
class="authorization-input"
|
||||||
|
v-model="OAuth2.authorization_base_url"
|
||||||
|
placeholder="authorization_base_url"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a-empty
|
||||||
|
v-else
|
||||||
|
:image-style="{
|
||||||
|
height: '60px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||||
|
<span slot="description"> 暂无请求认证 </span>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Authorization',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
authorizationType: 'none',
|
||||||
|
BasicAuth: {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
Bearer: {
|
||||||
|
token: '',
|
||||||
|
},
|
||||||
|
APIKey: {
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
OAuth2: {
|
||||||
|
client_id: '',
|
||||||
|
client_secret: '',
|
||||||
|
authorization_base_url: '',
|
||||||
|
token_url: '',
|
||||||
|
redirect_url: '',
|
||||||
|
scope: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.authorization-wrapper {
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.authorization-input {
|
||||||
|
border: none;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
79
cmdb-ui/src/modules/cmdb/components/webhook/body.vue
Normal file
79
cmdb-ui/src/modules/cmdb/components/webhook/body.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="body-wrapper">
|
||||||
|
<div class="body-header">
|
||||||
|
<!-- <a-space>
|
||||||
|
<span>Content Type</span>
|
||||||
|
<a-select size="small" v-model="contentType" style="width: 200px" :showSearch="true">
|
||||||
|
<a-select-option value="none">
|
||||||
|
None
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-opt-group v-for="item in segmentedContentTypes" :key="item.title" :label="item.title">
|
||||||
|
<a-select-option v-for="ele in item.contentTypes" :key="ele" :value="ele">
|
||||||
|
{{ ele }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select-opt-group>
|
||||||
|
</a-select>
|
||||||
|
</a-space> -->
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:10px">
|
||||||
|
<vue-json-editor v-model="jsonData" :showBtns="false" :mode="'text'" />
|
||||||
|
<!-- <a-empty
|
||||||
|
v-else
|
||||||
|
:image-style="{
|
||||||
|
height: '60px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||||
|
<span slot="description"> 暂无请求体 </span>
|
||||||
|
</a-empty> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import vueJsonEditor from 'vue-json-editor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Body',
|
||||||
|
components: { vueJsonEditor },
|
||||||
|
data() {
|
||||||
|
const segmentedContentTypes = [
|
||||||
|
{
|
||||||
|
title: 'text',
|
||||||
|
contentTypes: [
|
||||||
|
'application/json',
|
||||||
|
'application/ld+json',
|
||||||
|
'application/hal+json',
|
||||||
|
'application/vnd.api+json',
|
||||||
|
'application/xml',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'structured',
|
||||||
|
contentTypes: ['application/x-www-form-urlencoded', 'multipart/form-data'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'others',
|
||||||
|
contentTypes: ['text/html', 'text/plain'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
segmentedContentTypes,
|
||||||
|
// contentType: 'none',
|
||||||
|
jsonData: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
||||||
|
<style lang="less">
|
||||||
|
.body-wrapper {
|
||||||
|
div.jsoneditor-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.jsoneditor {
|
||||||
|
border-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
101
cmdb-ui/src/modules/cmdb/components/webhook/header.vue
Normal file
101
cmdb-ui/src/modules/cmdb/components/webhook/header.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="headers-header">
|
||||||
|
<span>请求参数</span>
|
||||||
|
<a-space>
|
||||||
|
<a-tooltip title="清空">
|
||||||
|
<ops-icon
|
||||||
|
type="icon-xianxing-delete"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
headers = [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="新增">
|
||||||
|
<a-icon type="plus" @click="add" />
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div class="headers-box">
|
||||||
|
<table>
|
||||||
|
<tr v-for="(item, index) in headers" :key="item.id">
|
||||||
|
<td><a-input class="headers-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td>
|
||||||
|
<td><a-input class="headers-input" v-model="item.value" :placeholder="`值${index + 1}`" /></td>
|
||||||
|
<td>
|
||||||
|
<a style="color:red">
|
||||||
|
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
uuidv4,
|
||||||
|
add() {
|
||||||
|
this.headers.push({
|
||||||
|
id: uuidv4(),
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteParam(index) {
|
||||||
|
this.headers.splice(index, 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.headers-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.headers-box {
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.headers-input {
|
||||||
|
border: none;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
2
cmdb-ui/src/modules/cmdb/components/webhook/index.js
Normal file
2
cmdb-ui/src/modules/cmdb/components/webhook/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Webhook from './index.vue'
|
||||||
|
export default Webhook
|
140
cmdb-ui/src/modules/cmdb/components/webhook/index.vue
Normal file
140
cmdb-ui/src/modules/cmdb/components/webhook/index.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-input-group compact>
|
||||||
|
<treeselect
|
||||||
|
:disable-branch-nodes="true"
|
||||||
|
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||||
|
:style="{
|
||||||
|
'--custom-height': '30px',
|
||||||
|
lineHeight: '30px',
|
||||||
|
'--custom-bg-color': '#fff',
|
||||||
|
'--custom-border': '1px solid #d9d9d9',
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100px',
|
||||||
|
}"
|
||||||
|
v-model="method"
|
||||||
|
:multiple="false"
|
||||||
|
:clearable="false"
|
||||||
|
searchable
|
||||||
|
:options="methodList"
|
||||||
|
value-consists-of="LEAF_PRIORITY"
|
||||||
|
placeholder="请选择方式"
|
||||||
|
>
|
||||||
|
</treeselect>
|
||||||
|
<a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-tabs>
|
||||||
|
<a-tab-pane key="Parameters" tab="Parameters">
|
||||||
|
<Parameters ref="Parameters" />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="Body" tab="Body" force-render>
|
||||||
|
<Body ref="Body" />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="Headers" tab="Headers" force-render>
|
||||||
|
<Header ref="Header" />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="Authorization" tab="Authorization" force-render>
|
||||||
|
<Authorization ref="Authorization" />
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import Parameters from './paramaters.vue'
|
||||||
|
import Body from './body.vue'
|
||||||
|
import Header from './header.vue'
|
||||||
|
import Authorization from './authorization.vue'
|
||||||
|
export default {
|
||||||
|
name: 'Webhook',
|
||||||
|
components: { Parameters, Body, Header, Authorization },
|
||||||
|
data() {
|
||||||
|
const methodList = [
|
||||||
|
{
|
||||||
|
id: 'GET',
|
||||||
|
label: 'GET',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'POST',
|
||||||
|
label: 'POST',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'PUT',
|
||||||
|
label: 'PUT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'DELETE',
|
||||||
|
label: 'DELETE',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
methodList,
|
||||||
|
method: 'GET',
|
||||||
|
url: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getParams() {
|
||||||
|
const parameters = {}
|
||||||
|
this.$refs.Parameters.parameters.forEach((item) => {
|
||||||
|
parameters[item.key] = item.value
|
||||||
|
})
|
||||||
|
const body = this.$refs.Body.jsonData
|
||||||
|
const headers = {}
|
||||||
|
this.$refs.Header.headers.forEach((item) => {
|
||||||
|
headers[item.key] = item.value
|
||||||
|
})
|
||||||
|
let authorization = {}
|
||||||
|
const type = this.$refs.Authorization.authorizationType
|
||||||
|
if (type !== 'none') {
|
||||||
|
if (type === 'OAuth2.0') {
|
||||||
|
authorization = { ...this.$refs.Authorization['OAuth2'], type }
|
||||||
|
} else {
|
||||||
|
authorization = { ...this.$refs.Authorization[type], type }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { method, url } = this
|
||||||
|
return { method, url, parameters, body, headers, authorization }
|
||||||
|
},
|
||||||
|
setParams(params) {
|
||||||
|
console.log(2222, params)
|
||||||
|
const { method, url, parameters, body, headers, authorization = {} } = params ?? {}
|
||||||
|
this.method = method
|
||||||
|
this.url = url
|
||||||
|
this.$refs.Parameters.parameters =
|
||||||
|
Object.keys(parameters).map((key) => {
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
key: key,
|
||||||
|
value: parameters[key],
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
this.$refs.Body.jsonData = body
|
||||||
|
this.$refs.Header.headers =
|
||||||
|
Object.keys(headers).map((key) => {
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
key: key,
|
||||||
|
value: headers[key],
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
const { type = 'none' } = authorization
|
||||||
|
console.log(type)
|
||||||
|
this.$refs.Authorization.authorizationType = type
|
||||||
|
if (type !== 'none') {
|
||||||
|
const _authorization = _.cloneDeep(authorization)
|
||||||
|
delete _authorization.type
|
||||||
|
if (type === 'OAuth2.0') {
|
||||||
|
this.$refs.Authorization.OAuth2 = _authorization
|
||||||
|
} else {
|
||||||
|
this.$refs.Authorization[type] = _authorization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
100
cmdb-ui/src/modules/cmdb/components/webhook/paramaters.vue
Normal file
100
cmdb-ui/src/modules/cmdb/components/webhook/paramaters.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="parameters-header">
|
||||||
|
<span>请求参数</span>
|
||||||
|
<a-space>
|
||||||
|
<a-tooltip title="清空">
|
||||||
|
<ops-icon
|
||||||
|
type="icon-xianxing-delete"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
parameters = []
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="新增">
|
||||||
|
<a-icon type="plus" @click="add" />
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div class="parameters-box" v-if="parameters && parameters.length">
|
||||||
|
<table>
|
||||||
|
<tr v-for="(item, index) in parameters" :key="item.id">
|
||||||
|
<td><a-input class="parameters-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td>
|
||||||
|
<td><a-input class="parameters-input" v-model="item.value" :placeholder="`值${index + 1}`" /></td>
|
||||||
|
<td>
|
||||||
|
<a style="color:red">
|
||||||
|
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<a-empty
|
||||||
|
v-else
|
||||||
|
:image-style="{
|
||||||
|
height: '60px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||||
|
<span slot="description"> 暂无请求参数 </span>
|
||||||
|
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
|
||||||
|
添加
|
||||||
|
</a-button>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Parameters',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
parameters: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.parameters.push({
|
||||||
|
id: uuidv4(),
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteParam(index) {
|
||||||
|
this.parameters.splice(index, 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.parameters-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.parameters-box {
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.parameters-input {
|
||||||
|
border: none;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,5 +1,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import XLSXS from 'xlsx-js-style'
|
||||||
export function sum(arr) {
|
export function sum(arr) {
|
||||||
if (!arr.length) {
|
if (!arr.length) {
|
||||||
return 0
|
return 0
|
||||||
@@ -149,4 +151,26 @@ export const toThousands = (num = 0) => {
|
|||||||
return num.toString().replace(/\d+/, function (n) {
|
return num.toString().replace(/\d+/, function (n) {
|
||||||
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
|
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,20 +14,83 @@
|
|||||||
}}</a-select-option>
|
}}</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-button
|
<a-button
|
||||||
@click="downLoadExcel"
|
@click="openModal"
|
||||||
:disabled="!selectNum"
|
:disabled="!selectNum"
|
||||||
type="primary"
|
type="primary"
|
||||||
class="ops-button-primary"
|
class="ops-button-primary"
|
||||||
icon="download"
|
icon="download"
|
||||||
>下载模板</a-button
|
>下载模板</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>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { downloadExcel } from '../../../utils/helper'
|
||||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import { writeExcel } from '@/modules/cmdb/api/batch'
|
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CiTypeChoice',
|
name: 'CiTypeChoice',
|
||||||
@@ -37,6 +100,13 @@ export default {
|
|||||||
ciTypeName: '',
|
ciTypeName: '',
|
||||||
selectNum: 0,
|
selectNum: 0,
|
||||||
selectCiTypeAttrList: [],
|
selectCiTypeAttrList: [],
|
||||||
|
visible: false,
|
||||||
|
checkedAttrs: [],
|
||||||
|
indeterminate: false,
|
||||||
|
checkAll: true,
|
||||||
|
parentsType: [],
|
||||||
|
parentsForm: {},
|
||||||
|
checkedParents: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function() {
|
created: function() {
|
||||||
@@ -44,6 +114,18 @@ export default {
|
|||||||
this.ciTypeList = res.ci_types
|
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: {
|
methods: {
|
||||||
selectCiType(el) {
|
selectCiType(el) {
|
||||||
// 当选择好模板类型时的回调函数
|
// 当选择好模板类型时的回调函数
|
||||||
@@ -60,24 +142,70 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
downLoadExcel() {
|
openModal() {
|
||||||
const columns = []
|
getCITypeParent(this.selectNum).then((res) => {
|
||||||
this.selectCiTypeAttrList.attributes.forEach((item) => {
|
this.parentsType = res.parents
|
||||||
columns.push(item.alias)
|
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)
|
||||||
})
|
})
|
||||||
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) {
|
filterOption(input, option) {
|
||||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
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>
|
</script>
|
||||||
@@ -105,3 +233,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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,15 +40,25 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
columns() {
|
columns() {
|
||||||
|
const _columns = []
|
||||||
if (this.ciTypeAttrs.attributes) {
|
if (this.ciTypeAttrs.attributes) {
|
||||||
return this.ciTypeAttrs.attributes.map((item) => {
|
_columns.push(
|
||||||
return {
|
...this.ciTypeAttrs.attributes.map((item) => {
|
||||||
title: item.alias || item.name,
|
return {
|
||||||
field: item.alias || item.name,
|
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 []
|
return _columns
|
||||||
},
|
},
|
||||||
dataSource() {
|
dataSource() {
|
||||||
return _.cloneDeep(this.uploadData)
|
return _.cloneDeep(this.uploadData)
|
||||||
|
@@ -4,13 +4,13 @@
|
|||||||
ref="upload"
|
ref="upload"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:customRequest="customRequest"
|
:customRequest="customRequest"
|
||||||
accept=".xls"
|
accept=".xls,.xlsx"
|
||||||
:showUploadList="false"
|
:showUploadList="false"
|
||||||
:fileList="fileList"
|
:fileList="fileList"
|
||||||
>
|
>
|
||||||
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
|
||||||
<p class="ant-upload-text">点击或拖拽文件至此上传!</p>
|
<p class="ant-upload-text">点击或拖拽文件至此上传!</p>
|
||||||
<p class="ant-upload-hint">支持文件类型:xls</p>
|
<p class="ant-upload-hint">支持文件类型:xls,xlsx</p>
|
||||||
</a-upload-dragger>
|
</a-upload-dragger>
|
||||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
<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>
|
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user