mirror of
https://github.com/veops/cmdb.git
synced 2025-09-06 21:37:00 +08:00
Compare commits
126 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
ed5936250f | ||
|
52c32e2ab1 | ||
|
d3224625b6 | ||
|
f158c7e33a | ||
|
6dc12bb6ac | ||
|
b33ae16c00 | ||
|
2caffc2670 | ||
|
f28af51007 | ||
|
3a0369559f | ||
|
a74a2c5a94 | ||
|
9fbcb2838e | ||
|
60a445b972 | ||
|
bfdd7b6a0e | ||
|
ab093d2493 | ||
|
315a578a31 | ||
|
1e16dc5e5b | ||
|
f67e196acf | ||
|
439e25d5dd | ||
|
ea59c0d71f | ||
|
1137127aab | ||
|
4ad1b5282e | ||
|
cdd5e4d9aa | ||
|
432de5e847 | ||
|
3a2339765a | ||
|
b5a2af7420 | ||
|
8b267613d6 | ||
|
b365eb27f6 | ||
|
2125f020b5 | ||
|
ea762e35a0 | ||
|
f11aadf6d4 | ||
|
9cbf133b9f | ||
|
95e8f9de74 | ||
|
26792147ae | ||
|
4f9b581c2e | ||
|
e2b1cb3003 | ||
|
f75a85b48a | ||
|
313fc80e54 | ||
|
e0666689e5 | ||
|
7a9fd4f9d6 | ||
|
2fd706be85 | ||
|
3df51bb670 | ||
|
9bbbcbe6dc | ||
|
16d6b40e8d | ||
|
ef2d3812a2 | ||
|
bc653efd04 | ||
|
d891d7365d | ||
|
9953b2fc98 | ||
|
8de54812dc | ||
|
eb7d52cf35 | ||
|
6c4a5f2f6b | ||
|
17c5d4538b | ||
|
6c3e3f9eed | ||
|
b0494adc17 | ||
|
fc133f2ae9 | ||
|
ac6e3a0318 | ||
|
404ec976cc | ||
|
4211bbcbc9 | ||
|
0158636671 | ||
|
d986bc3bbc | ||
|
044b820548 | ||
|
536daa6d4f | ||
|
b0620b043b | ||
|
a88c9cf7f7 | ||
|
be50f505d1 | ||
|
0bb4f633d6 | ||
|
78b521f3af | ||
|
77bc850d4a | ||
|
e52f201ba1 | ||
|
64aea424dc | ||
|
0655b0e9eb | ||
|
cce0649299 | ||
|
52574c64cc |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.vue linguist-language=python
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,6 +39,7 @@ pip-log.txt
|
||||
nosetests.xml
|
||||
.pytest_cache
|
||||
cmdb-api/test-output
|
||||
cmdb-api/api/uploaded_files
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
49
Makefile
49
Makefile
@@ -1,37 +1,52 @@
|
||||
.PHONY: env clean api ui worker
|
||||
MYSQL_ROOT_PASSWORD ?= root
|
||||
MYSQL_PORT ?= 3306
|
||||
REDIS_PORT ?= 6379
|
||||
|
||||
help:
|
||||
@echo " env create a development environment using pipenv"
|
||||
@echo " deps install dependencies using pip"
|
||||
@echo " clean remove unwanted files like .pyc's"
|
||||
@echo " lint check style with flake8"
|
||||
@echo " api start api server"
|
||||
@echo " ui start ui server"
|
||||
@echo " worker start async tasks worker"
|
||||
default: help
|
||||
help: ## display this help
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
.PHONY: help
|
||||
|
||||
env:
|
||||
env: ## create a development environment using pipenv
|
||||
sudo easy_install pip && \
|
||||
pip install pipenv -i https://pypi.douban.com/simple && \
|
||||
npm install yarn && \
|
||||
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 run flask db-setup && \
|
||||
pipenv run flask cmdb-init-cache && \
|
||||
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
|
||||
.PHONY: api
|
||||
|
||||
worker:
|
||||
cd cmdb-api && pipenv run celery worker -A celery_worker.celery -E -Q one_cmdb_async --concurrency=1 -D && pipenv run celery worker -A celery_worker.celery -E -Q acl_async --concurrency=1 -D
|
||||
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
|
||||
.PHONY: worker
|
||||
|
||||
ui:
|
||||
ui: ## start ui server
|
||||
cd cmdb-ui && yarn run serve
|
||||
.PHONY: ui
|
||||
|
||||
clean:
|
||||
clean: ## remove unwanted files like .pyc's
|
||||
pipenv run flask clean
|
||||
.PHONY: clean
|
||||
|
||||
lint:
|
||||
lint: ## check style with flake8
|
||||
flake8 --exclude=env .
|
||||
.PHONY: lint
|
||||
|
16
README.md
16
README.md
@@ -1,13 +1,13 @@
|
||||

|
||||

|
||||
|
||||
[](https://github.com/veops/cmdb/blob/master/LICENSE)
|
||||
[](https://github.com/sendya/ant-design-pro-vue)
|
||||
[](https://github.com/pallets/flask)
|
||||
|
||||
[English](README_en.md) / [中文](README.md)
|
||||
|
||||
[English](docs/README_en.md) / [中文](README.md)
|
||||
- 产品文档:https://veops.cn/docs/
|
||||
- 在线体验: <a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
|
||||
- username: demo
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
||||
> **重要提示**: `master` 分支在开发过程中可能处于 _不稳定的状态_ 。
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
- 服务树
|
||||
|
||||

|
||||

|
||||
|
||||
[查看更多展示](docs/screenshot.md)
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
## 接入公司
|
||||
|
||||
> 欢迎使用CMDB的公司,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
> 欢迎使用开源CMDB的公司,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -83,6 +83,6 @@ docker-compose up -d
|
||||
|
||||
---
|
||||
|
||||
_**欢迎关注我们的公众号,点击联系我们,加入微信、qq运维群(336164978),获得更多产品、行业相关资讯**_
|
||||
_**欢迎关注我们的公众号,点击联系我们,加入微信、QQ群(336164978),获得更多产品、行业相关资讯**_
|
||||
|
||||

|
||||

|
||||
|
@@ -1,16 +0,0 @@
|
||||
default: help
|
||||
|
||||
test: ## test in local environment
|
||||
pytest -s --html=test-output/test/index.html --cov-report html:test-output/coverage --cov=api tests
|
||||
|
||||
clean_test: ## clean test output
|
||||
rm -f .coverage
|
||||
rm -rf .pytest_cache
|
||||
rm -rf test-output
|
||||
|
||||
|
||||
docker_test: ## test all case in docker container
|
||||
@echo "TODO"
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' ./Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@@ -5,26 +5,26 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
# Flask
|
||||
Flask = "==1.0.3"
|
||||
Werkzeug = "==0.15.5"
|
||||
Flask = "==2.3.2"
|
||||
Werkzeug = "==2.3.6"
|
||||
click = ">=5.0"
|
||||
# Api
|
||||
Flask-RESTful = "==0.3.7"
|
||||
Flask-RESTful = "==0.3.10"
|
||||
# Database
|
||||
Flask-SQLAlchemy = "==2.4.0"
|
||||
SQLAlchemy = "==1.3.5"
|
||||
PyMySQL = "==0.9.3"
|
||||
redis = "==3.2.1"
|
||||
Flask-SQLAlchemy = "==2.5.0"
|
||||
SQLAlchemy = "==1.4.49"
|
||||
PyMySQL = "==1.1.0"
|
||||
redis = "==4.6.0"
|
||||
# Migrations
|
||||
Flask-Migrate = "==2.5.2"
|
||||
# Deployment
|
||||
gunicorn = "==19.5.0"
|
||||
gunicorn = "==21.0.1"
|
||||
supervisor = "==4.0.3"
|
||||
# Auth
|
||||
Flask-Login = "==0.4.1"
|
||||
Flask-Bcrypt = "==0.7.1"
|
||||
Flask-Login = "==0.6.2"
|
||||
Flask-Bcrypt = "==1.0.1"
|
||||
Flask-Cors = ">=3.0.8"
|
||||
python-ldap = "==3.2.0"
|
||||
python-ldap = "==3.4.0"
|
||||
pycryptodome = "==3.12.0"
|
||||
# Caching
|
||||
Flask-Caching = ">=1.0.0"
|
||||
@@ -32,20 +32,17 @@ Flask-Caching = ">=1.0.0"
|
||||
environs = "==4.2.0"
|
||||
marshmallow = "==2.20.2"
|
||||
# async tasks
|
||||
celery = "==4.3.0"
|
||||
celery = "==5.3.1"
|
||||
celery_once = "==3.0.1"
|
||||
more-itertools = "==5.0.0"
|
||||
kombu = "==4.4.0"
|
||||
kombu = "==5.3.1"
|
||||
# common setting
|
||||
Flask-APScheduler = "==1.12.4"
|
||||
timeout-decorator = "==0.5.0"
|
||||
numpy = "==1.18.5"
|
||||
pandas = "==1.3.2"
|
||||
WTForms = "==3.0.0"
|
||||
email-validator = "==1.3.1"
|
||||
treelib = "==1.6.1"
|
||||
flasgger = "==0.9.5"
|
||||
Pillow = "==8.3.2"
|
||||
Pillow = "==9.3.0"
|
||||
# other
|
||||
six = "==1.12.0"
|
||||
bs4 = ">=0.0.1"
|
||||
@@ -53,9 +50,9 @@ toposort = ">=1.5"
|
||||
requests = ">=2.22.0"
|
||||
PyJWT = "==2.4.0"
|
||||
elasticsearch = "==7.17.9"
|
||||
future = "==0.18.2"
|
||||
itsdangerous = "==2.0.1"
|
||||
Jinja2 = "==3.0.1"
|
||||
future = "==0.18.3"
|
||||
itsdangerous = "==2.1.2"
|
||||
Jinja2 = "==3.1.2"
|
||||
jinja2schema = "==0.1.4"
|
||||
msgpack-python = "==0.5.6"
|
||||
alembic = "==1.7.7"
|
||||
|
@@ -9,29 +9,19 @@ from inspect import getmembers
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
from flask import Flask
|
||||
from flask import make_response, jsonify
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.cli import click
|
||||
from flask.json import JSONEncoder
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
|
||||
import api.views.entry
|
||||
from api.extensions import (
|
||||
bcrypt,
|
||||
cors,
|
||||
cache,
|
||||
db,
|
||||
login_manager,
|
||||
migrate,
|
||||
celery,
|
||||
rd,
|
||||
es,
|
||||
)
|
||||
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
|
||||
from api.flask_cas import CAS
|
||||
from api.models.acl import User
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
PROJECT_ROOT = os.path.join(HERE, os.pardir)
|
||||
API_PACKAGE = "api"
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
@@ -75,7 +65,7 @@ class ReverseProxy(object):
|
||||
return self.app(environ, start_response)
|
||||
|
||||
|
||||
class MyJSONEncoder(JSONEncoder):
|
||||
class MyJSONEncoder(DefaultJSONProvider):
|
||||
def default(self, o):
|
||||
if isinstance(o, (decimal.Decimal, datetime.date, datetime.time)):
|
||||
return str(o)
|
||||
@@ -103,7 +93,7 @@ def create_app(config_object="settings"):
|
||||
app = Flask(__name__.split(".")[0])
|
||||
|
||||
app.config.from_object(config_object)
|
||||
app.json_encoder = MyJSONEncoder
|
||||
app.json = MyJSONEncoder(app)
|
||||
configure_logger(app)
|
||||
register_extensions(app)
|
||||
register_blueprints(app)
|
||||
@@ -139,6 +129,8 @@ def register_extensions(app):
|
||||
rd.init_app(app)
|
||||
if app.config.get('USE_ES'):
|
||||
es.init_app(app)
|
||||
|
||||
app.config.update(app.config.get("CELERY"))
|
||||
celery.conf.update(app.config)
|
||||
|
||||
|
||||
@@ -158,10 +150,8 @@ def register_error_handlers(app):
|
||||
error_code = getattr(error, "code", 500)
|
||||
if not str(error_code).isdigit():
|
||||
error_code = 400
|
||||
if error_code != 500:
|
||||
return make_response(jsonify(message=str(error)), error_code)
|
||||
else:
|
||||
return make_response(jsonify(message=traceback.format_exc(-1)), error_code)
|
||||
|
||||
return make_response(jsonify(message=str(error)), error_code)
|
||||
|
||||
for errcode in app.config.get("ERROR_CODES") or [400, 401, 403, 404, 405, 500, 502]:
|
||||
app.errorhandler(errcode)(render_error)
|
||||
@@ -184,9 +174,8 @@ def register_commands(app):
|
||||
for root, _, files in os.walk(os.path.join(HERE, "commands")):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("commands"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
if root not in sys.path:
|
||||
sys.path.insert(1, root)
|
||||
command = __import__(os.path.splitext(filename)[0])
|
||||
func_list = [o[0] for o in getmembers(command) if isinstance(o[1], click.core.Command)]
|
||||
for func_name in func_list:
|
||||
|
@@ -5,6 +5,9 @@ from flask.cli import with_appcontext
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def init_acl():
|
||||
"""
|
||||
acl init
|
||||
"""
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import App
|
||||
from api.tasks.acl import role_rebuild
|
||||
|
@@ -13,6 +13,7 @@ from flask.cli import with_appcontext
|
||||
import api.lib.cmdb.ci
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
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 REDIS_PREFIX_CI
|
||||
@@ -29,6 +30,7 @@ from api.lib.perm.acl.role import RoleCRUD
|
||||
from api.lib.perm.acl.user import UserCRUD
|
||||
from api.models.acl import App
|
||||
from api.models.acl import ResourceType
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CIType
|
||||
@@ -200,6 +202,9 @@ def del_user(user):
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def cmdb_counter():
|
||||
"""
|
||||
Dashboard calculations
|
||||
"""
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
while True:
|
||||
@@ -217,6 +222,9 @@ def cmdb_counter():
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def cmdb_trigger():
|
||||
"""
|
||||
Trigger execution
|
||||
"""
|
||||
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
|
||||
trigger2cis = dict()
|
||||
trigger2completed = dict()
|
||||
@@ -259,3 +267,34 @@ def cmdb_trigger():
|
||||
|
||||
i += 1
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def cmdb_index_table_upgrade():
|
||||
"""
|
||||
Migrate data from tables c_value_integers, c_value_floats, and c_value_datetime
|
||||
"""
|
||||
for attr in Attribute.get_by(to_dict=False):
|
||||
if attr.value_type not in {ValueTypeEnum.TEXT, ValueTypeEnum.JSON} and not attr.is_index:
|
||||
attr.update(is_index=True)
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
from api.models.cmdb import CIValueInteger, CIIndexValueInteger
|
||||
from api.models.cmdb import CIValueFloat, CIIndexValueFloat
|
||||
from api.models.cmdb import CIValueDateTime, CIIndexValueDateTime
|
||||
|
||||
for i in CIValueInteger.get_by(to_dict=False):
|
||||
CIIndexValueInteger.create(ci_id=i.ci_id, attr_id=i.attr_id, value=i.value, commit=False)
|
||||
i.delete(commit=False)
|
||||
db.session.commit()
|
||||
|
||||
for i in CIValueFloat.get_by(to_dict=False):
|
||||
CIIndexValueFloat.create(ci_id=i.ci_id, attr_id=i.attr_id, value=i.value, commit=False)
|
||||
i.delete(commit=False)
|
||||
db.session.commit()
|
||||
|
||||
for i in CIValueDateTime.get_by(to_dict=False):
|
||||
CIIndexValueDateTime.create(ci_id=i.ci_id, attr_id=i.attr_id, value=i.value, commit=False)
|
||||
i.delete(commit=False)
|
||||
db.session.commit()
|
||||
|
@@ -19,16 +19,27 @@ class InitEmployee(object):
|
||||
|
||||
def import_user_from_acl(self):
|
||||
"""
|
||||
从ACL导入用户
|
||||
Import users from ACL
|
||||
"""
|
||||
|
||||
InitDepartment().init()
|
||||
acl = ACLManager('acl')
|
||||
user_list = acl.get_all_users()
|
||||
|
||||
username_list = [e['username'] for e in Employee.get_by()]
|
||||
|
||||
for user in user_list:
|
||||
acl_uid = user['uid']
|
||||
block = 1 if user['block'] else 0
|
||||
acl_rid = self.get_rid_by_uid(acl_uid)
|
||||
if user['username'] in username_list:
|
||||
existed = Employee.get_by(first=True, username=user['username'], to_dict=False)
|
||||
if existed:
|
||||
existed.update(
|
||||
acl_uid=acl_uid,
|
||||
acl_rid=acl_rid,
|
||||
block=block,
|
||||
)
|
||||
continue
|
||||
try:
|
||||
form = EmployeeAddForm(MultiDict(user))
|
||||
@@ -36,8 +47,9 @@ class InitEmployee(object):
|
||||
raise Exception(
|
||||
','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
|
||||
data = form.data
|
||||
data['acl_uid'] = user['uid']
|
||||
data['block'] = 1 if user['block'] else 0
|
||||
data['acl_uid'] = acl_uid
|
||||
data['acl_rid'] = acl_rid
|
||||
data['block'] = block
|
||||
data.pop('password')
|
||||
Employee.create(
|
||||
**data
|
||||
@@ -46,6 +58,11 @@ class InitEmployee(object):
|
||||
self.log.error(ErrFormat.acl_import_user_failed.format(user['username'], str(e)))
|
||||
self.log.error(e)
|
||||
|
||||
def get_rid_by_uid(self, uid):
|
||||
from api.models.acl import Role
|
||||
role = Role.get_by(first=True, uid=uid)
|
||||
return role['id'] if role is not None else 0
|
||||
|
||||
|
||||
class InitDepartment(object):
|
||||
def __init__(self):
|
||||
@@ -149,7 +166,7 @@ class InitDepartment(object):
|
||||
@with_appcontext
|
||||
def init_import_user_from_acl():
|
||||
"""
|
||||
从ACL导入用户
|
||||
Import users from ACL
|
||||
"""
|
||||
InitEmployee().import_user_from_acl()
|
||||
|
||||
@@ -158,7 +175,7 @@ def init_import_user_from_acl():
|
||||
@with_appcontext
|
||||
def init_department():
|
||||
"""
|
||||
初始化 部门
|
||||
Department initialization
|
||||
"""
|
||||
InitDepartment().init()
|
||||
InitDepartment().create_acl_role_with_department()
|
||||
|
@@ -3,13 +3,19 @@
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BUILTIN_KEYWORDS
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum
|
||||
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.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
@@ -39,7 +45,7 @@ class AttributeManager(object):
|
||||
ret_key = choice_web_hook.get('ret_key')
|
||||
headers = choice_web_hook.get('headers') or {}
|
||||
payload = choice_web_hook.get('payload') or {}
|
||||
method = choice_web_hook.get('method', 'GET').lower()
|
||||
method = (choice_web_hook.get('method') or 'GET').lower()
|
||||
|
||||
try:
|
||||
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
||||
@@ -54,15 +60,17 @@ class AttributeManager(object):
|
||||
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
current_app.logger.error("get choice values failed: {}".format(e))
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
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:
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
elif choice_web_hook and not choice_web_hook_parse:
|
||||
return []
|
||||
if choice_web_hook:
|
||||
if choice_web_hook_parse:
|
||||
if isinstance(choice_web_hook, dict):
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
else:
|
||||
return []
|
||||
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||
@@ -72,34 +80,34 @@ class AttributeManager(object):
|
||||
@staticmethod
|
||||
def add_choice_values(_id, value_type, choice_values):
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
if choice_table is None:
|
||||
return
|
||||
|
||||
choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
||||
db.session.flush()
|
||||
choice_values = choice_values
|
||||
for v, option in choice_values:
|
||||
table = choice_table(attr_id=_id, value=v, option=option)
|
||||
|
||||
db.session.add(table)
|
||||
choice_table.create(attr_id=_id, value=v, option=option, commit=False)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def _del_choice_values(_id, 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()
|
||||
|
||||
@classmethod
|
||||
def search_attributes(cls, name=None, alias=None, page=1, page_size=None):
|
||||
"""
|
||||
:param name:
|
||||
:param alias:
|
||||
:param page:
|
||||
:param page_size:
|
||||
:param name:
|
||||
:param alias:
|
||||
:param page:
|
||||
:param page_size:
|
||||
:return: attribute, if name is None, then return all attributes
|
||||
"""
|
||||
if name is not None:
|
||||
@@ -113,8 +121,8 @@ class AttributeManager(object):
|
||||
attrs = attrs[(page - 1) * page_size:][:page_size]
|
||||
res = list()
|
||||
for attr in attrs:
|
||||
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
attr["is_choice"] and attr.update(
|
||||
dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
attr['is_choice'] and attr.pop('choice_web_hook', None)
|
||||
|
||||
res.append(attr)
|
||||
@@ -123,30 +131,31 @@ class AttributeManager(object):
|
||||
|
||||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key, choice_web_hook_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])), choice_web_hook_parse=choice_web_hook_parse)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"], choice_web_hook_parse=choice_web_hook_parse)
|
||||
|
||||
return attr
|
||||
|
||||
@staticmethod
|
||||
@@ -154,6 +163,17 @@ class AttributeManager(object):
|
||||
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
|
||||
|
||||
@staticmethod
|
||||
def calc_computed_attribute(attr_id):
|
||||
"""
|
||||
calculate computed attribute for all ci
|
||||
:param attr_id:
|
||||
:return:
|
||||
"""
|
||||
from api.tasks.cmdb import calc_computed_attribute
|
||||
|
||||
calc_computed_attribute.apply_async(args=(attr_id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
@@ -162,8 +182,9 @@ class AttributeManager(object):
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
|
||||
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)
|
||||
|
||||
alias = kwargs.pop("alias", "")
|
||||
alias = name if not alias else alias
|
||||
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
||||
@@ -177,7 +198,7 @@ class AttributeManager(object):
|
||||
name=name,
|
||||
alias=alias,
|
||||
is_choice=is_choice,
|
||||
uid=g.user.uid,
|
||||
uid=current_user.uid,
|
||||
**kwargs)
|
||||
|
||||
if choice_value:
|
||||
@@ -211,6 +232,11 @@ class AttributeManager(object):
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
def _clean_ci_type_attributes_cache(attr_id):
|
||||
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
|
||||
CITypeAttributesCache.clean(i.type_id)
|
||||
|
||||
@staticmethod
|
||||
def _change_index(attr, old, new):
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
@@ -221,11 +247,11 @@ class AttributeManager(object):
|
||||
new_table = TableMap(attr=attr, is_index=new).table
|
||||
|
||||
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)
|
||||
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:
|
||||
db.session.commit()
|
||||
@@ -240,7 +266,7 @@ class AttributeManager(object):
|
||||
def _can_edit_attribute(attr):
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
|
||||
if attr.uid == g.user.uid:
|
||||
if attr.uid == current_user.uid:
|
||||
return True
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=attr.id, to_dict=False):
|
||||
@@ -290,7 +316,7 @@ class AttributeManager(object):
|
||||
|
||||
if is_choice and 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)
|
||||
|
||||
try:
|
||||
@@ -309,6 +335,8 @@ class AttributeManager(object):
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
self._clean_ci_type_attributes_cache(_id)
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
@@ -319,25 +347,28 @@ class AttributeManager(object):
|
||||
if CIType.get_by(unique_id=attr.id, first=True, to_dict=False) is not None:
|
||||
return abort(400, ErrFormat.attribute_is_unique_id)
|
||||
|
||||
if attr.uid and attr.uid != g.user.uid:
|
||||
ref = CITypeAttribute.get_by(attr_id=_id, to_dict=False, first=True)
|
||||
if ref is not None:
|
||||
ci_type = CITypeCache.get(ref.type_id)
|
||||
return abort(400, ErrFormat.attribute_is_ref_by_type.format(ci_type and ci_type.alias or ref.type_id))
|
||||
|
||||
if attr.uid != current_user.uid and not is_app_admin('cmdb'):
|
||||
return abort(403, ErrFormat.cannot_delete_attribute)
|
||||
|
||||
if attr.is_choice:
|
||||
choice_table = ValueTypeMap.choice.get(attr.value_type)
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict
|
||||
db.session.flush()
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
choice_table.get_by(attr_id=_id, only_query=True).delete()
|
||||
|
||||
attr.soft_delete()
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=_id, to_dict=False):
|
||||
i.soft_delete()
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
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):
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return name
|
||||
|
@@ -5,7 +5,7 @@ import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
@@ -156,7 +156,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
continue
|
||||
|
||||
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'):
|
||||
if not (g.user.username == "cmdb_agent" or g.user.uid == rule['uid']):
|
||||
if not (current_user.username == "cmdb_agent" or current_user.uid == rule['uid']):
|
||||
rule['extra_option'].pop('secret', None)
|
||||
else:
|
||||
rule['extra_option']['secret'] = AESCrypto.decrypt(rule['extra_option']['secret'])
|
||||
@@ -213,7 +213,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
agent_id = agent_id.strip()
|
||||
q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}"
|
||||
|
||||
s = search(q.format(g.user.username, agent_id.strip()))
|
||||
s = search(q.format(current_user.username, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
@@ -222,7 +222,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
s = search(q.format(g.user.nickname, agent_id.strip()))
|
||||
s = search(q.format(current_user.nickname, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
@@ -240,9 +240,10 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
for i in response:
|
||||
if g.user.username not in (i.get('rd_duty') or []) and g.user.username not in \
|
||||
(i.get('op_duty') or []) and g.user.nickname not in (i.get('rd_duty') or []) and \
|
||||
g.user.nickname not in (i.get('op_duty') or []):
|
||||
if (current_user.username not in (i.get('rd_duty') or []) and
|
||||
current_user.username not in (i.get('op_duty') or []) and
|
||||
current_user.nickname not in (i.get('rd_duty') or []) and
|
||||
current_user.nickname not in (i.get('op_duty') or [])):
|
||||
return abort(403, ErrFormat.adt_target_expr_no_permission.format(
|
||||
i.get("{}_name".format(i.get('ci_type')))))
|
||||
except SearchError as e:
|
||||
@@ -270,7 +271,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
|
||||
|
||||
kwargs['uid'] = g.user.uid
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
return kwargs
|
||||
|
||||
@@ -281,7 +282,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
if g.user.uid != existed.uid:
|
||||
if current_user.uid != existed.uid:
|
||||
return abort(403, ErrFormat.adt_secret_no_permission)
|
||||
|
||||
return existed
|
||||
@@ -453,10 +454,12 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
|
||||
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
|
||||
for r_adt in relation_adts:
|
||||
if r_adt.relation and ci_id is not None:
|
||||
ad_key, cmdb_key = None, {}
|
||||
for ad_key in r_adt.relation:
|
||||
cmdb_key = r_adt.relation[ad_key]
|
||||
if not r_adt.relation or ci_id is None:
|
||||
continue
|
||||
for ad_key in r_adt.relation:
|
||||
if not adc.instance.get(ad_key):
|
||||
continue
|
||||
cmdb_key = r_adt.relation[ad_key]
|
||||
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'),
|
||||
adc.instance.get(ad_key))
|
||||
s = search(query)
|
||||
@@ -476,7 +479,10 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||
except:
|
||||
pass
|
||||
|
||||
adc.update(is_accept=True, accept_by=nickname or g.user.nickname, accept_time=datetime.datetime.now())
|
||||
adc.update(is_accept=True,
|
||||
accept_by=nickname or current_user.nickname,
|
||||
accept_time=datetime.datetime.now(),
|
||||
ci_id=ci_id)
|
||||
|
||||
|
||||
class AutoDiscoveryHTTPManager(object):
|
||||
|
@@ -34,6 +34,7 @@ class AttributeCache(object):
|
||||
attr = attr or Attribute.get_by(alias=key, first=True, to_dict=False)
|
||||
if attr is not None:
|
||||
cls.set(attr)
|
||||
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
@@ -67,6 +68,7 @@ class CITypeCache(object):
|
||||
ct = ct or CIType.get_by(alias=key, first=True, to_dict=False)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
@@ -98,6 +100,7 @@ class RelationTypeCache(object):
|
||||
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
@@ -133,12 +136,15 @@ class CITypeAttributesCache(object):
|
||||
attrs = attrs or cache.get(cls.PREFIX_ID.format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
|
||||
if attrs is not None:
|
||||
cls.set(key, attrs)
|
||||
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
@@ -155,13 +161,16 @@ class CITypeAttributesCache(object):
|
||||
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
|
||||
if attrs is not None:
|
||||
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
|
||||
cls.set2(key, attrs)
|
||||
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
@@ -204,10 +213,11 @@ class CITypeAttributeCache(object):
|
||||
|
||||
attr = 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 = CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
||||
if attr is not None:
|
||||
cls.set(type_id, attr_id, attr)
|
||||
attr = attr or CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False)
|
||||
|
||||
if attr is not None:
|
||||
cls.set(type_id, attr_id, attr)
|
||||
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
|
@@ -7,7 +7,7 @@ import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
@@ -24,8 +24,8 @@ from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CIRelationHistoryManager
|
||||
@@ -40,15 +40,19 @@ from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.tasks.cmdb import ci_delete
|
||||
from api.tasks.cmdb import ci_relation_add
|
||||
from api.tasks.cmdb import ci_relation_cache
|
||||
from api.tasks.cmdb import ci_relation_delete
|
||||
|
||||
PRIVILEGED_USERS = {"worker", "cmdb_agent", "agent"}
|
||||
|
||||
|
||||
class CIManager(object):
|
||||
""" manage CI interface
|
||||
@@ -64,11 +68,13 @@ class CIManager(object):
|
||||
@staticmethod
|
||||
def get_type_name(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
return CITypeCache.get(ci.type_id).name
|
||||
|
||||
@staticmethod
|
||||
def get_type(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
return CITypeCache.get(ci.type_id)
|
||||
|
||||
@staticmethod
|
||||
@@ -90,9 +96,7 @@ class CIManager(object):
|
||||
|
||||
res = dict()
|
||||
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
@@ -159,14 +163,11 @@ class CIManager(object):
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
if valid:
|
||||
cls.valid_ci_only_read(ci)
|
||||
valid and cls.valid_ci_only_read(ci)
|
||||
|
||||
res = dict()
|
||||
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
@@ -245,7 +246,7 @@ class CIManager(object):
|
||||
for i in unique_constraints:
|
||||
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}
|
||||
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
|
||||
if not_existed_fields and ci_id is not None:
|
||||
@@ -290,7 +291,7 @@ class CIManager(object):
|
||||
_is_admin=False,
|
||||
**ci_dict):
|
||||
"""
|
||||
|
||||
add ci
|
||||
:param ci_type_name:
|
||||
:param exist_policy: replace or reject or need
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
@@ -305,9 +306,7 @@ class CIManager(object):
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||
|
||||
unique_value = ci_dict.get(unique_key.name)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.alias)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.id)
|
||||
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id)
|
||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
||||
@@ -316,7 +315,7 @@ class CIManager(object):
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
|
||||
ci = None
|
||||
need_lock = g.user.username not in ("worker", "cmdb_agent", "agent")
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci_type_name, need_lock=need_lock):
|
||||
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
||||
if existed is not None:
|
||||
@@ -330,10 +329,6 @@ class CIManager(object):
|
||||
if exist_policy == ExistPolicy.NEED:
|
||||
return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value)))
|
||||
|
||||
from api.lib.cmdb.const import L_CI
|
||||
if L_CI and len(CI.get_by(type_id=ci_type.id)) > L_CI * 2:
|
||||
return abort(400, ErrFormat.limit_ci.format(L_CI * 2))
|
||||
|
||||
limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {}
|
||||
|
||||
if existed is None: # set default
|
||||
@@ -364,13 +359,18 @@ class CIManager(object):
|
||||
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
ref_ci_dict = dict()
|
||||
for k in ci_dict:
|
||||
if k not in ci_type_attrs_name and k not in ci_type_attrs_alias and \
|
||||
_no_attribute_policy == ExistPolicy.REJECT:
|
||||
if k.startswith("$") and "." in k:
|
||||
ref_ci_dict[k] = ci_dict[k]
|
||||
continue
|
||||
|
||||
if k not in ci_type_attrs_name and (
|
||||
k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
|
||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and \
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs:
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs):
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
@@ -389,6 +389,9 @@ class CIManager(object):
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
if ref_ci_dict: # add relations
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
||||
@@ -411,7 +414,7 @@ class CIManager(object):
|
||||
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
need_lock = g.user.username not in ("worker", "cmdb_agent", "agent")
|
||||
need_lock = current_user.username not in current_app.config.get('PRIVILEGED_USERS', PRIVILEGED_USERS)
|
||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||
|
||||
@@ -431,6 +434,10 @@ class CIManager(object):
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
|
||||
if ref_ci_dict:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
@@ -453,17 +460,22 @@ class CIManager(object):
|
||||
for attr_name in attr_names:
|
||||
value_table = TableMap(attr_name=attr_name).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
item.delete(commit=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)
|
||||
item.delete()
|
||||
item.delete(commit=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)
|
||||
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)
|
||||
|
||||
@@ -478,11 +490,8 @@ class CIManager(object):
|
||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr=unique_key).table
|
||||
|
||||
v = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True) \
|
||||
or abort(404, ErrFormat.not_found)
|
||||
v = (value_table.get_by(attr_id=unique_key.id, value=unique_value, to_dict=False, first=True) or
|
||||
abort(404, ErrFormat.not_found))
|
||||
|
||||
ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id)))
|
||||
|
||||
@@ -528,6 +537,7 @@ class CIManager(object):
|
||||
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
||||
heartbeat_dict.get(i.get("_id"))) for i in res
|
||||
if i.get("private_ip")]
|
||||
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
@@ -647,6 +657,7 @@ class CIManager(object):
|
||||
return res
|
||||
|
||||
current_app.logger.warning("cache not hit...............")
|
||||
|
||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
|
||||
|
||||
|
||||
@@ -672,6 +683,7 @@ class CIRelationManager(object):
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
|
||||
res[ci_type.name] = children
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -743,17 +755,28 @@ class CIRelationManager(object):
|
||||
return ci_ids
|
||||
|
||||
@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:
|
||||
return
|
||||
|
||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
if type_relation.constraint == ConstraintEnum.One2One and (first_existed or second_existed):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1对1"))
|
||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
relation_type_id=type_relation.relation_type_id, to_dict=False)
|
||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id,
|
||||
relation_type_id=type_relation.relation_type_id, to_dict=False)
|
||||
if type_relation.constraint == ConstraintEnum.One2One:
|
||||
for i in first_existed:
|
||||
if i.second_ci.type_id == second_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
||||
|
||||
if type_relation.constraint == ConstraintEnum.One2Many and second_existed:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1对多"))
|
||||
for i in second_existed:
|
||||
if i.first_ci.type_id == first_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-1"))
|
||||
|
||||
if type_relation.constraint == ConstraintEnum.One2Many:
|
||||
for i in second_existed:
|
||||
if i.first_ci.type_id == first_type_id:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1-N"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
@@ -792,15 +815,17 @@ class CIRelationManager(object):
|
||||
else:
|
||||
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,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||
|
||||
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:
|
||||
existed.upadte(more=more)
|
||||
@@ -848,12 +873,12 @@ class CIRelationManager(object):
|
||||
:param children:
|
||||
:return:
|
||||
"""
|
||||
if parents is not None and isinstance(parents, list):
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
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 ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
@@ -867,7 +892,7 @@ class CIRelationManager(object):
|
||||
:return:
|
||||
"""
|
||||
|
||||
if parents is not None and isinstance(parents, list):
|
||||
if isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id)
|
||||
|
@@ -5,7 +5,7 @@ import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -16,7 +16,9 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum, 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.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
@@ -27,7 +29,10 @@ from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIFilterPerms
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeAttributeGroup
|
||||
@@ -37,6 +42,9 @@ from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import RelationType
|
||||
@@ -54,6 +62,7 @@ class CITypeManager(object):
|
||||
@staticmethod
|
||||
def get_name_by_id(type_id):
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
|
||||
return ci_type and ci_type.name
|
||||
|
||||
@staticmethod
|
||||
@@ -65,7 +74,7 @@ class CITypeManager(object):
|
||||
@staticmethod
|
||||
def get_ci_types(type_name=None):
|
||||
resources = None
|
||||
if current_app.config.get('USE_ACL') and not is_app_admin():
|
||||
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")])
|
||||
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
@@ -104,9 +113,6 @@ class CITypeManager(object):
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
from api.lib.cmdb.const import L_TYPE
|
||||
if L_TYPE and len(CIType.get_by()) > L_TYPE * 2:
|
||||
return abort(400, ErrFormat.limit_ci_type.format(L_TYPE * 2))
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
|
||||
@@ -117,7 +123,7 @@ class CITypeManager(object):
|
||||
cls._validate_unique(alias=kwargs['alias'])
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
kwargs['uid'] = g.user.uid
|
||||
kwargs['uid'] = current_user.uid
|
||||
ci_type = CIType.create(**kwargs)
|
||||
|
||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||
@@ -131,7 +137,7 @@ class CITypeManager(object):
|
||||
ResourceTypeEnum.CI,
|
||||
permissions=[PermEnum.READ])
|
||||
ACLManager().grant_resource_to_role(ci_type.name,
|
||||
g.user.username,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD, ci_type.id, change=ci_type.to_dict())
|
||||
@@ -178,32 +184,41 @@ class CITypeManager(object):
|
||||
def set_enabled(cls, type_id, enabled=True):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
ci_type.update(enabled=enabled)
|
||||
|
||||
return type_id
|
||||
|
||||
@classmethod
|
||||
def delete(cls, type_id):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
|
||||
if ci_type.uid and ci_type.uid != g.user.uid:
|
||||
if ci_type.uid and ci_type.uid != current_user.uid:
|
||||
return abort(403, ErrFormat.only_owner_can_delete)
|
||||
|
||||
if CI.get_by(type_id=type_id, first=True, to_dict=False) is not None:
|
||||
return abort(400, ErrFormat.ci_exists_and_cannot_delete_type)
|
||||
|
||||
relation_views = PreferenceRelationView.get_by(to_dict=False)
|
||||
for rv in relation_views:
|
||||
for item in (rv.cr_ids or []):
|
||||
if item.get('parent_id') == type_id or item.get('child_id') == type_id:
|
||||
return abort(400, ErrFormat.ci_relation_view_exists_and_cannot_delete_type.format(rv.name))
|
||||
|
||||
for item in CITypeRelation.get_by(parent_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
item.soft_delete(commit=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):
|
||||
item.soft_delete()
|
||||
for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard,
|
||||
CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger,
|
||||
AutoDiscoveryCIType, CIFilterPerms]:
|
||||
for item in table.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete(commit=False)
|
||||
|
||||
for item in PreferenceShowAttributes.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
|
||||
item.delete(commit=False)
|
||||
|
||||
for item in CITypeGroupItem.get_by(type_id=type_id, to_dict=False):
|
||||
item.soft_delete()
|
||||
db.session.commit()
|
||||
|
||||
ci_type.soft_delete()
|
||||
|
||||
@@ -254,6 +269,7 @@ class CITypeGroupManager(object):
|
||||
@staticmethod
|
||||
def add(name):
|
||||
CITypeGroup.get_by(name=name, first=True) and abort(400, ErrFormat.ci_type_group_exists.format(name))
|
||||
|
||||
return CITypeGroup.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
@@ -320,6 +336,17 @@ class CITypeAttributeManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attr_name(ci_type_name, key):
|
||||
ci_type = CITypeCache.get(ci_type_name)
|
||||
if ci_type is None:
|
||||
return
|
||||
|
||||
for i in CITypeAttributesCache.get(ci_type.id):
|
||||
attr = AttributeCache.get(i.attr_id)
|
||||
if attr and (attr.name == key or attr.alias == key):
|
||||
return attr.name
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
@@ -340,6 +367,7 @@ class CITypeAttributeManager(object):
|
||||
attr_dict.pop('choice_web_hook', None)
|
||||
|
||||
result.append(attr_dict)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@@ -527,6 +555,7 @@ class CITypeRelationManager(object):
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
ci_type_dict["constraint"] = relation_inst.constraint
|
||||
|
||||
return ci_type_dict
|
||||
|
||||
@classmethod
|
||||
@@ -575,7 +604,7 @@ class CITypeRelationManager(object):
|
||||
ResourceTypeEnum.CI_TYPE_RELATION,
|
||||
permissions=[PermEnum.READ])
|
||||
ACLManager().grant_resource_to_role(resource_name,
|
||||
g.user.username,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION)
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
|
||||
@@ -585,8 +614,8 @@ class CITypeRelationManager(object):
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
ctr = CITypeRelation.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id)))
|
||||
ctr = (CITypeRelation.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_relation_not_found.format("id={}".format(_id))))
|
||||
ctr.soft_delete()
|
||||
|
||||
CITypeHistoryManager.add(CITypeOperateType.DELETE_RELATION, ctr.parent_id,
|
||||
@@ -640,6 +669,7 @@ class CITypeAttributeGroupManager(object):
|
||||
:param name:
|
||||
:param group_order: group order
|
||||
:param attr_order:
|
||||
:param is_update:
|
||||
:return:
|
||||
"""
|
||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False)
|
||||
@@ -680,8 +710,8 @@ class CITypeAttributeGroupManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(group_id):
|
||||
group = CITypeAttributeGroup.get_by_id(group_id) \
|
||||
or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id)))
|
||||
group = (CITypeAttributeGroup.get_by_id(group_id) or
|
||||
abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(group_id))))
|
||||
group.soft_delete()
|
||||
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
|
||||
@@ -809,7 +839,7 @@ class CITypeTemplateManager(object):
|
||||
ResourceTypeEnum.CI,
|
||||
permissions=[PermEnum.READ])
|
||||
ACLManager().grant_resource_to_role(type_name,
|
||||
g.user.username,
|
||||
current_user.username,
|
||||
ResourceTypeEnum.CI)
|
||||
|
||||
else:
|
||||
@@ -947,11 +977,11 @@ class CITypeTemplateManager(object):
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
|
||||
rule['uid'] = g.user.uid
|
||||
rule['uid'] = current_user.uid
|
||||
try:
|
||||
AutoDiscoveryCITypeCRUD.add(**rule)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
current_app.logger.warning("import auto discovery rules failed: {}".format(e))
|
||||
|
||||
def import_template(self, tpt):
|
||||
import time
|
||||
@@ -1110,8 +1140,8 @@ class CITypeTriggerManager(object):
|
||||
|
||||
@staticmethod
|
||||
def update(_id, notify):
|
||||
existed = CITypeTrigger.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
||||
existed = (CITypeTrigger.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||
|
||||
existed2 = existed.to_dict()
|
||||
new = existed.update(notify=notify)
|
||||
@@ -1125,8 +1155,8 @@ class CITypeTriggerManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
existed = CITypeTrigger.get_by_id(_id) or \
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))
|
||||
existed = (CITypeTrigger.get_by_id(_id) or
|
||||
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@@ -1149,16 +1179,16 @@ class CITypeTriggerManager(object):
|
||||
|
||||
result = []
|
||||
for v in values:
|
||||
if isinstance(v.value, (datetime.date, datetime.datetime)) and \
|
||||
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d"):
|
||||
if (isinstance(v.value, (datetime.date, datetime.datetime)) and
|
||||
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d")):
|
||||
result.append(v)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def trigger_notify(trigger, ci):
|
||||
if trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or \
|
||||
not trigger.notify.get('notify_at'):
|
||||
if (trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
|
||||
not trigger.notify.get('notify_at')):
|
||||
from api.tasks.cmdb import trigger_notify
|
||||
|
||||
trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE)
|
||||
|
@@ -99,5 +99,7 @@ CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}
|
||||
|
||||
L_TYPE = None
|
||||
L_CI = None
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
@@ -176,8 +176,8 @@ class AttributeHistoryManger(object):
|
||||
def get_record_detail(record_id):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
record = OperationRecord.get_by_id(record_id) or \
|
||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id)))
|
||||
record = (OperationRecord.get_by_id(record_id) or
|
||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id))))
|
||||
|
||||
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
||||
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -201,7 +201,7 @@ class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True):
|
||||
if record_id is None:
|
||||
record = OperationRecord.create(uid=g.user.uid, type_id=type_id)
|
||||
record = OperationRecord.create(uid=current_user.uid, type_id=type_id)
|
||||
record_id = record.id
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
@@ -220,7 +220,7 @@ class AttributeHistoryManger(object):
|
||||
class CIRelationHistoryManager(object):
|
||||
@staticmethod
|
||||
def add(rel_obj, operate_type=OperateType.ADD):
|
||||
record = OperationRecord.create(uid=g.user.uid)
|
||||
record = OperationRecord.create(uid=current_user.uid)
|
||||
|
||||
CIRelationHistory.create(relation_id=rel_obj.id,
|
||||
record_id=record.id,
|
||||
@@ -279,7 +279,7 @@ class CITypeHistoryManager(object):
|
||||
for _type_id in type_ids:
|
||||
payload = dict(operate_type=operate_type,
|
||||
type_id=_type_id,
|
||||
uid=g.user.uid,
|
||||
uid=current_user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
|
@@ -4,8 +4,8 @@ import functools
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
@@ -74,7 +74,7 @@ class CIFilterPermsCRUD(DBMixin):
|
||||
|
||||
@classmethod
|
||||
def get_attr_filter(cls, type_id):
|
||||
if is_app_admin('cmdb') or g.user.username in ('worker', 'cmdb_agent'):
|
||||
if is_app_admin('cmdb') or current_user.username in ('worker', 'cmdb_agent'):
|
||||
return []
|
||||
|
||||
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
@@ -160,7 +160,7 @@ def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||
resource = callback(resource)
|
||||
|
||||
if current_app.config.get("USE_ACL") and resource:
|
||||
if g.user.username == "worker" or g.user.username == "cmdb_agent":
|
||||
if current_user.username == "worker" or current_user.username == "cmdb_agent":
|
||||
request.values['__is_admin'] = True
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import six
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -36,11 +36,13 @@ class PreferenceManager(object):
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == g.user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(PreferenceShowAttributes.type_id).all() \
|
||||
if instance else []
|
||||
tree_types = PreferenceTreeView.get_by(uid=g.user.uid, to_dict=False) if tree else []
|
||||
type_ids = list(set([i.type_id for i in types + tree_types]))
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.type_id).all() if instance else []
|
||||
|
||||
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
|
||||
type_ids = set([i.type_id for i in types + tree_types])
|
||||
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@staticmethod
|
||||
@@ -62,7 +64,7 @@ class PreferenceManager(object):
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
if i.uid == g.user.uid:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
@@ -72,7 +74,7 @@ class PreferenceManager(object):
|
||||
if tree:
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
for i in types:
|
||||
if i.uid == g.user.uid:
|
||||
if i.uid == current_user.uid:
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
@@ -91,7 +93,7 @@ class PreferenceManager(object):
|
||||
|
||||
attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join(
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == g.user.uid).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).all()
|
||||
@@ -120,7 +122,7 @@ class PreferenceManager(object):
|
||||
|
||||
@classmethod
|
||||
def create_or_update_show_attributes(cls, type_id, attr_order):
|
||||
existed_all = PreferenceShowAttributes.get_by(type_id=type_id, uid=g.user.uid, to_dict=False)
|
||||
existed_all = PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False)
|
||||
for x, order in attr_order:
|
||||
if isinstance(x, list):
|
||||
_attr, is_fixed = x
|
||||
@@ -128,13 +130,13 @@ class PreferenceManager(object):
|
||||
_attr, is_fixed = x, False
|
||||
attr = AttributeCache.get(_attr) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
||||
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
uid=current_user.uid,
|
||||
attr_id=attr.id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is None:
|
||||
PreferenceShowAttributes.create(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
uid=current_user.uid,
|
||||
attr_id=attr.id,
|
||||
order=order,
|
||||
is_fixed=is_fixed)
|
||||
@@ -148,7 +150,7 @@ class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_tree_view():
|
||||
res = PreferenceTreeView.get_by(uid=g.user.uid, to_dict=True)
|
||||
res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True)
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
ci_type = CITypeCache.get(item['type_id']).to_dict()
|
||||
@@ -176,14 +178,14 @@ class PreferenceManager(object):
|
||||
if i == attr.id or i == attr.name or i == attr.alias:
|
||||
levels[idx] = attr.id
|
||||
|
||||
existed = PreferenceTreeView.get_by(uid=g.user.uid, type_id=type_id, to_dict=False, first=True)
|
||||
existed = PreferenceTreeView.get_by(uid=current_user.uid, type_id=type_id, to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
if not levels:
|
||||
existed.soft_delete()
|
||||
return existed
|
||||
return existed.update(levels=levels)
|
||||
elif levels:
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=g.user.uid)
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid)
|
||||
|
||||
@staticmethod
|
||||
def get_relation_view():
|
||||
@@ -254,7 +256,7 @@ class PreferenceManager(object):
|
||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||
current_app.logger.debug(existed)
|
||||
if existed is None:
|
||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=g.user.uid, is_public=is_public)
|
||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=current_user.uid, is_public=is_public)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||
@@ -278,7 +280,7 @@ class PreferenceManager(object):
|
||||
@staticmethod
|
||||
def get_search_option(**kwargs):
|
||||
query = PreferenceSearchOption.get_by(only_query=True)
|
||||
query = query.filter(PreferenceSearchOption.uid == g.user.uid)
|
||||
query = query.filter(PreferenceSearchOption.uid == current_user.uid)
|
||||
|
||||
for k in kwargs:
|
||||
if hasattr(PreferenceSearchOption, k) and kwargs[k]:
|
||||
@@ -288,9 +290,9 @@ class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def add_search_option(**kwargs):
|
||||
kwargs['uid'] = g.user.uid
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
existed = PreferenceSearchOption.get_by(uid=g.user.uid,
|
||||
existed = PreferenceSearchOption.get_by(uid=current_user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
@@ -306,10 +308,10 @@ class PreferenceManager(object):
|
||||
|
||||
existed = PreferenceSearchOption.get_by_id(_id) or abort(404, ErrFormat.preference_search_option_not_found)
|
||||
|
||||
if g.user.uid != existed.uid:
|
||||
if current_user.uid != existed.uid:
|
||||
return abort(400, ErrFormat.no_permission2)
|
||||
|
||||
other = PreferenceSearchOption.get_by(uid=g.user.uid,
|
||||
other = PreferenceSearchOption.get_by(uid=current_user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
@@ -324,7 +326,7 @@ class PreferenceManager(object):
|
||||
def delete_search_option(_id):
|
||||
existed = PreferenceSearchOption.get_by_id(_id) or abort(404, ErrFormat.preference_search_option_not_found)
|
||||
|
||||
if g.user.uid != existed.uid:
|
||||
if current_user.uid != existed.uid:
|
||||
return abort(400, ErrFormat.no_permission2)
|
||||
|
||||
existed.soft_delete()
|
||||
|
@@ -24,21 +24,21 @@ class RelationTypeManager(object):
|
||||
|
||||
@staticmethod
|
||||
def add(name):
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and \
|
||||
abort(400, ErrFormat.relation_type_exists.format(name))
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and abort(
|
||||
400, ErrFormat.relation_type_exists.format(name))
|
||||
|
||||
return RelationType.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(rel_id, name):
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
existed = RelationType.get_by_id(rel_id) or abort(
|
||||
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
return existed.update(name=name)
|
||||
|
||||
@staticmethod
|
||||
def delete(rel_id):
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
existed = RelationType.get_by_id(rel_id) or abort(
|
||||
404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
existed.soft_delete()
|
||||
|
@@ -11,6 +11,7 @@ class ErrFormat(CommonErrFormat):
|
||||
|
||||
attribute_not_found = "属性 {} 不存在!"
|
||||
attribute_is_unique_id = "该属性是模型的唯一标识,不能被删除!"
|
||||
attribute_is_ref_by_type = "该属性被模型 {} 引用, 不能删除!"
|
||||
attribute_value_type_cannot_change = "属性的值类型不允许修改!"
|
||||
attribute_list_value_cannot_change = "多值不被允许修改!"
|
||||
attribute_index_cannot_change = "修改索引 非管理员不被允许!"
|
||||
@@ -20,7 +21,7 @@ class ErrFormat(CommonErrFormat):
|
||||
add_attribute_failed = "创建属性 {} 失败!"
|
||||
update_attribute_failed = "修改属性 {} 失败!"
|
||||
cannot_edit_attribute = "您没有权限修改该属性!"
|
||||
cannot_delete_attribute = "您没有权限删除该属性!"
|
||||
cannot_delete_attribute = "目前只允许 属性创建人、管理员 删除属性!"
|
||||
attribute_name_cannot_be_builtin = "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
|
||||
ci_not_found = "CI {} 不存在"
|
||||
@@ -37,6 +38,7 @@ class ErrFormat(CommonErrFormat):
|
||||
unique_key_not_define = "主键未定义或者已被删除"
|
||||
only_owner_can_delete = "只有创建人才能删除它!"
|
||||
ci_exists_and_cannot_delete_type = "因为CI已经存在,不能删除模型"
|
||||
ci_relation_view_exists_and_cannot_delete_type = "因为关系视图 {} 引用了该模型,不能删除模型"
|
||||
ci_type_group_not_found = "模型分组 {} 不存在"
|
||||
ci_type_group_exists = "模型分组 {} 已经存在"
|
||||
ci_type_relation_not_found = "模型关系 {} 不存在"
|
||||
|
@@ -7,8 +7,9 @@ import copy
|
||||
import time
|
||||
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
from jinja2 import Template
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
@@ -105,7 +106,7 @@ class Search(object):
|
||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||
if ci_filter:
|
||||
sub = []
|
||||
ci_filter = Template(ci_filter).render(user=g.user)
|
||||
ci_filter = Template(ci_filter).render(user=current_user)
|
||||
for i in ci_filter.split(','):
|
||||
if i.startswith("~") and not sub:
|
||||
queries.append(i)
|
||||
@@ -244,10 +245,8 @@ class Search(object):
|
||||
new_table = _v_query_sql
|
||||
|
||||
if self.only_type_query or not self.type_id_list:
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \
|
||||
"FROM ({0}) AS C " \
|
||||
"ORDER BY C.value {2} " \
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count)
|
||||
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
elif self.type_id_list:
|
||||
self.query_sql = """SELECT C.ci_id
|
||||
@@ -355,7 +354,7 @@ class Search(object):
|
||||
else:
|
||||
result.append(q)
|
||||
|
||||
_is_app_admin = is_app_admin('cmdb') or g.user.username == "worker"
|
||||
_is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
|
||||
if result and not has_type and not _is_app_admin:
|
||||
type_q = self.__get_types_has_read()
|
||||
if id_query:
|
||||
|
@@ -297,8 +297,8 @@ class Search(object):
|
||||
if not attr:
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
||||
|
||||
sort_by = "{0}.keyword".format(field) \
|
||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field
|
||||
sort_by = ("{0}.keyword".format(field)
|
||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field)
|
||||
sorts.append({sort_by: {"order": sort_type}})
|
||||
|
||||
self.query.update(dict(sort=sorts))
|
||||
|
@@ -4,14 +4,16 @@ from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
import six
|
||||
from markupsafe import escape
|
||||
|
||||
import api.models.cmdb as model
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
|
||||
TIME_RE = re.compile(r"^(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d$")
|
||||
|
||||
|
||||
def string2int(x):
|
||||
return int(float(x))
|
||||
@@ -30,8 +32,8 @@ class ValueTypeMap(object):
|
||||
deserialize = {
|
||||
ValueTypeEnum.INT: string2int,
|
||||
ValueTypeEnum.FLOAT: float,
|
||||
ValueTypeEnum.TEXT: lambda x: escape(x).encode('utf-8').decode('utf-8'),
|
||||
ValueTypeEnum.TIME: lambda x: escape(x).encode('utf-8').decode('utf-8'),
|
||||
ValueTypeEnum.TEXT: lambda x: x,
|
||||
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
|
||||
ValueTypeEnum.DATETIME: str2datetime,
|
||||
ValueTypeEnum.DATE: str2datetime,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
@@ -61,15 +63,11 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.INT: model.IntegerChoice,
|
||||
ValueTypeEnum.FLOAT: model.FloatChoice,
|
||||
ValueTypeEnum.TEXT: model.TextChoice,
|
||||
ValueTypeEnum.TIME: model.TextChoice,
|
||||
}
|
||||
|
||||
table = {
|
||||
ValueTypeEnum.INT: model.CIValueInteger,
|
||||
ValueTypeEnum.TEXT: model.CIValueText,
|
||||
ValueTypeEnum.DATETIME: model.CIValueDateTime,
|
||||
ValueTypeEnum.DATE: model.CIValueDateTime,
|
||||
ValueTypeEnum.TIME: model.CIValueText,
|
||||
ValueTypeEnum.FLOAT: model.CIValueFloat,
|
||||
ValueTypeEnum.JSON: model.CIValueJson,
|
||||
'index_{0}'.format(ValueTypeEnum.INT): model.CIIndexValueInteger,
|
||||
'index_{0}'.format(ValueTypeEnum.TEXT): model.CIIndexValueText,
|
||||
@@ -81,12 +79,7 @@ class ValueTypeMap(object):
|
||||
}
|
||||
|
||||
table_name = {
|
||||
ValueTypeEnum.INT: 'c_value_integers',
|
||||
ValueTypeEnum.TEXT: 'c_value_texts',
|
||||
ValueTypeEnum.DATETIME: 'c_value_datetime',
|
||||
ValueTypeEnum.DATE: 'c_value_datetime',
|
||||
ValueTypeEnum.TIME: 'c_value_texts',
|
||||
ValueTypeEnum.FLOAT: 'c_value_floats',
|
||||
ValueTypeEnum.JSON: 'c_value_json',
|
||||
'index_{0}'.format(ValueTypeEnum.INT): 'c_value_index_integers',
|
||||
'index_{0}'.format(ValueTypeEnum.TEXT): 'c_value_index_texts',
|
||||
@@ -117,8 +110,11 @@ class TableMap(object):
|
||||
@property
|
||||
def table(self):
|
||||
attr = AttributeCache.get(self.attr_name) if not self.attr else self.attr
|
||||
if self.is_index is None:
|
||||
if attr.value_type != ValueTypeEnum.TEXT and attr.value_type != ValueTypeEnum.JSON:
|
||||
self.is_index = True
|
||||
elif self.is_index is None:
|
||||
self.is_index = attr.is_index
|
||||
|
||||
i = "index_{0}".format(attr.value_type) if self.is_index else attr.value_type
|
||||
|
||||
return ValueTypeMap.table.get(i)
|
||||
@@ -126,8 +122,11 @@ class TableMap(object):
|
||||
@property
|
||||
def table_name(self):
|
||||
attr = AttributeCache.get(self.attr_name) if not self.attr else self.attr
|
||||
if self.is_index is None:
|
||||
if attr.value_type != ValueTypeEnum.TEXT and attr.value_type != ValueTypeEnum.JSON:
|
||||
self.is_index = True
|
||||
elif self.is_index is None:
|
||||
self.is_index = attr.is_index
|
||||
|
||||
i = "index_{0}".format(attr.value_type) if self.is_index else attr.value_type
|
||||
|
||||
return ValueTypeMap.table_name.get(i)
|
||||
|
@@ -80,9 +80,10 @@ class AttributeValueManager(object):
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def __deserialize_value(value_type, value):
|
||||
def _deserialize_value(value_type, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
deserialize = ValueTypeMap.deserialize[value_type]
|
||||
try:
|
||||
v = deserialize(value)
|
||||
@@ -91,13 +92,13 @@ class AttributeValueManager(object):
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_choice(attr, value_type, value):
|
||||
def _check_is_choice(attr, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook)
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
def _check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
existed = db.session.query(value_table.attr_id).join(CI, CI.id == value_table.ci_id).filter(
|
||||
CI.type_id == type_id).filter(
|
||||
value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter(
|
||||
@@ -106,20 +107,20 @@ class AttributeValueManager(object):
|
||||
existed and abort(400, ErrFormat.attribute_value_unique_required.format(attr.alias, value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_required(type_id, attr, value, type_attr=None):
|
||||
def _check_is_required(type_id, attr, value, type_attr=None):
|
||||
type_attr = type_attr or CITypeAttributeCache.get(type_id, attr.id)
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self.__deserialize_value(attr.value_type, value)
|
||||
v = self._deserialize_value(attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self.__check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self.__check_is_unique(
|
||||
attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self._check_is_unique(
|
||||
value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
|
||||
|
||||
self.__check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
self._check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
@@ -144,7 +145,7 @@ class AttributeValueManager(object):
|
||||
return record_id
|
||||
|
||||
@staticmethod
|
||||
def __compute_attr_value_from_expr(expr, ci_dict):
|
||||
def _compute_attr_value_from_expr(expr, ci_dict):
|
||||
t = jinja2.Template(expr).render(ci_dict)
|
||||
|
||||
try:
|
||||
@@ -154,7 +155,7 @@ class AttributeValueManager(object):
|
||||
return t
|
||||
|
||||
@staticmethod
|
||||
def __compute_attr_value_from_script(script, ci_dict):
|
||||
def _compute_attr_value_from_script(script, ci_dict):
|
||||
script = jinja2.Template(script).render(ci_dict)
|
||||
|
||||
script_f = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
|
||||
@@ -183,22 +184,22 @@ class AttributeValueManager(object):
|
||||
|
||||
return [var for var in schema.get("properties")]
|
||||
|
||||
def _compute_attr_value(self, attr, payload, ci):
|
||||
attrs = self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr') else \
|
||||
self._jinja2_parse(attr['compute_script'])
|
||||
def _compute_attr_value(self, attr, payload, ci_id):
|
||||
attrs = (self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr')
|
||||
else self._jinja2_parse(attr['compute_script']))
|
||||
not_existed = [i for i in attrs if i not in payload]
|
||||
if ci is not None:
|
||||
payload.update(self.get_attr_values(not_existed, ci.id))
|
||||
if ci_id is not None:
|
||||
payload.update(self.get_attr_values(not_existed, ci_id))
|
||||
|
||||
if attr['compute_expr']:
|
||||
return self.__compute_attr_value_from_expr(attr['compute_expr'], payload)
|
||||
return self._compute_attr_value_from_expr(attr['compute_expr'], payload)
|
||||
elif attr['compute_script']:
|
||||
return self.__compute_attr_value_from_script(attr['compute_script'], payload)
|
||||
return self._compute_attr_value_from_script(attr['compute_script'], payload)
|
||||
|
||||
def handle_ci_compute_attributes(self, ci_dict, computed_attrs, ci):
|
||||
payload = copy.deepcopy(ci_dict)
|
||||
for attr in computed_attrs:
|
||||
computed_value = self._compute_attr_value(attr, payload, ci)
|
||||
computed_value = self._compute_attr_value(attr, payload, ci and ci.id)
|
||||
if computed_value is not None:
|
||||
ci_dict[attr['name']] = computed_value
|
||||
|
||||
@@ -220,7 +221,7 @@ class AttributeValueManager(object):
|
||||
for i in handle_arg_list(value)]
|
||||
ci_dict[key] = value_list
|
||||
if not value_list:
|
||||
self.__check_is_required(type_id, attr, '')
|
||||
self._check_is_required(type_id, attr, '')
|
||||
|
||||
else:
|
||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||
@@ -310,7 +311,7 @@ class AttributeValueManager(object):
|
||||
if attr.is_list:
|
||||
value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)]
|
||||
if not value_list:
|
||||
self.__check_is_required(ci.type_id, attr, '')
|
||||
self._check_is_required(ci.type_id, attr, '')
|
||||
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
|
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))
|
@@ -4,11 +4,11 @@ COMMON_SETTING_QUEUE = "common_setting_async"
|
||||
|
||||
|
||||
class OperatorType(BaseEnum):
|
||||
EQUAL = 1 # 等于
|
||||
NOT_EQUAL = 2 # 不等于
|
||||
IN = 3 # 包含
|
||||
NOT_IN = 4 # 不包含
|
||||
GREATER_THAN = 5 # 大于
|
||||
LESS_THAN = 6 # 小于
|
||||
IS_EMPTY = 7 # 为空
|
||||
IS_NOT_EMPTY = 8 # 不为空
|
||||
EQUAL = 1
|
||||
NOT_EQUAL = 2
|
||||
IN = 3
|
||||
NOT_IN = 4
|
||||
GREATER_THAN = 5
|
||||
LESS_THAN = 6
|
||||
IS_EMPTY = 7
|
||||
IS_NOT_EMPTY = 8
|
||||
|
@@ -7,41 +7,26 @@ from wtforms import IntegerField
|
||||
from wtforms import StringField
|
||||
from wtforms import validators
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.utils import get_df_from_read_sql
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
from api.models.common_setting import Department, Employee
|
||||
|
||||
sub_departments_column_name = 'sub_departments'
|
||||
|
||||
|
||||
def drop_ts_column(df):
|
||||
columns = list(df.columns)
|
||||
remove_columns = []
|
||||
for column in ['created_at', 'updated_at', 'deleted_at', 'last_login']:
|
||||
targets = list(filter(lambda c: c.startswith(column), columns))
|
||||
if targets:
|
||||
remove_columns.extend(targets)
|
||||
|
||||
remove_columns = list(set(remove_columns))
|
||||
|
||||
return df.drop(remove_columns, axis=1) if len(remove_columns) > 0 else df
|
||||
|
||||
|
||||
def get_department_df():
|
||||
def get_all_department_list(to_dict=True):
|
||||
criterion = [
|
||||
Department.deleted == 0,
|
||||
]
|
||||
query = Department.query.filter(
|
||||
*criterion
|
||||
)
|
||||
df = get_df_from_read_sql(query)
|
||||
if df.empty:
|
||||
return
|
||||
return drop_ts_column(df)
|
||||
).order_by(Department.department_id.asc())
|
||||
results = query.all()
|
||||
return [r.to_dict() for r in results] if to_dict else results
|
||||
|
||||
|
||||
def get_all_employee_df(block=0):
|
||||
def get_all_employee_list(block=0, to_dict=True):
|
||||
criterion = [
|
||||
Employee.deleted == 0,
|
||||
]
|
||||
@@ -50,112 +35,106 @@ def get_all_employee_df(block=0):
|
||||
Employee.block == block
|
||||
)
|
||||
|
||||
entities = [getattr(Employee, c) for c in Employee.get_columns(
|
||||
).keys() if c not in ['deleted', 'deleted_at']]
|
||||
query = Employee.query.with_entities(
|
||||
*entities
|
||||
).filter(
|
||||
*criterion
|
||||
)
|
||||
df = get_df_from_read_sql(query)
|
||||
if df.empty:
|
||||
return df
|
||||
return drop_ts_column(df)
|
||||
results = db.session.query(Employee).filter(*criterion).all()
|
||||
|
||||
DepartmentTreeEmployeeColumns = [
|
||||
'acl_rid',
|
||||
'employee_id',
|
||||
'username',
|
||||
'nickname',
|
||||
'email',
|
||||
'mobile',
|
||||
'direct_supervisor_id',
|
||||
'block',
|
||||
'department_id',
|
||||
]
|
||||
|
||||
def format_columns(e):
|
||||
return {column: getattr(e, column) for column in DepartmentTreeEmployeeColumns}
|
||||
|
||||
return [format_columns(r) for r in results] if to_dict else results
|
||||
|
||||
|
||||
class DepartmentTree(object):
|
||||
def __init__(self, append_employee=False, block=-1):
|
||||
self.append_employee = append_employee
|
||||
self.block = block
|
||||
self.d_df = get_department_df()
|
||||
self.employee_df = get_all_employee_df(
|
||||
self.all_department_list = get_all_department_list()
|
||||
self.all_employee_list = get_all_employee_list(
|
||||
block) if append_employee else None
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def get_employees_by_d_id(self, d_id):
|
||||
_df = self.employee_df[
|
||||
self.employee_df['department_id'].eq(d_id)
|
||||
].sort_values(by=['direct_supervisor_id'], ascending=True)
|
||||
if _df.empty:
|
||||
block = self.block
|
||||
|
||||
def filter_department_id(e):
|
||||
if self.block != -1:
|
||||
return e['department_id'] == d_id and e['block'] == block
|
||||
return e.department_id == d_id
|
||||
|
||||
results = list(filter(lambda e: filter_department_id(e), self.all_employee_list))
|
||||
|
||||
return results
|
||||
|
||||
def get_department_by_parent_id(self, parent_id):
|
||||
results = list(filter(lambda d: d['department_parent_id'] == parent_id, self.all_department_list))
|
||||
if not results:
|
||||
return []
|
||||
|
||||
if self.block != -1:
|
||||
_df = _df[
|
||||
_df['block'].eq(self.block)
|
||||
]
|
||||
|
||||
return _df.to_dict('records')
|
||||
return results
|
||||
|
||||
def get_tree_departments(self):
|
||||
# 一级部门
|
||||
top_df = self.d_df[self.d_df['department_parent_id'].eq(-1)]
|
||||
if top_df.empty:
|
||||
top_departments = self.get_department_by_parent_id(-1)
|
||||
if len(top_departments) == 0:
|
||||
return []
|
||||
|
||||
d_list = []
|
||||
|
||||
for index in top_df.index:
|
||||
top_d = top_df.loc[index].to_dict()
|
||||
|
||||
for top_d in top_departments:
|
||||
department_id = top_d['department_id']
|
||||
|
||||
# 检查 department_id 是否作为其他部门的 parent
|
||||
sub_df = self.d_df[
|
||||
self.d_df['department_parent_id'].eq(department_id)
|
||||
].sort_values(by=['sort_value'], ascending=True)
|
||||
|
||||
sub_deps = self.get_department_by_parent_id(department_id)
|
||||
employees = []
|
||||
|
||||
if self.append_employee:
|
||||
# 要包含员工
|
||||
employees = self.get_employees_by_d_id(department_id)
|
||||
|
||||
top_d['employees'] = employees
|
||||
|
||||
if sub_df.empty:
|
||||
if len(sub_deps) == 0:
|
||||
top_d[sub_departments_column_name] = []
|
||||
d_list.append(top_d)
|
||||
continue
|
||||
|
||||
self.parse_sub_department(sub_df, top_d)
|
||||
self.parse_sub_department(sub_deps, top_d)
|
||||
d_list.append(top_d)
|
||||
|
||||
return d_list
|
||||
|
||||
def get_all_departments(self, is_tree=1):
|
||||
if self.d_df.empty:
|
||||
if len(self.all_department_list) == 0:
|
||||
return []
|
||||
|
||||
if is_tree != 1:
|
||||
return self.d_df.to_dict('records')
|
||||
return self.all_department_list
|
||||
|
||||
return self.get_tree_departments()
|
||||
|
||||
def parse_sub_department(self, df, top_d):
|
||||
def parse_sub_department(self, deps, top_d):
|
||||
sub_departments = []
|
||||
for s_index in df.index:
|
||||
d = df.loc[s_index].to_dict()
|
||||
sub_df = self.d_df[
|
||||
self.d_df['department_parent_id'].eq(
|
||||
df.at[s_index, 'department_id'])
|
||||
].sort_values(by=['sort_value'], ascending=True)
|
||||
for d in deps:
|
||||
sub_deps = self.get_department_by_parent_id(d['department_id'])
|
||||
employees = []
|
||||
|
||||
if self.append_employee:
|
||||
# 要包含员工
|
||||
employees = self.get_employees_by_d_id(
|
||||
df.at[s_index, 'department_id'])
|
||||
employees = self.get_employees_by_d_id(d['department_id'])
|
||||
|
||||
d['employees'] = employees
|
||||
|
||||
if sub_df.empty:
|
||||
if len(sub_deps) == 0:
|
||||
d[sub_departments_column_name] = []
|
||||
sub_departments.append(d)
|
||||
continue
|
||||
|
||||
self.parse_sub_department(sub_df, d)
|
||||
self.parse_sub_department(sub_deps, d)
|
||||
sub_departments.append(d)
|
||||
|
||||
top_d[sub_departments_column_name] = sub_departments
|
||||
@@ -202,7 +181,6 @@ class DepartmentCRUD(object):
|
||||
def check_department_parent_id_allow(d_id, department_parent_id):
|
||||
if department_parent_id == 0:
|
||||
return
|
||||
# 检查 department_parent_id 是否在许可范围内
|
||||
allow_p_d_id_list = DepartmentCRUD.get_allow_parent_d_id_by(d_id)
|
||||
target = list(
|
||||
filter(lambda d: d['department_id'] == department_parent_id, allow_p_d_id_list))
|
||||
@@ -281,9 +259,6 @@ class DepartmentCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_allow_parent_d_id_by(department_id):
|
||||
"""
|
||||
获取可以成为 department_id 的 department_parent_id 的 list
|
||||
"""
|
||||
tree_list = DepartmentCRUD.get_department_tree_list()
|
||||
|
||||
allow_d_id_list = []
|
||||
@@ -321,58 +296,57 @@ class DepartmentCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_department_tree_list():
|
||||
df = get_department_df()
|
||||
if df.empty:
|
||||
all_deps = get_all_department_list()
|
||||
if len(all_deps) == 0:
|
||||
return []
|
||||
|
||||
# 一级部门
|
||||
top_df = df[df['department_parent_id'].eq(-1)]
|
||||
if top_df.empty:
|
||||
top_deps = list(filter(lambda d: d['department_parent_id'] == -1, all_deps))
|
||||
if len(top_deps) == 0:
|
||||
return []
|
||||
|
||||
tree_list = []
|
||||
|
||||
for index in top_df.index:
|
||||
for top_d in top_deps:
|
||||
tree = Tree()
|
||||
identifier_root = top_df.at[index, 'department_id']
|
||||
identifier_root = top_d['department_id']
|
||||
tree.create_node(
|
||||
top_df.at[index, 'department_name'],
|
||||
top_d['department_name'],
|
||||
identifier_root
|
||||
)
|
||||
|
||||
# 检查 department_id 是否作为其他部门的 parent
|
||||
sub_df = df[
|
||||
df['department_parent_id'].eq(identifier_root)
|
||||
]
|
||||
if sub_df.empty:
|
||||
sub_ds = list(filter(lambda d: d['department_parent_id'] == identifier_root, all_deps))
|
||||
if len(sub_ds) == 0:
|
||||
tree_list.append(tree)
|
||||
continue
|
||||
|
||||
DepartmentCRUD.parse_sub_department_node(
|
||||
sub_df, df, tree, identifier_root)
|
||||
sub_ds, all_deps, tree, identifier_root)
|
||||
|
||||
tree_list.append(tree)
|
||||
|
||||
return tree_list
|
||||
|
||||
@staticmethod
|
||||
def parse_sub_department_node(df, all_df, tree, parent_id):
|
||||
for s_index in df.index:
|
||||
def parse_sub_department_node(sub_ds, all_ds, tree, parent_id):
|
||||
for d in sub_ds:
|
||||
tree.create_node(
|
||||
df.at[s_index, 'department_name'],
|
||||
df.at[s_index, 'department_id'],
|
||||
d['department_name'],
|
||||
d['department_id'],
|
||||
parent=parent_id
|
||||
)
|
||||
|
||||
sub_df = all_df[
|
||||
all_df['department_parent_id'].eq(
|
||||
df.at[s_index, 'department_id'])
|
||||
]
|
||||
if sub_df.empty:
|
||||
next_sub_ds = list(filter(lambda item_d: item_d['department_parent_id'] == d['department_id'], all_ds))
|
||||
if len(next_sub_ds) == 0:
|
||||
continue
|
||||
|
||||
DepartmentCRUD.parse_sub_department_node(
|
||||
sub_df, all_df, tree, df.at[s_index, 'department_id'])
|
||||
next_sub_ds, all_ds, tree, d['department_id'])
|
||||
|
||||
@staticmethod
|
||||
def get_department_by_query(query, to_dict=True):
|
||||
results = query.all()
|
||||
if not results:
|
||||
return []
|
||||
return results if not to_dict else [r.to_dict() for r in results]
|
||||
|
||||
@staticmethod
|
||||
def get_departments_and_ids(department_parent_id, block):
|
||||
@@ -380,44 +354,30 @@ class DepartmentCRUD(object):
|
||||
Department.department_parent_id == department_parent_id,
|
||||
Department.deleted == 0,
|
||||
).order_by(Department.sort_value.asc())
|
||||
df = get_df_from_read_sql(query)
|
||||
if df.empty:
|
||||
all_departments = DepartmentCRUD.get_department_by_query(query)
|
||||
if len(all_departments) == 0:
|
||||
return [], []
|
||||
|
||||
tree_list = DepartmentCRUD.get_department_tree_list()
|
||||
employee_df = get_all_employee_df(block)
|
||||
all_employee_list = get_all_employee_list(block)
|
||||
|
||||
department_id_list = list(df['department_id'].values)
|
||||
department_id_list = [d['department_id'] for d in all_departments]
|
||||
query = Department.query.filter(
|
||||
Department.department_parent_id.in_(department_id_list),
|
||||
Department.deleted == 0,
|
||||
).order_by(Department.sort_value.asc()).group_by(Department.department_id)
|
||||
sub_df = get_df_from_read_sql(query)
|
||||
if sub_df.empty:
|
||||
df['has_sub'] = 0
|
||||
sub_deps = DepartmentCRUD.get_department_by_query(query)
|
||||
|
||||
def handle_row_employee_count(row):
|
||||
return len(employee_df[employee_df['department_id'] == row['department_id']])
|
||||
sub_map = {d['department_parent_id']: 1 for d in sub_deps}
|
||||
|
||||
df['employee_count'] = df.apply(
|
||||
lambda row: handle_row_employee_count(row), axis=1)
|
||||
for d in all_departments:
|
||||
d['has_sub'] = sub_map.get(d['department_id'], 0)
|
||||
|
||||
else:
|
||||
sub_map = {d['department_parent_id']: 1 for d in sub_df.to_dict('records')}
|
||||
d_ids = DepartmentCRUD.get_department_id_list_by_root(d['department_id'], tree_list)
|
||||
|
||||
def handle_row(row):
|
||||
d_ids = DepartmentCRUD.get_department_id_list_by_root(
|
||||
row['department_id'], tree_list)
|
||||
row['employee_count'] = len(
|
||||
employee_df[employee_df['department_id'].isin(d_ids)])
|
||||
d['employee_count'] = len(list(filter(lambda e: e['department_id'] in d_ids, all_employee_list)))
|
||||
|
||||
row['has_sub'] = sub_map.get(row['department_id'], 0)
|
||||
|
||||
return row
|
||||
|
||||
df = df.apply(lambda row: handle_row(row), axis=1)
|
||||
|
||||
return df.to_dict('records'), department_id_list
|
||||
return all_departments, department_id_list
|
||||
|
||||
@staticmethod
|
||||
def get_department_id_list_by_root(root_department_id, tree_list=None):
|
||||
|
@@ -3,7 +3,6 @@
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
import pandas as pd
|
||||
from flask import abort
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import or_, literal_column, func, not_, and_
|
||||
@@ -17,9 +16,20 @@ from api.extensions import db
|
||||
from api.lib.common_setting.acl import ACLManager
|
||||
from api.lib.common_setting.const import COMMON_SETTING_QUEUE, OperatorType
|
||||
from api.lib.common_setting.resp_format import ErrFormat
|
||||
from api.lib.common_setting.utils import get_df_from_read_sql
|
||||
from api.models.common_setting import Employee, Department
|
||||
|
||||
acl_user_columns = [
|
||||
'email',
|
||||
'mobile',
|
||||
'nickname',
|
||||
'username',
|
||||
'password',
|
||||
'block',
|
||||
'avatar',
|
||||
]
|
||||
employee_pop_columns = ['password']
|
||||
can_not_edit_columns = ['email']
|
||||
|
||||
|
||||
def edit_acl_user(uid, **kwargs):
|
||||
user_data = {column: kwargs.get(
|
||||
@@ -70,9 +80,6 @@ class EmployeeCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_employee_by_uid_with_create(_uid):
|
||||
"""
|
||||
根据 uid 获取员工信息,不存在则创建
|
||||
"""
|
||||
try:
|
||||
return EmployeeCRUD.get_employee_by_uid(_uid).to_dict()
|
||||
except Exception as e:
|
||||
@@ -102,7 +109,6 @@ class EmployeeCRUD(object):
|
||||
acl_uid=user_info['uid'],
|
||||
)
|
||||
return existed.to_dict()
|
||||
# 创建员工
|
||||
if not user_info.get('nickname', None):
|
||||
user_info['nickname'] = user_info['name']
|
||||
|
||||
@@ -145,9 +151,6 @@ class EmployeeCRUD(object):
|
||||
|
||||
if len(e_list) > 0:
|
||||
from api.tasks.common_setting import edit_employee_department_in_acl
|
||||
# fixme: comment next line
|
||||
# edit_employee_department_in_acl(e_list, new_department_id, current_user.uid)
|
||||
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(e_list, new_department_id, current_user.uid),
|
||||
queue=COMMON_SETTING_QUEUE
|
||||
@@ -209,173 +212,6 @@ class EmployeeCRUD(object):
|
||||
*criterion
|
||||
).count()
|
||||
|
||||
@staticmethod
|
||||
def import_employee(employee_list):
|
||||
return CreateEmployee().batch_create(employee_list)
|
||||
|
||||
@staticmethod
|
||||
def get_export_employee_df(block_status):
|
||||
criterion = [
|
||||
Employee.deleted == 0
|
||||
]
|
||||
if block_status >= 0:
|
||||
criterion.append(
|
||||
Employee.block == block_status
|
||||
)
|
||||
|
||||
query = Employee.query.with_entities(
|
||||
Employee.employee_id,
|
||||
Employee.nickname,
|
||||
Employee.email,
|
||||
Employee.sex,
|
||||
Employee.mobile,
|
||||
Employee.position_name,
|
||||
Employee.last_login,
|
||||
Employee.department_id,
|
||||
Employee.direct_supervisor_id,
|
||||
).filter(*criterion)
|
||||
df = get_df_from_read_sql(query)
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
query = Department.query.filter(
|
||||
*criterion
|
||||
)
|
||||
department_df = get_df_from_read_sql(query)
|
||||
|
||||
def find_name(row):
|
||||
department_id = row['department_id']
|
||||
_df = department_df[department_df['department_id']
|
||||
== department_id]
|
||||
row['department_name'] = '' if _df.empty else _df.iloc[0]['department_name']
|
||||
|
||||
direct_supervisor_id = row['direct_supervisor_id']
|
||||
_df = df[df['employee_id'] == direct_supervisor_id]
|
||||
row['nickname_direct_supervisor'] = '' if _df.empty else _df.iloc[0]['nickname']
|
||||
|
||||
if isinstance(row['last_login'], pd.Timestamp):
|
||||
try:
|
||||
row['last_login'] = str(row['last_login'])
|
||||
except:
|
||||
row['last_login'] = ''
|
||||
else:
|
||||
row['last_login'] = ''
|
||||
|
||||
return row
|
||||
|
||||
df = df.apply(find_name, axis=1)
|
||||
df.drop(['department_id', 'direct_supervisor_id',
|
||||
'employee_id'], axis=1, inplace=True)
|
||||
return df
|
||||
|
||||
@staticmethod
|
||||
def batch_employee(column_name, column_value, employee_id_list):
|
||||
if not column_value:
|
||||
abort(400, ErrFormat.value_is_required)
|
||||
if column_name in ['password', 'block']:
|
||||
return EmployeeCRUD.batch_edit_password_or_block_column(column_name, employee_id_list, column_value, True)
|
||||
|
||||
elif column_name in ['department_id']:
|
||||
return EmployeeCRUD.batch_edit_employee_department(employee_id_list, column_value)
|
||||
|
||||
elif column_name in [
|
||||
'direct_supervisor_id', 'position_name'
|
||||
]:
|
||||
return EmployeeCRUD.batch_edit_column(column_name, employee_id_list, column_value, False)
|
||||
|
||||
else:
|
||||
abort(400, ErrFormat.column_name_not_support)
|
||||
|
||||
@staticmethod
|
||||
def batch_edit_employee_department(employee_id_list, column_value):
|
||||
err_list = []
|
||||
employee_list = []
|
||||
for _id in employee_id_list:
|
||||
try:
|
||||
existed = EmployeeCRUD.get_employee_by_id(_id)
|
||||
employee = dict(
|
||||
e_acl_rid=existed.acl_rid,
|
||||
department_id=existed.department_id
|
||||
)
|
||||
employee_list.append(employee)
|
||||
existed.update(department_id=column_value)
|
||||
|
||||
except Exception as e:
|
||||
err_list.append({
|
||||
'employee_id': _id,
|
||||
'err': str(e),
|
||||
})
|
||||
from api.tasks.common_setting import edit_employee_department_in_acl
|
||||
edit_employee_department_in_acl.apply_async(
|
||||
args=(employee_list, column_value, current_user.uid),
|
||||
queue=COMMON_SETTING_QUEUE
|
||||
)
|
||||
return err_list
|
||||
|
||||
@staticmethod
|
||||
def batch_edit_password_or_block_column(column_name, employee_id_list, column_value, is_acl=False):
|
||||
if column_name == 'block':
|
||||
err_list = []
|
||||
success_list = []
|
||||
for _id in employee_id_list:
|
||||
try:
|
||||
employee = EmployeeCRUD.edit_employee_block_column(
|
||||
_id, is_acl, **{column_name: column_value})
|
||||
success_list.append(employee)
|
||||
except Exception as e:
|
||||
err_list.append({
|
||||
'employee_id': _id,
|
||||
'err': str(e),
|
||||
})
|
||||
return err_list
|
||||
else:
|
||||
return EmployeeCRUD.batch_edit_column(column_name, employee_id_list, column_value, is_acl)
|
||||
|
||||
@staticmethod
|
||||
def batch_edit_column(column_name, employee_id_list, column_value, is_acl=False):
|
||||
err_list = []
|
||||
for _id in employee_id_list:
|
||||
try:
|
||||
EmployeeCRUD.edit_employee_single_column(
|
||||
_id, is_acl, **{column_name: column_value})
|
||||
except Exception as e:
|
||||
err_list.append({
|
||||
'employee_id': _id,
|
||||
'err': str(e),
|
||||
})
|
||||
|
||||
return err_list
|
||||
|
||||
@staticmethod
|
||||
def edit_employee_single_column(_id, is_acl=False, **kwargs):
|
||||
existed = EmployeeCRUD.get_employee_by_id(_id)
|
||||
|
||||
if is_acl:
|
||||
return edit_acl_user(existed.acl_uid, **kwargs)
|
||||
|
||||
try:
|
||||
for column in employee_pop_columns:
|
||||
if kwargs.get(column):
|
||||
kwargs.pop(column)
|
||||
|
||||
return existed.update(**kwargs)
|
||||
except Exception as e:
|
||||
return abort(400, str(e))
|
||||
|
||||
@staticmethod
|
||||
def edit_employee_block_column(_id, is_acl=False, **kwargs):
|
||||
existed = EmployeeCRUD.get_employee_by_id(_id)
|
||||
value = get_block_value(kwargs.get('block'))
|
||||
if value is True:
|
||||
# 判断该用户是否为 部门负责人,或者员工的直接上级
|
||||
check_department_director_id_or_direct_supervisor_id(_id)
|
||||
|
||||
if is_acl:
|
||||
kwargs['block'] = value
|
||||
edit_acl_user(existed.acl_uid, **kwargs)
|
||||
data = existed.to_dict()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def check_email_unique(email, _id=0):
|
||||
criterion = [
|
||||
@@ -395,7 +231,7 @@ class EmployeeCRUD(object):
|
||||
raise Exception(err)
|
||||
|
||||
@staticmethod
|
||||
def get_employee_list_by_body(department_id, block_status, search='', order='', conditions=[], page=1,
|
||||
def get_employee_list_by_body(department_id, block_status, search='', order='', conditions=None, page=1,
|
||||
page_size=10):
|
||||
criterion = [
|
||||
Employee.deleted == 0
|
||||
@@ -461,7 +297,7 @@ class EmployeeCRUD(object):
|
||||
@staticmethod
|
||||
def get_expr_by_condition(column, operator, value, relation):
|
||||
"""
|
||||
根据conditions返回expr: (and_list, or_list)
|
||||
get expr: (and_list, or_list)
|
||||
"""
|
||||
attr = EmployeeCRUD.get_attr_by_column(column)
|
||||
# 根据operator生成条件表达式
|
||||
@@ -481,7 +317,7 @@ class EmployeeCRUD(object):
|
||||
if value:
|
||||
abort(400, ErrFormat.query_column_none_keep_value_empty.format(column))
|
||||
expr = [attr.is_(None)]
|
||||
if column not in ["entry_date", "leave_date", "dfc_entry_date", "last_login"]:
|
||||
if column not in ["last_login"]:
|
||||
expr += [attr == '']
|
||||
expr = [or_(*expr)]
|
||||
elif operator == OperatorType.IS_NOT_EMPTY:
|
||||
@@ -495,7 +331,6 @@ class EmployeeCRUD(object):
|
||||
else:
|
||||
abort(400, ErrFormat.not_support_operator.format(operator))
|
||||
|
||||
# 根据relation生成复合条件
|
||||
if relation == "&":
|
||||
return expr, []
|
||||
elif relation == "|":
|
||||
@@ -505,7 +340,6 @@ class EmployeeCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def check_condition(column, operator, value, relation):
|
||||
# 对于condition中column为空的,报错
|
||||
if column is None or operator is None or relation is None:
|
||||
return abort(400, ErrFormat.conditions_field_missing)
|
||||
|
||||
@@ -654,19 +488,6 @@ def get_user_map(key='uid', acl=None):
|
||||
return data
|
||||
|
||||
|
||||
acl_user_columns = [
|
||||
'email',
|
||||
'mobile',
|
||||
'nickname',
|
||||
'username',
|
||||
'password',
|
||||
'block',
|
||||
'avatar',
|
||||
]
|
||||
employee_pop_columns = ['password']
|
||||
can_not_edit_columns = ['email']
|
||||
|
||||
|
||||
def format_params(params):
|
||||
for k in ['_key', '_secret']:
|
||||
params.pop(k, None)
|
||||
@@ -676,19 +497,22 @@ def format_params(params):
|
||||
class CreateEmployee(object):
|
||||
def __init__(self):
|
||||
self.acl = ACLManager()
|
||||
self.useremail_map = {}
|
||||
self.all_acl_users = self.acl.get_all_users()
|
||||
|
||||
def check_acl_user(self, email):
|
||||
user_info = self.useremail_map.get(email, None)
|
||||
if user_info:
|
||||
return user_info
|
||||
return None
|
||||
def check_acl_user(self, user_data):
|
||||
target_email = list(filter(lambda x: x['email'] == user_data['email'], self.all_acl_users))
|
||||
if target_email:
|
||||
return target_email[0]
|
||||
|
||||
target_username = list(filter(lambda x: x['username'] == user_data['username'], self.all_acl_users))
|
||||
if target_username:
|
||||
return target_username[0]
|
||||
|
||||
def add_acl_user(self, **kwargs):
|
||||
user_data = {column: kwargs.get(
|
||||
column, '') for column in acl_user_columns if kwargs.get(column, '')}
|
||||
try:
|
||||
existed = self.check_acl_user(user_data['email'])
|
||||
existed = self.check_acl_user(user_data)
|
||||
if not existed:
|
||||
return self.acl.create_user(user_data)
|
||||
return existed
|
||||
@@ -697,8 +521,6 @@ class CreateEmployee(object):
|
||||
|
||||
def create_single(self, **kwargs):
|
||||
EmployeeCRUD.check_email_unique(kwargs['email'])
|
||||
self.useremail_map = self.useremail_map if self.useremail_map else get_user_map(
|
||||
'email', self.acl)
|
||||
user = self.add_acl_user(**kwargs)
|
||||
kwargs['acl_uid'] = user['uid']
|
||||
kwargs['last_login'] = user['last_login']
|
||||
@@ -711,8 +533,6 @@ class CreateEmployee(object):
|
||||
)
|
||||
|
||||
def create_single_with_import(self, **kwargs):
|
||||
self.useremail_map = self.useremail_map if self.useremail_map else get_user_map(
|
||||
'email', self.acl)
|
||||
user = self.add_acl_user(**kwargs)
|
||||
kwargs['acl_uid'] = user['uid']
|
||||
kwargs['last_login'] = user['last_login']
|
||||
@@ -755,9 +575,6 @@ class CreateEmployee(object):
|
||||
return end_d_id
|
||||
|
||||
def format_department_id(self, employee):
|
||||
"""
|
||||
部门名称转化为ID,不存在则创建
|
||||
"""
|
||||
department_name_map = {}
|
||||
try:
|
||||
department_name = employee.get('department_name', '')
|
||||
@@ -774,16 +591,13 @@ class CreateEmployee(object):
|
||||
|
||||
def batch_create(self, employee_list):
|
||||
err_list = []
|
||||
self.useremail_map = get_user_map('email', self.acl)
|
||||
|
||||
for employee in employee_list:
|
||||
try:
|
||||
# 获取username
|
||||
username = employee.get('username', None)
|
||||
if username is None:
|
||||
employee['username'] = employee['email']
|
||||
|
||||
# 校验通过后获取department_id
|
||||
employee = self.format_department_id(employee)
|
||||
err = employee.get('err', None)
|
||||
if err:
|
||||
@@ -795,7 +609,7 @@ class CreateEmployee(object):
|
||||
raise Exception(
|
||||
','.join(['{}: {}'.format(filed, ','.join(msg)) for filed, msg in form.errors.items()]))
|
||||
|
||||
data = self.create_single_with_import(**form.data)
|
||||
self.create_single_with_import(**form.data)
|
||||
except Exception as e:
|
||||
err_list.append({
|
||||
'email': employee.get('email', ''),
|
||||
@@ -809,12 +623,12 @@ class CreateEmployee(object):
|
||||
|
||||
class EmployeeAddForm(Form):
|
||||
username = StringField(validators=[
|
||||
validators.DataRequired(message="username不能为空"),
|
||||
validators.DataRequired(message=ErrFormat.username_is_required),
|
||||
validators.Length(max=255),
|
||||
])
|
||||
email = StringField(validators=[
|
||||
validators.DataRequired(message="邮箱不能为空"),
|
||||
validators.Email(message="邮箱格式不正确"),
|
||||
validators.DataRequired(message=ErrFormat.email_is_required),
|
||||
validators.Email(message=ErrFormat.email_format_error),
|
||||
validators.Length(max=255),
|
||||
])
|
||||
password = StringField(validators=[
|
||||
@@ -823,7 +637,7 @@ class EmployeeAddForm(Form):
|
||||
position_name = StringField(validators=[])
|
||||
|
||||
nickname = StringField(validators=[
|
||||
validators.DataRequired(message="用户名不能为空"),
|
||||
validators.DataRequired(message=ErrFormat.nickname_is_required),
|
||||
validators.Length(max=255),
|
||||
])
|
||||
sex = StringField(validators=[])
|
||||
@@ -834,7 +648,7 @@ class EmployeeAddForm(Form):
|
||||
|
||||
class EmployeeUpdateByUidForm(Form):
|
||||
nickname = StringField(validators=[
|
||||
validators.DataRequired(message="用户名不能为空"),
|
||||
validators.DataRequired(message=ErrFormat.nickname_is_required),
|
||||
validators.Length(max=255),
|
||||
])
|
||||
avatar = StringField(validators=[])
|
||||
|
@@ -49,3 +49,9 @@ class ErrFormat(CommonErrFormat):
|
||||
acl_add_user_to_role_failed = "ACL 添加用户到角色失败: {}"
|
||||
acl_import_user_failed = "ACL 导入用户[{}]失败: {}"
|
||||
|
||||
nickname_is_required = "用户名不能为空"
|
||||
username_is_required = "username不能为空"
|
||||
email_is_required = "邮箱不能为空"
|
||||
email_format_error = "邮箱格式错误"
|
||||
|
||||
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):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||
|
||||
|
||||
def generate_new_file_name(name):
|
||||
@@ -13,4 +12,5 @@ def generate_new_file_name(name):
|
||||
prev_name = ''.join(name.split(f".{ext}")[:-1])
|
||||
uid = str(uuid.uuid4())
|
||||
cur_str = get_cur_time_str('_')
|
||||
|
||||
return f"{prev_name}_{cur_str}_{uid}.{ext}"
|
||||
|
@@ -1,23 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import text
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
|
||||
def get_df_from_read_sql(query, to_dict=False):
|
||||
bind = query.session.bind
|
||||
query = query.statement.compile(dialect=bind.dialect if bind else None,
|
||||
compile_kwargs={"literal_binds": True}).string
|
||||
a = db.engine
|
||||
df = pd.read_sql(sql=text(query), con=a.connect())
|
||||
|
||||
if to_dict:
|
||||
return df.to_dict('records')
|
||||
return df
|
||||
|
||||
|
||||
def get_cur_time_str(split_flag='-'):
|
||||
f = f"%Y{split_flag}%m{split_flag}%d{split_flag}%H{split_flag}%M{split_flag}%S{split_flag}%f"
|
||||
|
@@ -80,10 +80,10 @@ class CRUDMixin(FormatMixin):
|
||||
db.session.rollback()
|
||||
raise CommitException(str(e))
|
||||
|
||||
def soft_delete(self, flush=False):
|
||||
def soft_delete(self, flush=False, commit=True):
|
||||
setattr(self, "deleted", True)
|
||||
setattr(self, "deleted_at", datetime.datetime.now())
|
||||
self.save(flush=flush)
|
||||
self.save(flush=flush, commit=commit)
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, _id):
|
||||
@@ -138,8 +138,11 @@ class CRUDMixin(FormatMixin):
|
||||
return result[0] if first and result else (None if first else result)
|
||||
|
||||
@classmethod
|
||||
def get_by_like(cls, to_dict=True, **kwargs):
|
||||
def get_by_like(cls, to_dict=True, deleted=False, **kwargs):
|
||||
query = db.session.query(cls)
|
||||
if hasattr(cls, "deleted") and deleted is not None:
|
||||
query = query.filter(cls.deleted.is_(deleted))
|
||||
|
||||
for k, v in kwargs.items():
|
||||
query = query.filter(getattr(cls, k).ilike('%{0}%'.format(v)))
|
||||
return [i.to_dict() if to_dict else i for i in query]
|
||||
|
@@ -55,8 +55,8 @@ def args_validate(model_cls, exclude_args=None):
|
||||
if exclude_args and arg in exclude_args:
|
||||
continue
|
||||
|
||||
if attr.type.python_type == str and attr.type.length and \
|
||||
len(request.values[arg] or '') > attr.type.length:
|
||||
if attr.type.python_type == str and attr.type.length and (
|
||||
len(request.values[arg] or '') > attr.type.length):
|
||||
|
||||
return abort(400, CommonErrFormat.argument_str_length_limit.format(arg, attr.type.length))
|
||||
elif attr.type.python_type in (int, float) and request.values[arg]:
|
||||
|
@@ -4,21 +4,22 @@
|
||||
import hashlib
|
||||
|
||||
import requests
|
||||
from future.moves.urllib.parse import urlparse
|
||||
from flask import abort
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
from future.moves.urllib.parse import urlparse
|
||||
|
||||
|
||||
def build_api_key(path, params):
|
||||
g.user is not None or abort(403, u"您得登陆才能进行该操作")
|
||||
key = g.user.key
|
||||
secret = g.user.secret
|
||||
current_user is not None or abort(403, u"您得登陆才能进行该操作")
|
||||
key = current_user.key
|
||||
secret = current_user.secret
|
||||
values = "".join([str(params[k]) for k in sorted(params.keys())
|
||||
if params[k] is not None]) if params.keys() else ""
|
||||
_secret = "".join([path, secret, values]).encode("utf-8")
|
||||
params["_secret"] = hashlib.sha1(_secret).hexdigest()
|
||||
params["_key"] = key
|
||||
|
||||
return params
|
||||
|
||||
|
||||
|
@@ -5,8 +5,11 @@ import hashlib
|
||||
|
||||
import requests
|
||||
import six
|
||||
from flask import current_app, g, request
|
||||
from flask import session, abort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
|
||||
from api.extensions import cache
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
@@ -84,8 +87,8 @@ class ACLManager(object):
|
||||
if user:
|
||||
return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False)
|
||||
|
||||
return Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or \
|
||||
Role.get_by(name=name, first=True, to_dict=False)
|
||||
return (Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False) or
|
||||
Role.get_by(name=name, first=True, to_dict=False))
|
||||
|
||||
def add_resource(self, name, resource_type_name=None):
|
||||
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
||||
@@ -154,9 +157,9 @@ class ACLManager(object):
|
||||
if is_app_admin(self.app_id):
|
||||
return True
|
||||
|
||||
role = self._get_role(g.user.username)
|
||||
role = self._get_role(current_user.username)
|
||||
|
||||
role or abort(404, ErrFormat.role_not_found.format(g.user.username))
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
|
||||
return RoleCRUD.has_permission(role.id, resource_name, resource_type, self.app_id, perm,
|
||||
resource_id=resource_id)
|
||||
@@ -193,9 +196,9 @@ class ACLManager(object):
|
||||
return user
|
||||
|
||||
def get_resources(self, resource_type_name=None):
|
||||
role = self._get_role(g.user.username)
|
||||
role = self._get_role(current_user.username)
|
||||
|
||||
role or abort(404, ErrFormat.role_not_found.format(g.user.username))
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
rid = role.id
|
||||
|
||||
return RoleCRUD.recursive_resources(rid, self.app_id, resource_type_name).get('resources')
|
||||
@@ -215,7 +218,7 @@ def validate_permission(resources, resource_type, perm, app=None):
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if g.user.username == "worker":
|
||||
if current_user.username == "worker":
|
||||
return
|
||||
|
||||
resources = [resources] if isinstance(resources, six.string_types) else resources
|
||||
@@ -313,7 +316,7 @@ def role_required(role_name, app=None):
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if getattr(g.user, 'username', None) == "worker":
|
||||
if getattr(current_user, 'username', None) == "worker":
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app):
|
||||
|
@@ -8,7 +8,9 @@ from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
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.models.acl import App
|
||||
|
||||
|
@@ -4,13 +4,21 @@ import json
|
||||
from enum import Enum
|
||||
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 sqlalchemy import func
|
||||
|
||||
from api.lib.perm.acl import AppCache
|
||||
from api.models.acl import AuditRoleLog, AuditResourceLog, AuditPermissionLog, AuditTriggerLog, RolePermission, \
|
||||
Resource, ResourceGroup, Permission, Role, ResourceType
|
||||
from api.models.acl import AuditPermissionLog
|
||||
from api.models.acl import AuditResourceLog
|
||||
from api.models.acl import AuditRoleLog
|
||||
from api.models.acl import AuditTriggerLog
|
||||
from api.models.acl import Permission
|
||||
from api.models.acl import Resource
|
||||
from api.models.acl import ResourceGroup
|
||||
from api.models.acl import ResourceType
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import RolePermission
|
||||
|
||||
|
||||
class AuditScope(str, Enum):
|
||||
@@ -49,9 +57,7 @@ class AuditCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def get_current_operate_uid(uid=None):
|
||||
|
||||
user_id = uid or (hasattr(g, 'user') and getattr(g.user, 'uid', None)) \
|
||||
or getattr(current_user, 'user_id', 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'):
|
||||
_user_id = request.headers['X-User-Id']
|
||||
@@ -93,11 +99,8 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditPermissionLog.operate_type == v)
|
||||
|
||||
records = AuditPermissionLog.query.filter(
|
||||
AuditPermissionLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditPermissionLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
AuditPermissionLog.deleted == 0, *criterion).order_by(
|
||||
AuditPermissionLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -160,10 +163,8 @@ class AuditCRUD(object):
|
||||
elif k == 'operate_type':
|
||||
criterion.append(AuditRoleLog.operate_type == v)
|
||||
|
||||
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion) \
|
||||
.order_by(AuditRoleLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion).order_by(
|
||||
AuditRoleLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -225,11 +226,8 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditResourceLog.operate_type == v)
|
||||
|
||||
records = AuditResourceLog.query.filter(
|
||||
AuditResourceLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditResourceLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
AuditResourceLog.deleted == 0, *criterion).order_by(
|
||||
AuditResourceLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
@@ -259,11 +257,8 @@ class AuditCRUD(object):
|
||||
criterion.append(AuditTriggerLog.operate_type == v)
|
||||
|
||||
records = AuditTriggerLog.query.filter(
|
||||
AuditTriggerLog.deleted == 0,
|
||||
*criterion) \
|
||||
.order_by(AuditTriggerLog.id.desc()) \
|
||||
.offset((page - 1) * page_size) \
|
||||
.limit(page_size).all()
|
||||
AuditTriggerLog.deleted == 0, *criterion).order_by(
|
||||
AuditTriggerLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
data = {
|
||||
'data': [r.to_dict() for r in records],
|
||||
|
@@ -60,15 +60,15 @@ class UserCache(object):
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
user = cache.get(cls.PREFIX_ID.format(key)) or \
|
||||
cache.get(cls.PREFIX_NAME.format(key)) or \
|
||||
cache.get(cls.PREFIX_NICK.format(key)) or \
|
||||
cache.get(cls.PREFIX_WXID.format(key))
|
||||
user = (cache.get(cls.PREFIX_ID.format(key)) or
|
||||
cache.get(cls.PREFIX_NAME.format(key)) or
|
||||
cache.get(cls.PREFIX_NICK.format(key)) or
|
||||
cache.get(cls.PREFIX_WXID.format(key)))
|
||||
if not user:
|
||||
user = User.query.get(key) or \
|
||||
User.query.get_by_username(key) or \
|
||||
User.query.get_by_nickname(key) or \
|
||||
User.query.get_by_wxid(key)
|
||||
user = (User.query.get(key) or
|
||||
User.query.get_by_username(key) or
|
||||
User.query.get_by_nickname(key) or
|
||||
User.query.get_by_wxid(key))
|
||||
if user:
|
||||
cls.set(user)
|
||||
|
||||
|
@@ -4,7 +4,9 @@ import datetime
|
||||
from flask import abort
|
||||
|
||||
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 RoleCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -97,8 +99,8 @@ class PermissionCRUD(object):
|
||||
elif group_id is not None:
|
||||
from api.models.acl import ResourceGroup
|
||||
|
||||
group = ResourceGroup.get_by_id(group_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
app_id = group.app_id
|
||||
rt_id = group.resource_type_id
|
||||
if not perms:
|
||||
@@ -206,8 +208,8 @@ class PermissionCRUD(object):
|
||||
if resource_id is not None:
|
||||
from api.models.acl import Resource
|
||||
|
||||
resource = Resource.get_by_id(resource_id) or \
|
||||
abort(404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
||||
resource = Resource.get_by_id(resource_id) or abort(
|
||||
404, ErrFormat.resource_not_found.format("id={}".format(resource_id)))
|
||||
app_id = resource.app_id
|
||||
rt_id = resource.resource_type_id
|
||||
if not perms:
|
||||
@@ -216,8 +218,8 @@ class PermissionCRUD(object):
|
||||
elif group_id is not None:
|
||||
from api.models.acl import ResourceGroup
|
||||
|
||||
group = ResourceGroup.get_by_id(group_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
group = ResourceGroup.get_by_id(group_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(group_id)))
|
||||
app_id = group.app_id
|
||||
|
||||
rt_id = group.resource_type_id
|
||||
|
@@ -5,7 +5,9 @@ from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
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 ResourceGroupCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
@@ -102,8 +104,8 @@ class ResourceTypeCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def delete(cls, rt_id):
|
||||
rt = ResourceType.get_by_id(rt_id) or \
|
||||
abort(404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
|
||||
rt = ResourceType.get_by_id(rt_id) or abort(
|
||||
404, ErrFormat.resource_type_not_found.format("id={}".format(rt_id)))
|
||||
|
||||
Resource.get_by(resource_type_id=rt_id) and abort(400, ErrFormat.resource_type_cannot_delete)
|
||||
|
||||
@@ -165,8 +167,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def add(name, type_id, app_id, uid=None):
|
||||
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \
|
||||
abort(400, ErrFormat.resource_group_exists.format(name))
|
||||
ResourceGroup.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||
400, ErrFormat.resource_group_exists.format(name))
|
||||
rg = ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||
|
||||
AuditCRUD.add_resource_log(app_id, AuditOperateType.create,
|
||||
@@ -175,8 +177,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def update(rg_id, items):
|
||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
|
||||
existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False)
|
||||
existed_ids = [i.resource_id for i in existed]
|
||||
@@ -196,8 +198,8 @@ class ResourceGroupCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(rg_id):
|
||||
rg = ResourceGroup.get_by_id(rg_id) or \
|
||||
abort(404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
rg = ResourceGroup.get_by_id(rg_id) or abort(
|
||||
404, ErrFormat.resource_group_not_found.format("id={}".format(rg_id)))
|
||||
|
||||
origin = rg.to_dict()
|
||||
rg.soft_delete()
|
||||
@@ -266,8 +268,8 @@ class ResourceCRUD(object):
|
||||
def add(cls, name, type_id, app_id, uid=None):
|
||||
type_id = cls._parse_resource_type_id(type_id, app_id)
|
||||
|
||||
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and \
|
||||
abort(400, ErrFormat.resource_exists.format(name))
|
||||
Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) and abort(
|
||||
400, ErrFormat.resource_exists.format(name))
|
||||
|
||||
r = Resource.create(name=name, resource_type_id=type_id, app_id=app_id, uid=uid)
|
||||
|
||||
|
@@ -17,6 +17,7 @@ class ErrFormat(CommonErrFormat):
|
||||
role_exists = "角色 {} 已经存在!"
|
||||
global_role_not_found = "全局角色 {} 不存在!"
|
||||
global_role_exists = "全局角色 {} 已经存在!"
|
||||
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
|
||||
|
||||
resource_no_permission = "您没有资源: {} 的 {} 权限"
|
||||
admin_required = "需要管理员权限"
|
||||
|
@@ -6,10 +6,13 @@ import time
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.app import AppCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, 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 HasResourceRoleCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
@@ -68,16 +71,16 @@ class RoleRelationCRUD(object):
|
||||
@staticmethod
|
||||
def get_parent_ids(rid, app_id):
|
||||
if app_id is not None:
|
||||
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] + \
|
||||
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)]
|
||||
return ([i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)] +
|
||||
[i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=None, to_dict=False)])
|
||||
else:
|
||||
return [i.parent_id for i in RoleRelation.get_by(child_id=rid, app_id=app_id, to_dict=False)]
|
||||
|
||||
@staticmethod
|
||||
def get_child_ids(rid, app_id):
|
||||
if app_id is not None:
|
||||
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] + \
|
||||
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)]
|
||||
return ([i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)] +
|
||||
[i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=None, to_dict=False)])
|
||||
else:
|
||||
return [i.child_id for i in RoleRelation.get_by(parent_id=rid, app_id=app_id, to_dict=False)]
|
||||
|
||||
@@ -212,18 +215,16 @@ class RoleCRUD(object):
|
||||
|
||||
@staticmethod
|
||||
def search(q, app_id, page=1, page_size=None, user_role=True, is_all=False, user_only=False):
|
||||
query = db.session.query(Role).filter(Role.deleted.is_(False))
|
||||
query1 = query.filter(Role.app_id == app_id).filter(Role.uid.is_(None))
|
||||
query2 = query.filter(Role.app_id.is_(None)).filter(Role.uid.is_(None))
|
||||
query = query1.union(query2)
|
||||
|
||||
if user_role:
|
||||
query1 = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.isnot(None))
|
||||
query = query.union(query1)
|
||||
|
||||
if user_only:
|
||||
if user_only: # only user role
|
||||
query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.isnot(None))
|
||||
|
||||
else:
|
||||
query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(
|
||||
or_(Role.app_id == app_id, Role.app_id.is_(None)))
|
||||
if not user_role: # only virtual role
|
||||
query = query.filter(Role.uid.is_(None))
|
||||
|
||||
if not is_all:
|
||||
role_ids = list(HasResourceRoleCache.get(app_id).keys())
|
||||
query = query.filter(Role.id.in_(role_ids))
|
||||
@@ -286,11 +287,13 @@ class RoleCRUD(object):
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
def delete_role(cls, rid):
|
||||
def delete_role(cls, rid, force=False):
|
||||
from api.lib.perm.acl.acl import is_admin
|
||||
|
||||
role = Role.get_by_id(rid) or abort(404, ErrFormat.role_not_found.format("rid={}".format(rid)))
|
||||
|
||||
not force and role.uid and abort(400, ErrFormat.user_role_delete_invalid)
|
||||
|
||||
if not role.app_id and not is_admin():
|
||||
return abort(403, ErrFormat.admin_required)
|
||||
|
||||
@@ -302,18 +305,20 @@ class RoleCRUD(object):
|
||||
|
||||
for i in RoleRelation.get_by(parent_id=rid, to_dict=False):
|
||||
child_ids.append(i.child_id)
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
|
||||
for i in RoleRelation.get_by(child_id=rid, to_dict=False):
|
||||
parent_ids.append(i.parent_id)
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
|
||||
role_permissions = []
|
||||
for i in RolePermission.get_by(rid=rid, to_dict=False):
|
||||
role_permissions.append(i.to_dict())
|
||||
i.soft_delete()
|
||||
i.soft_delete(commit=False)
|
||||
|
||||
role.soft_delete()
|
||||
role.soft_delete(commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
role_rebuild.apply_async(args=(recursive_child_ids, role.app_id), queue=ACL_QUEUE)
|
||||
|
||||
|
@@ -6,9 +6,10 @@ import json
|
||||
import re
|
||||
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.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
@@ -6,10 +6,12 @@ import string
|
||||
import uuid
|
||||
|
||||
from flask import abort
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
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.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
@@ -39,18 +41,19 @@ class UserCRUD(object):
|
||||
|
||||
@classmethod
|
||||
def add(cls, **kwargs):
|
||||
existed = User.get_by(username=kwargs['username'], email=kwargs['email'])
|
||||
existed = User.get_by(username=kwargs['username'])
|
||||
existed and abort(400, ErrFormat.user_exists.format(kwargs['username']))
|
||||
|
||||
existed = User.get_by(username=kwargs['email'])
|
||||
existed and abort(400, ErrFormat.user_exists.format(kwargs['email']))
|
||||
|
||||
kwargs['nickname'] = kwargs.get('nickname') or kwargs['username']
|
||||
kwargs['block'] = 0
|
||||
kwargs['key'], kwargs['secret'] = cls.gen_key_secret()
|
||||
|
||||
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(
|
||||
User.employee_id.desc()).first()
|
||||
user_employee = db.session.query(User).filter(User.deleted.is_(False)).order_by(User.employee_id.desc()).first()
|
||||
|
||||
biggest_employee_id = int(float(user_employee.employee_id)) \
|
||||
if user_employee is not None else 0
|
||||
biggest_employee_id = int(float(user_employee.employee_id)) if user_employee is not None else 0
|
||||
|
||||
kwargs['employee_id'] = '{0:04d}'.format(biggest_employee_id + 1)
|
||||
user = User.create(**kwargs)
|
||||
@@ -90,9 +93,9 @@ class UserCRUD(object):
|
||||
@classmethod
|
||||
def reset_key_secret(cls):
|
||||
key, secret = cls.gen_key_secret()
|
||||
g.user.update(key=key, secret=secret)
|
||||
current_user.update(key=key, secret=secret)
|
||||
|
||||
UserCache.clean(g.user)
|
||||
UserCache.clean(current_user)
|
||||
|
||||
return key, secret
|
||||
|
||||
@@ -103,10 +106,14 @@ class UserCRUD(object):
|
||||
|
||||
origin = user.to_dict()
|
||||
|
||||
user.soft_delete()
|
||||
user.delete()
|
||||
|
||||
UserCache.clean(user)
|
||||
|
||||
role = RoleCRUD.get_by_name(user.username, app_id=None)
|
||||
if role:
|
||||
RoleCRUD.delete_role(role[0]['id'], force=True)
|
||||
|
||||
AuditCRUD.add_role_log(None, AuditOperateType.delete,
|
||||
AuditScope.user, user.uid, origin, {}, {}, {})
|
||||
|
||||
|
@@ -7,7 +7,6 @@ from functools import wraps
|
||||
import jwt
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import login_user
|
||||
@@ -64,12 +63,10 @@ def _auth_with_key():
|
||||
|
||||
|
||||
def _auth_with_session():
|
||||
if isinstance(getattr(g, 'user', None), User):
|
||||
login_user(g.user)
|
||||
return True
|
||||
if "acl" in session and "userName" in (session["acl"] or {}):
|
||||
login_user(UserCache.get(session["acl"]["userName"]))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -108,7 +105,7 @@ def _auth_with_ip_white_list():
|
||||
|
||||
|
||||
def _auth_with_app_token():
|
||||
if _auth_with_session():
|
||||
if _auth_with_session() or _auth_with_token():
|
||||
if not is_app_admin(request.values.get('app_id')) and request.method != "GET":
|
||||
return False
|
||||
elif is_app_admin(request.values.get('app_id')):
|
||||
@@ -157,7 +154,7 @@ def _auth_with_acl_token():
|
||||
|
||||
|
||||
def auth_required(func):
|
||||
if request.json is not None:
|
||||
if request.get_json(silent=True) is not None:
|
||||
setattr(request, 'values', request.json)
|
||||
else:
|
||||
setattr(request, 'values', request.values.to_dict())
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from typing import Set
|
||||
@@ -113,7 +112,7 @@ class RedisHandler(object):
|
||||
try:
|
||||
ret = self.r.hdel(prefix, key_id)
|
||||
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:
|
||||
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)
|
||||
if res['hits'].get('hits'):
|
||||
return res['hits']['total']['value'], \
|
||||
[i['_source'] for i in res['hits']['hits']], \
|
||||
res.get("aggregations", {})
|
||||
return (res['hits']['total']['value'],
|
||||
[i['_source'] for i in res['hits']['hits']],
|
||||
res.get("aggregations", {}))
|
||||
else:
|
||||
return 0, [], {}
|
||||
|
||||
@@ -257,93 +256,10 @@ class Lock(object):
|
||||
self.release()
|
||||
|
||||
|
||||
class Redis2Handler(object):
|
||||
def __init__(self, flask_app=None, prefix=None):
|
||||
self.flask_app = flask_app
|
||||
self.prefix = prefix
|
||||
self.r = None
|
||||
|
||||
def init_app(self, app):
|
||||
self.flask_app = app
|
||||
config = self.flask_app.config
|
||||
try:
|
||||
pool = redis.ConnectionPool(
|
||||
max_connections=config.get("REDIS_MAX_CONN"),
|
||||
host=config.get("ONEAGENT_REDIS_HOST"),
|
||||
port=config.get("ONEAGENT_REDIS_PORT"),
|
||||
db=config.get("ONEAGENT_REDIS_DB"),
|
||||
password=config.get("ONEAGENT_REDIS_PASSWORD")
|
||||
)
|
||||
self.r = redis.Redis(connection_pool=pool)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
current_app.logger.error("init redis connection failed")
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
value = json.loads(self.r.get(key))
|
||||
except:
|
||||
return
|
||||
|
||||
return value
|
||||
|
||||
def lrange(self, key, start=0, end=-1):
|
||||
try:
|
||||
value = "".join(map(redis_decode, self.r.lrange(key, start, end) or []))
|
||||
except:
|
||||
return
|
||||
|
||||
return value
|
||||
|
||||
def lrange2(self, key, start=0, end=-1):
|
||||
try:
|
||||
return list(map(redis_decode, self.r.lrange(key, start, end) or []))
|
||||
except:
|
||||
return []
|
||||
|
||||
def llen(self, key):
|
||||
try:
|
||||
return self.r.llen(key) or 0
|
||||
except:
|
||||
return 0
|
||||
|
||||
def hget(self, key, field):
|
||||
try:
|
||||
return self.r.hget(key, field)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("hget redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
def hset(self, key, field, value):
|
||||
try:
|
||||
self.r.hset(key, field, value)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("hset redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
def expire(self, key, timeout):
|
||||
try:
|
||||
self.r.expire(key, timeout)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("expire redis failed, %s" % str(e))
|
||||
return
|
||||
|
||||
|
||||
def redis_decode(x):
|
||||
try:
|
||||
return x.decode()
|
||||
except Exception as e:
|
||||
print(x, e)
|
||||
try:
|
||||
return x.decode("gb18030")
|
||||
except:
|
||||
return "decode failed"
|
||||
|
||||
|
||||
class AESCrypto(object):
|
||||
BLOCK_SIZE = 16 # Bytes
|
||||
pad = lambda s: s + (AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) * \
|
||||
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE)
|
||||
pad = lambda s: s + ((AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE) *
|
||||
chr(AESCrypto.BLOCK_SIZE - len(s) % AESCrypto.BLOCK_SIZE))
|
||||
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
|
||||
|
||||
iv = '0102030405060708'
|
||||
@@ -352,7 +268,7 @@ class AESCrypto(object):
|
||||
def key():
|
||||
key = current_app.config.get("SECRET_KEY")[:16]
|
||||
if len(key) < 16:
|
||||
key = "{}{}".format(key, (16 - len(key) * "x"))
|
||||
key = "{}{}".format(key, (16 - len(key)) * "x")
|
||||
|
||||
return key.encode('utf8')
|
||||
|
||||
|
@@ -62,10 +62,10 @@ class UserQuery(BaseQuery):
|
||||
ldap_conn.set_option(ldap.OPT_REFERRALS, 0)
|
||||
if '@' in username:
|
||||
email = username
|
||||
who = '{0}@{1}'.format(username.split('@')[0], current_app.config.get('LDAP_DOMAIN'))
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
|
||||
else:
|
||||
who = '{0}@{1}'.format(username, current_app.config.get('LDAP_DOMAIN'))
|
||||
email = who
|
||||
who = current_app.config.get('LDAP_USER_DN').format(username)
|
||||
email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))
|
||||
|
||||
username = username.split('@')[0]
|
||||
user = self.get_by_username(username)
|
||||
|
@@ -250,6 +250,9 @@ class CIIndexValueDateTime(Model):
|
||||
|
||||
|
||||
class CIValueInteger(Model):
|
||||
"""
|
||||
Deprecated in a future version
|
||||
"""
|
||||
__tablename__ = "c_value_integers"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
@@ -261,6 +264,9 @@ class CIValueInteger(Model):
|
||||
|
||||
|
||||
class CIValueFloat(Model):
|
||||
"""
|
||||
Deprecated in a future version
|
||||
"""
|
||||
__tablename__ = "c_value_floats"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
@@ -283,6 +289,9 @@ class CIValueText(Model):
|
||||
|
||||
|
||||
class CIValueDateTime(Model):
|
||||
"""
|
||||
Deprecated in a future version
|
||||
"""
|
||||
__tablename__ = "c_value_datetime"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
|
@@ -13,40 +13,39 @@ class Department(ModelWithoutPK):
|
||||
__tablename__ = 'common_department'
|
||||
department_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
department_name = db.Column(db.VARCHAR(255), default='', comment='部门名称')
|
||||
department_name = db.Column(db.VARCHAR(255), default='')
|
||||
department_director_id = db.Column(
|
||||
db.Integer, default=0, comment='部门负责人ID')
|
||||
department_parent_id = db.Column(db.Integer, default=1, comment='上级部门ID')
|
||||
db.Integer, default=0)
|
||||
department_parent_id = db.Column(db.Integer, default=1)
|
||||
|
||||
sort_value = db.Column(db.Integer, default=0, comment='排序值')
|
||||
sort_value = db.Column(db.Integer, default=0)
|
||||
|
||||
acl_rid = db.Column(db.Integer, comment='ACL中rid', default=0)
|
||||
acl_rid = db.Column(db.Integer, default=0)
|
||||
|
||||
|
||||
class Employee(ModelWithoutPK):
|
||||
__tablename__ = 'common_employee'
|
||||
employee_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
email = db.Column(db.VARCHAR(255), default='', comment='邮箱')
|
||||
username = db.Column(db.VARCHAR(255), default='', comment='用户名')
|
||||
nickname = db.Column(db.VARCHAR(255), default='', comment='姓名')
|
||||
sex = db.Column(db.VARCHAR(64), default='', comment='性别')
|
||||
position_name = db.Column(db.VARCHAR(255), default='', comment='职位名称')
|
||||
mobile = db.Column(db.VARCHAR(255), default='', comment='电话号码')
|
||||
avatar = db.Column(db.VARCHAR(255), default='', comment='头像')
|
||||
email = db.Column(db.VARCHAR(255), default='')
|
||||
username = db.Column(db.VARCHAR(255), default='')
|
||||
nickname = db.Column(db.VARCHAR(255), default='')
|
||||
sex = db.Column(db.VARCHAR(64), default='')
|
||||
position_name = db.Column(db.VARCHAR(255), default='')
|
||||
mobile = db.Column(db.VARCHAR(255), default='')
|
||||
avatar = db.Column(db.VARCHAR(255), default='')
|
||||
|
||||
direct_supervisor_id = db.Column(db.Integer, default=0, comment='直接上级ID')
|
||||
direct_supervisor_id = db.Column(db.Integer, default=0)
|
||||
|
||||
department_id = db.Column(db.Integer,
|
||||
db.ForeignKey('common_department.department_id'),
|
||||
comment='部门ID',
|
||||
db.ForeignKey('common_department.department_id')
|
||||
)
|
||||
|
||||
acl_uid = db.Column(db.Integer, comment='ACL中uid', default=0)
|
||||
acl_rid = db.Column(db.Integer, comment='ACL中rid', default=0)
|
||||
acl_virtual_rid = db.Column(db.Integer, comment='ACL中虚拟角色rid', default=0)
|
||||
last_login = db.Column(db.TIMESTAMP, nullable=True, comment='上次登录时间')
|
||||
block = db.Column(db.Integer, comment='锁定状态', default=0)
|
||||
acl_uid = db.Column(db.Integer, default=0)
|
||||
acl_rid = db.Column(db.Integer, default=0)
|
||||
acl_virtual_rid = db.Column(db.Integer, default=0)
|
||||
last_login = db.Column(db.TIMESTAMP, nullable=True)
|
||||
block = db.Column(db.Integer, default=0)
|
||||
|
||||
_department = db.relationship(
|
||||
'Department', backref='common_employee.department_id',
|
||||
@@ -55,14 +54,11 @@ class Employee(ModelWithoutPK):
|
||||
|
||||
|
||||
class EmployeeInfo(Model):
|
||||
"""
|
||||
员工信息
|
||||
"""
|
||||
__tablename__ = 'common_employee_info'
|
||||
|
||||
info = db.Column(db.JSON, default={}, comment='员工信息')
|
||||
info = db.Column(db.JSON, default={})
|
||||
employee_id = db.Column(db.Integer, db.ForeignKey(
|
||||
'common_employee.employee_id'), comment='员工ID')
|
||||
'common_employee.employee_id'))
|
||||
employee = db.relationship(
|
||||
'Employee', backref='common_employee.employee_id', lazy='joined')
|
||||
|
||||
@@ -74,16 +70,20 @@ class CompanyInfo(Model):
|
||||
|
||||
|
||||
class InternalMessage(Model):
|
||||
"""
|
||||
内部消息
|
||||
"""
|
||||
__tablename__ = "common_internal_message"
|
||||
|
||||
title = db.Column(db.VARCHAR(255), nullable=True, comment='标题')
|
||||
content = db.Column(db.TEXT, nullable=True, comment='内容')
|
||||
path = db.Column(db.VARCHAR(255), nullable=True, comment='跳转路径')
|
||||
is_read = db.Column(db.Boolean, default=False, comment='是否已读')
|
||||
app_name = db.Column(db.VARCHAR(128), nullable=False, comment='应用名称')
|
||||
category = db.Column(db.VARCHAR(128), nullable=False, comment='分类')
|
||||
message_data = db.Column(db.JSON, nullable=True, comment='数据')
|
||||
title = db.Column(db.VARCHAR(255), nullable=True)
|
||||
content = db.Column(db.TEXT, nullable=True)
|
||||
path = db.Column(db.VARCHAR(255), nullable=True)
|
||||
is_read = db.Column(db.Boolean, default=False)
|
||||
app_name = db.Column(db.VARCHAR(128), nullable=False)
|
||||
category = db.Column(db.VARCHAR(128), nullable=False)
|
||||
message_data = db.Column(db.JSON, nullable=True)
|
||||
employee_id = db.Column(db.Integer, db.ForeignKey('common_employee.employee_id'), comment='ID')
|
||||
|
||||
|
||||
class CommonData(Model):
|
||||
__table_name__ = 'common_data'
|
||||
|
||||
data_type = db.Column(db.VARCHAR(255), default='')
|
||||
data = db.Column(db.JSON)
|
||||
|
@@ -2,7 +2,8 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from inspect import getmembers, isclass
|
||||
from inspect import getmembers
|
||||
from inspect import isclass
|
||||
|
||||
import six
|
||||
from flask import jsonify
|
||||
@@ -27,16 +28,15 @@ class APIView(Resource):
|
||||
return send_file(*args, **kwargs)
|
||||
|
||||
|
||||
API_PACKAGE = "api"
|
||||
API_PACKAGE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def register_resources(resource_path, rest_api):
|
||||
for root, _, files in os.walk(os.path.join(resource_path)):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("views"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
if root not in sys.path:
|
||||
sys.path.insert(1, root)
|
||||
view = __import__(os.path.splitext(filename)[0])
|
||||
resource_list = [o[0] for o in getmembers(view) if isclass(o[1]) and issubclass(o[1], Resource)]
|
||||
resource_list = [i for i in resource_list if i != "APIView"]
|
||||
|
@@ -5,17 +5,20 @@ import re
|
||||
|
||||
from celery_once import QueueOnce
|
||||
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 db
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.audit import AuditOperateSource
|
||||
from api.lib.perm.acl.audit import AuditOperateType
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import RoleRelationCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.const import ACL_QUEUE
|
||||
from api.lib.perm.acl.record import OperateRecordCRUD
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditOperateSource
|
||||
from api.models.acl import Resource
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import Trigger
|
||||
|
@@ -7,6 +7,7 @@ import time
|
||||
import jinja2
|
||||
import requests
|
||||
from flask import current_app
|
||||
from flask_login import login_user
|
||||
|
||||
import api.lib.cmdb.ci
|
||||
from api.extensions import celery
|
||||
@@ -18,8 +19,12 @@ from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.mail import send_mail
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
||||
@@ -84,6 +89,51 @@ def ci_relation_cache(parent_id, child_id):
|
||||
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_add", queue=CMDB_QUEUE)
|
||||
def ci_relation_add(parent_dict, child_id, uid):
|
||||
"""
|
||||
:param parent_dict: key is '$parent_model.attr_name'
|
||||
:param child_id:
|
||||
:param uid:
|
||||
:return:
|
||||
"""
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
db.session.remove()
|
||||
|
||||
for parent in parent_dict:
|
||||
parent_ci_type_name, _attr_name = parent.strip()[1:].split('.', 1)
|
||||
attr_name = CITypeAttributeManager.get_attr_name(parent_ci_type_name, _attr_name)
|
||||
if attr_name is None:
|
||||
current_app.logger.warning("attr name {} does not exist".format(_attr_name))
|
||||
continue
|
||||
|
||||
parent_dict[parent] = handle_arg_list(parent_dict[parent])
|
||||
for v in parent_dict[parent]:
|
||||
query = "_type:{},{}:{}".format(parent_ci_type_name, attr_name, v)
|
||||
s = search(query)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.error('ci relation add failed: {}'.format(e))
|
||||
continue
|
||||
|
||||
for ci in response:
|
||||
try:
|
||||
CIRelationManager.add(ci['_id'], child_id)
|
||||
ci_relation_cache(ci['_id'], child_id)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(e)
|
||||
finally:
|
||||
db.session.remove()
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
def ci_relation_delete(parent_id, child_id):
|
||||
with Lock("CIRelation_{}".format(parent_id)):
|
||||
@@ -156,3 +206,18 @@ def trigger_notify(notify, ci_id):
|
||||
for i in notify['mail_to'] if i], subject, body)
|
||||
except Exception as e:
|
||||
current_app.logger.error("Send mail failed: {0}".format(str(e)))
|
||||
|
||||
|
||||
@celery.task(name="cmdb.calc_computed_attribute", queue=CMDB_QUEUE)
|
||||
def calc_computed_attribute(attr_id, uid):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
db.session.remove()
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
|
||||
cis = CI.get_by(type_id=i.type_id, to_dict=False)
|
||||
for ci in cis:
|
||||
CIManager.update(ci.id, {})
|
||||
|
@@ -13,13 +13,9 @@ from api.models.common_setting import Department
|
||||
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=COMMON_SETTING_QUEUE)
|
||||
def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
"""
|
||||
在 ACL 员工更换部门
|
||||
:param e_list: 员工列表 {acl_rid: 11, department_id: 22}
|
||||
:param new_d_id: 新部门 ID
|
||||
:param op_uid: 操作人 ID
|
||||
|
||||
在老部门中删除员工
|
||||
在新部门中添加员工
|
||||
:param e_list:{acl_rid: 11, department_id: 22}
|
||||
:param new_d_id
|
||||
:param op_uid
|
||||
"""
|
||||
db.session.remove()
|
||||
|
||||
@@ -43,7 +39,6 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else new_d_rid_in_acl
|
||||
|
||||
for employee in e_list:
|
||||
# 根据 部门ID获取部门 acl_rid
|
||||
old_department = Department.get_by(
|
||||
first=True, department_id=employee.get('department_id'), to_dict=False)
|
||||
if not old_department:
|
||||
@@ -61,7 +56,6 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
acl_rid=old_d_rid_in_acl
|
||||
)
|
||||
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
|
||||
# 在老部门中删除员工
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'parent_id': d_acl_rid,
|
||||
@@ -71,7 +65,6 @@ def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
|
||||
except Exception as e:
|
||||
result.append(ErrFormat.acl_remove_user_from_role_failed.format(str(e)))
|
||||
|
||||
# 在新部门中添加员工
|
||||
payload = {
|
||||
'app_id': 'acl',
|
||||
'child_ids': [employee_acl_rid],
|
||||
|
@@ -2,12 +2,13 @@
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
import jwt
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
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.perm.acl.cache import User
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
@@ -103,8 +103,8 @@ class ResourceView(APIView):
|
||||
type_id = request.values.get('type_id')
|
||||
app_id = request.values.get('app_id')
|
||||
uid = request.values.get('uid')
|
||||
if not uid and hasattr(g, "user") and hasattr(g.user, "uid"):
|
||||
uid = g.user.uid
|
||||
if not uid and hasattr(current_user, "uid"):
|
||||
uid = current_user.uid
|
||||
|
||||
resource = ResourceCRUD.add(name, type_id, app_id, uid)
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
@@ -31,12 +31,9 @@ class RoleView(APIView):
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
q = request.values.get('q')
|
||||
app_id = request.values.get('app_id')
|
||||
is_all = request.values.get('is_all', True)
|
||||
is_all = True if is_all in current_app.config.get("BOOL_TRUE") else False
|
||||
user_role = request.values.get('user_role', True)
|
||||
user_only = request.values.get('user_only', False)
|
||||
user_role = True if user_role in current_app.config.get("BOOL_TRUE") else False
|
||||
user_only = True if user_only in current_app.config.get("BOOL_TRUE") else False
|
||||
is_all = request.values.get('is_all', True) in current_app.config.get("BOOL_TRUE")
|
||||
user_role = request.values.get('user_role', True) in current_app.config.get("BOOL_TRUE")
|
||||
user_only = request.values.get('user_only', False) in current_app.config.get("BOOL_TRUE")
|
||||
|
||||
numfound, roles = RoleCRUD.search(q, app_id, page, page_size, user_role, is_all, user_only)
|
||||
|
||||
@@ -160,8 +157,8 @@ class RoleHasPermissionView(APIView):
|
||||
@auth_with_app_token
|
||||
def get(self):
|
||||
if not request.values.get('rid'):
|
||||
role = RoleCache.get_by_name(None, g.user.username)
|
||||
role or abort(404, ErrFormat.role_not_found.format(g.user.username))
|
||||
role = RoleCache.get_by_name(None, current_user.username)
|
||||
role or abort(404, ErrFormat.role_not_found.format(current_user.username))
|
||||
else:
|
||||
role = RoleCache.get(int(request.values.get('rid')))
|
||||
|
||||
|
@@ -4,7 +4,6 @@
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
@@ -13,7 +12,6 @@ from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
@@ -116,7 +114,7 @@ class UserView(APIView):
|
||||
|
||||
@role_required("acl_admin")
|
||||
def delete(self, uid):
|
||||
if g.user.uid == uid:
|
||||
if current_user.uid == uid:
|
||||
return abort(400, ErrFormat.invalid_operation)
|
||||
UserCRUD.delete(uid)
|
||||
|
||||
@@ -162,8 +160,8 @@ class UserResetPasswordView(APIView):
|
||||
if app.name not in ('cas-server', 'acl'):
|
||||
return abort(403, ErrFormat.invalid_request)
|
||||
|
||||
elif hasattr(g, 'user'):
|
||||
if g.user.username != request.values['username']:
|
||||
elif hasattr(current_user, 'username'):
|
||||
if current_user.username != request.values['username']:
|
||||
return abort(403, ErrFormat.invalid_request)
|
||||
|
||||
else:
|
||||
|
@@ -33,7 +33,8 @@ class AttributeSearchView(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):
|
||||
attr_manager = AttributeManager()
|
||||
@@ -55,7 +56,12 @@ class AttributeView(APIView):
|
||||
|
||||
@args_required("name")
|
||||
@args_validate(AttributeManager.cls)
|
||||
def post(self):
|
||||
def post(self, attr_id=None):
|
||||
if request.url.endswith("/calc_computed_attribute"):
|
||||
AttributeManager.calc_computed_attribute(attr_id)
|
||||
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
choice_value = handle_arg_list(request.values.get("choice_value"))
|
||||
params = request.values
|
||||
params["choice_value"] = choice_value
|
||||
@@ -63,6 +69,7 @@ class AttributeView(APIView):
|
||||
current_app.logger.debug(params)
|
||||
|
||||
attr_id = AttributeManager.add(**params)
|
||||
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
@args_validate(AttributeManager.cls)
|
||||
@@ -72,8 +79,10 @@ class AttributeView(APIView):
|
||||
params["choice_value"] = choice_value
|
||||
current_app.logger.debug(params)
|
||||
AttributeManager().update(attr_id, **params)
|
||||
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
def delete(self, attr_id):
|
||||
attr_name = AttributeManager.delete(attr_id)
|
||||
|
||||
return self.jsonify(message="attribute {0} deleted".format(attr_name))
|
||||
|
@@ -5,8 +5,8 @@ from io import BytesIO
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCICRUD
|
||||
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
|
||||
@@ -75,9 +75,9 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
|
||||
|
||||
return self.send_file(bf,
|
||||
as_attachment=True,
|
||||
attachment_filename="cmdb_auto_discovery.json",
|
||||
download_name="cmdb_auto_discovery.json",
|
||||
mimetype='application/json',
|
||||
cache_timeout=0)
|
||||
max_age=0)
|
||||
|
||||
def post(self):
|
||||
f = request.files.get('file')
|
||||
@@ -119,7 +119,7 @@ class AutoDiscoveryCITypeView(APIView):
|
||||
_, res = AutoDiscoveryCITypeCRUD.search(page=1, page_size=100000, type_id=type_id, **request.values)
|
||||
for i in res:
|
||||
if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('secret'):
|
||||
if not (g.user.username == "cmdb_agent" or g.user.uid == i['uid']):
|
||||
if not (current_user.username == "cmdb_agent" or current_user.uid == i['uid']):
|
||||
i['extra_option'].pop('secret', None)
|
||||
else:
|
||||
i['extra_option']['secret'] = AESCrypto.decrypt(i['extra_option']['secret'])
|
||||
@@ -213,7 +213,7 @@ class AutoDiscoveryRuleSyncView(APIView):
|
||||
url_prefix = ("/adt/sync",)
|
||||
|
||||
def get(self):
|
||||
if g.user.username not in ("cmdb_agent", "worker", "admin"):
|
||||
if current_user.username not in ("cmdb_agent", "worker", "admin"):
|
||||
return abort(403)
|
||||
|
||||
oneagent_name = request.values.get('oneagent_name')
|
||||
|
@@ -11,7 +11,8 @@ from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import 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.perms import has_perm_for_ci
|
||||
from api.lib.cmdb.search import SearchError
|
||||
@@ -106,6 +107,7 @@ class CIView(APIView):
|
||||
_is_admin=request.values.pop('__is_admin', False),
|
||||
**ci_dict)
|
||||
else:
|
||||
request.values.pop('exist_policy', None)
|
||||
ci_id = manager.add(ci_type,
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
|
@@ -350,9 +350,9 @@ class CITypeTemplateFileView(APIView):
|
||||
|
||||
return self.send_file(bf,
|
||||
as_attachment=True,
|
||||
attachment_filename="cmdb_template.json",
|
||||
download_name="cmdb_template.json",
|
||||
mimetype='application/json',
|
||||
cache_timeout=0)
|
||||
max_age=0)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def post(self): # import
|
||||
|
@@ -6,7 +6,9 @@ from flask import request
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import PermEnum, 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.decorator import args_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
|
@@ -7,7 +7,8 @@ from flask import abort
|
||||
from flask import request
|
||||
|
||||
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.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
|
@@ -5,7 +5,9 @@ from flask import abort
|
||||
from flask import request
|
||||
|
||||
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.preference import PreferenceManager
|
||||
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())
|
||||
|
||||
def post(self):
|
||||
info = CompanyInfoCRUD.get()
|
||||
if info:
|
||||
abort(400, ErrFormat.company_info_is_already_existed)
|
||||
data = {
|
||||
'info': {
|
||||
**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()
|
||||
return self.jsonify(res)
|
||||
|
||||
|
@@ -100,7 +100,7 @@ class DepartmentSortView(APIView):
|
||||
|
||||
def put(self):
|
||||
"""
|
||||
修改部门排序,只能在同一个上级内排序
|
||||
only can sort in the same parent
|
||||
"""
|
||||
department_list = request.json.get('department_list', None)
|
||||
if department_list is None:
|
||||
|
@@ -145,43 +145,3 @@ class EmployeePositionView(APIView):
|
||||
result = EmployeeCRUD.get_all_position()
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class EmployeeViewExportExcel(APIView):
|
||||
url_prefix = (f'{prefix}/export_all',)
|
||||
|
||||
def get(self):
|
||||
col_desc_map = {
|
||||
'nickname': "姓名",
|
||||
'email': '邮箱',
|
||||
'sex': '性别',
|
||||
'mobile': '手机号',
|
||||
'department_name': '部门',
|
||||
'position_name': '岗位',
|
||||
'nickname_direct_supervisor': '直接上级',
|
||||
'last_login': '上次登录时间',
|
||||
}
|
||||
|
||||
# 规定了静态文件的存储位置
|
||||
excel_filename = 'all_employee_info.xlsx'
|
||||
excel_path = current_app.config['UPLOAD_DIRECTORY_FULL']
|
||||
excel_path_with_filename = os.path.join(excel_path, excel_filename)
|
||||
|
||||
# 根据parameter查表,自连接通过上级id获取上级名字列
|
||||
block_status = int(request.args.get('block_status', -1))
|
||||
df = EmployeeCRUD.get_export_employee_df(block_status)
|
||||
|
||||
# 改变列名为中文head
|
||||
try:
|
||||
df = df.rename(columns=col_desc_map)
|
||||
except Exception as e:
|
||||
abort(500, ErrFormat.rename_columns_failed.format(str(e)))
|
||||
|
||||
# 生成静态excel文件
|
||||
try:
|
||||
df.to_excel(excel_path_with_filename,
|
||||
sheet_name='Sheet1', index=False, encoding="utf-8")
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
abort(500, ErrFormat.generate_excel_failed.format(str(e)))
|
||||
|
||||
return send_from_directory(excel_path, excel_filename, as_attachment=True)
|
||||
|
@@ -6,7 +6,9 @@ from flask import Blueprint
|
||||
from flask_restful import Api
|
||||
|
||||
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__))
|
||||
|
||||
|
@@ -1,14 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Create an application instance."""
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
from api.app import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.user = current_user
|
||||
|
@@ -3,7 +3,7 @@
|
||||
from api.app import create_app
|
||||
from api.extensions import celery
|
||||
|
||||
# celery worker -A celery_worker.celery -l DEBUG -E -Q xxxx
|
||||
# celery -A celery_worker.celery worker -l DEBUG -E -Q xxxx
|
||||
|
||||
app = create_app()
|
||||
app.app_context().push()
|
||||
|
@@ -1,80 +1,46 @@
|
||||
-i https://mirrors.aliyun.com/pypi/simple
|
||||
alembic==1.7.7
|
||||
amqp==2.6.1
|
||||
aniso8601==9.0.1
|
||||
APScheduler==3.10.1
|
||||
attrs==23.1.0
|
||||
backports.zoneinfo==0.2.1
|
||||
bcrypt==4.0.1
|
||||
beautifulsoup4==4.12.2
|
||||
billiard==3.6.4.0
|
||||
bs4==0.0.1
|
||||
cachelib==0.9.0
|
||||
celery==4.3.0
|
||||
celery==5.3.1
|
||||
celery-once==3.0.1
|
||||
certifi==2023.5.7
|
||||
charset-normalizer==3.1.0
|
||||
click==8.1.3
|
||||
dnspython==2.3.0
|
||||
elasticsearch==7.17.9
|
||||
email-validator==1.3.1
|
||||
environs==4.2.0
|
||||
flasgger==0.9.5
|
||||
Flask==1.0.3
|
||||
Flask-APScheduler==1.12.4
|
||||
Flask-Bcrypt==0.7.1
|
||||
Flask==2.3.2
|
||||
Flask-Bcrypt==1.0.1
|
||||
Flask-Caching==2.0.2
|
||||
Flask-Cors==4.0.0
|
||||
Flask-Login==0.4.1
|
||||
Flask-Login==0.6.2
|
||||
Flask-Migrate==2.5.2
|
||||
Flask-RESTful==0.3.7
|
||||
Flask-SQLAlchemy==2.4.0
|
||||
future==0.18.2
|
||||
gunicorn==19.5.0
|
||||
idna==3.4
|
||||
importlib-metadata==6.8.0
|
||||
importlib-resources==6.0.0
|
||||
itsdangerous==2.0.1
|
||||
Jinja2==3.0.1
|
||||
Flask-RESTful==0.3.10
|
||||
Flask-SQLAlchemy==2.5.0
|
||||
future==0.18.3
|
||||
gunicorn==21.0.1
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
jinja2schema==0.1.4
|
||||
jsonschema==4.18.0
|
||||
jsonschema-specifications==2023.6.1
|
||||
kombu==4.4.0
|
||||
kombu==5.3.1
|
||||
Mako==1.2.4
|
||||
MarkupSafe==2.1.3
|
||||
marshmallow==2.20.2
|
||||
meld3==2.0.1
|
||||
mistune==3.0.1
|
||||
more-itertools==5.0.0
|
||||
msgpack-python==0.5.6
|
||||
numpy==1.18.5
|
||||
pandas==1.3.2
|
||||
Pillow==8.3.2
|
||||
pkgutil_resolve_name==1.3.10
|
||||
pyasn1==0.5.0
|
||||
pyasn1-modules==0.3.0
|
||||
Pillow==9.3.0
|
||||
pycryptodome==3.12.0
|
||||
PyJWT==2.4.0
|
||||
PyMySQL==0.9.3
|
||||
python-dateutil==2.8.2
|
||||
python-dotenv==1.0.0
|
||||
python-ldap==3.2.0
|
||||
pytz==2023.3
|
||||
PyMySQL==1.1.0
|
||||
python-ldap==3.4.0
|
||||
PyYAML==6.0
|
||||
redis==3.2.1
|
||||
referencing==0.29.1
|
||||
redis==4.6.0
|
||||
requests==2.31.0
|
||||
rpds-py==0.8.8
|
||||
six==1.12.0
|
||||
soupsieve==2.4.1
|
||||
SQLAlchemy==1.3.5
|
||||
SQLAlchemy==1.4.49
|
||||
supervisor==4.0.3
|
||||
timeout-decorator==0.5.0
|
||||
toposort==1.10
|
||||
treelib==1.6.1
|
||||
tzlocal==5.0.1
|
||||
urllib3==1.26.16
|
||||
vine==1.3.0
|
||||
Werkzeug==0.15.5
|
||||
WTForms==3.0.0
|
||||
zipp==3.16.0
|
||||
Werkzeug==2.3.6
|
||||
WTForms==3.0.0
|
@@ -35,6 +35,7 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_REDIS_PASSWORD = ""
|
||||
CACHE_KEY_PREFIX = "CMDB::"
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
@@ -53,13 +54,16 @@ MAIL_PASSWORD = ''
|
||||
DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# # queue
|
||||
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/2"
|
||||
BROKER_URL = 'redis://127.0.0.1:6379/2'
|
||||
BROKER_VHOST = '/'
|
||||
CELERY = {
|
||||
"broker_url": 'redis://127.0.0.1:6379/2',
|
||||
"result_backend": "redis://127.0.0.1:6379/2",
|
||||
"broker_vhost": "/",
|
||||
"broker_connection_retry_on_startup": True
|
||||
}
|
||||
ONCE = {
|
||||
'backend': 'celery_once.backends.Redis',
|
||||
'settings': {
|
||||
'url': BROKER_URL,
|
||||
'url': CELERY['broker_url'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +80,14 @@ DEFAULT_SERVICE = "http://127.0.0.1:8000"
|
||||
AUTH_WITH_LDAP = False
|
||||
LDAP_SERVER = ''
|
||||
LDAP_DOMAIN = ''
|
||||
LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
|
||||
|
||||
# # pagination
|
||||
DEFAULT_PAGE_COUNT = 50
|
||||
|
||||
# # permission
|
||||
WHITE_LIST = ["127.0.0.1"]
|
||||
USE_ACL = False
|
||||
USE_ACL = True
|
||||
|
||||
# # elastic search
|
||||
ES_HOST = '127.0.0.1'
|
||||
|
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""provide some sample data in database"""
|
||||
import uuid
|
||||
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 (
|
||||
Attribute,
|
||||
CIType,
|
||||
@@ -11,16 +14,12 @@ from api.models.cmdb import (
|
||||
CITypeRelation,
|
||||
RelationType
|
||||
)
|
||||
from api.models.acl import User
|
||||
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci import CIManager, CIRelationManager
|
||||
|
||||
|
||||
def force_add_user():
|
||||
from flask import g
|
||||
if not getattr(g, "user", None):
|
||||
g.user = User.query.first()
|
||||
from flask_login import current_user, login_user
|
||||
if not getattr(current_user, "username", None):
|
||||
login_user(User.query.first())
|
||||
|
||||
|
||||
def init_attributes(num=1):
|
||||
@@ -77,12 +76,12 @@ def init_relation_type(num=1):
|
||||
|
||||
def init_ci_type_relation(num=1):
|
||||
result = []
|
||||
ci_types = init_ci_types(num+1)
|
||||
ci_types = init_ci_types(num + 1)
|
||||
relation_types = init_relation_type(num)
|
||||
for i in range(num):
|
||||
result.append(CITypeRelation.create(
|
||||
parent_id=ci_types[i].id,
|
||||
child_id=ci_types[i+1].id,
|
||||
child_id=ci_types[i + 1].id,
|
||||
relation_type_id=relation_types[i].id
|
||||
))
|
||||
return result
|
||||
|
1
cmdb-ui/.gitattributes
vendored
1
cmdb-ui/.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
public/* linguist-vendored
|
26
cmdb-ui/.gitignore
vendored
26
cmdb-ui/.gitignore
vendored
@@ -1,26 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
/dist.zip
|
||||
/temp
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
*.css.map
|
||||
|
||||
.env.development
|
@@ -1,11 +0,0 @@
|
||||
#Oneops-UI
|
||||
|
||||
```shell
|
||||
## build
|
||||
yarn run build
|
||||
|
||||
## develop
|
||||
yarn run serve
|
||||
|
||||
|
||||
```
|
@@ -53,7 +53,7 @@
|
||||
"vuedraggable": "^2.23.0",
|
||||
"vuex": "^3.1.1",
|
||||
"vxe-table": "3.6.9",
|
||||
"vxe-table-plugin-export-xlsx": "^3.0.4",
|
||||
"vxe-table-plugin-export-xlsx": "2.0.0",
|
||||
"xe-utils": "3",
|
||||
"xlsx": "0.15.0",
|
||||
"xlsx-js-style": "^1.2.0"
|
||||
|
@@ -20,13 +20,7 @@ export function putCompanyInfo(id, parameter) {
|
||||
data: parameter,
|
||||
})
|
||||
}
|
||||
export function postImageFile(parameter) {
|
||||
return axios({
|
||||
url: '/common-setting/v1/file',
|
||||
method: 'post',
|
||||
data: parameter,
|
||||
})
|
||||
}
|
||||
|
||||
export function getDepartmentList(params) {
|
||||
// ?department_parent_id=-1 查询第一级部门,下面的id根据实际的传
|
||||
return axios({
|
||||
|
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',
|
||||
})
|
||||
}
|
@@ -15,31 +15,117 @@
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div :class="`${currentIconType === '4' ? 'selected' : ''}`" @click="handleChangeIconType('4')">
|
||||
自定义
|
||||
</div>
|
||||
<a-upload
|
||||
slot="description"
|
||||
name="avatar"
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
accept=".svg,.png,.jpg,.jpeg"
|
||||
v-if="currentIconType === '4'"
|
||||
>
|
||||
<a-button icon="plus" size="small" type="primary">添加</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
<div class="custom-icon-select-popover-content">
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<template v-if="iconList && iconList.length">
|
||||
<template v-if="currentIconType !== '4'">
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<div
|
||||
v-for="name in category.list"
|
||||
:key="name.value"
|
||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
||||
@click="clickIcon(name.value)"
|
||||
>
|
||||
<ops-icon :type="name.value" />
|
||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="custom-icon-select-popover-content-wrapper" :style="{ marginTop: '10px' }" v-else>
|
||||
<div
|
||||
v-for="name in category.list"
|
||||
:key="name.value"
|
||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
||||
@click="clickIcon(name.value)"
|
||||
v-for="icon in iconList"
|
||||
:key="icon.id"
|
||||
:class="`custom-icon-select-popover-item ${value.id === icon.id ? 'selected' : ''}`"
|
||||
@click="clickCustomIcon(icon)"
|
||||
>
|
||||
<ops-icon :type="name.value" />
|
||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
||||
<div class="custom-icon-select-popover-content-img-box">
|
||||
<img :src="`/api/common-setting/v1/file/${icon.data.url}`" />
|
||||
<a-popconfirm
|
||||
overlayClassName="custom-icon-select-confirm-popover"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
title="确认删除?"
|
||||
@confirm="(e) => deleteIcon(e, icon)"
|
||||
@cancel="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
"
|
||||
>
|
||||
<a-icon
|
||||
type="close"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
<span class="custom-icon-select-popover-item-label" :title="icon.data.name">{{ icon.data.name }}</span>
|
||||
</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>
|
||||
<template v-if="currentIconType !== '0' && currentIconType !== '3'">
|
||||
<template v-if="!['0', '3', '4'].includes(currentIconType)">
|
||||
<a-divider :style="{ margin: '5px 0' }" />
|
||||
<el-color-picker size="mini" v-model="value.color"> </el-color-picker>
|
||||
</template>
|
||||
<a-form class="custom-icon-select-form" :form="form" v-show="currentIconType === '4' && formVisible">
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:labelCol="{ span: 4 }"
|
||||
:wrapperCol="{ span: 16 }"
|
||||
><a-input
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入名称' }] }]"
|
||||
/></a-form-item>
|
||||
<a-form-item label="预览" :labelCol="{ span: 4 }">
|
||||
<div class="custom-icon-select-form-img">
|
||||
<img :src="formImg" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :labelCol="{ span: 16 }">
|
||||
<a-space>
|
||||
<a-button size="small" @click="handleCancel">取消</a-button>
|
||||
<a-button size="small" type="primary" @click="handleOk">确定</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect">
|
||||
<img v-if="value.id && value.url" :src="`/api/common-setting/v1/file/${value.url}`" />
|
||||
<ops-icon
|
||||
v-else
|
||||
:type="value.name"
|
||||
:style="{ color: value.name && value.name.startsWith('icon-') ? value.color || '' : '' }"
|
||||
/>
|
||||
@@ -56,6 +142,8 @@ import {
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
} from './constants'
|
||||
import { postImageFile, getFileData, addFileData, deleteFileData } from '@/api/file'
|
||||
|
||||
export default {
|
||||
name: 'CustomIconSelect',
|
||||
components: { ElColorPicker: ColorPicker },
|
||||
@@ -77,13 +165,18 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
iconTypeList,
|
||||
commonIconList,
|
||||
linearIconList,
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
visible: false,
|
||||
currentIconType: '1',
|
||||
currentIconType: '3',
|
||||
customIconList: [],
|
||||
formVisible: false,
|
||||
formImg: null,
|
||||
file: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -97,18 +190,30 @@ export default {
|
||||
return this.fillIconList
|
||||
case '3': // 多色
|
||||
return this.multicolorIconList
|
||||
case '4': // 自定义
|
||||
return this.customIconList
|
||||
default:
|
||||
return this.linearIconList
|
||||
}
|
||||
},
|
||||
fileName() {
|
||||
const splitFileName = this.file.name.split('.')
|
||||
return splitFileName.splice(0, splitFileName.length - 1).join('')
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.eventListener)
|
||||
this.getFileData()
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.eventListener)
|
||||
},
|
||||
methods: {
|
||||
getFileData() {
|
||||
getFileData('ops-custom-icon').then((res) => {
|
||||
this.customIconList = res
|
||||
})
|
||||
},
|
||||
eventListener(e) {
|
||||
if (this.visible) {
|
||||
const dom = document.getElementById(`custom-icon-select-popover`)
|
||||
@@ -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() {
|
||||
this.visible = true
|
||||
console.log(this.value)
|
||||
if (!this.value.name) {
|
||||
this.currentIconType = '1'
|
||||
this.currentIconType = '3'
|
||||
return
|
||||
}
|
||||
// changyong已废弃
|
||||
if (this.value.name.startsWith('changyong-')) {
|
||||
this.currentIconType = '0'
|
||||
} else if (this.value.name.startsWith('icon-xianxing')) {
|
||||
this.currentIconType = '1'
|
||||
} else if (this.value.name.startsWith('icon-shidi')) {
|
||||
this.currentIconType = '2'
|
||||
} else {
|
||||
} else if (this.value.name.startsWith('caise')) {
|
||||
this.currentIconType = '3'
|
||||
} else {
|
||||
this.currentIconType = '4'
|
||||
}
|
||||
},
|
||||
handleChangeIconType(value) {
|
||||
this.currentIconType = value
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
if (!isLt2M) {
|
||||
this.$message.error('图片大小不可超过2MB!')
|
||||
return false
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
this.formVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.file = file
|
||||
this.formImg = reader.result
|
||||
this.form.setFieldsValue({ name: this.fileName })
|
||||
})
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleCancel() {
|
||||
this.formVisible = false
|
||||
this.form.setFieldsValue({ name: '' })
|
||||
this.formImg = null
|
||||
},
|
||||
handleOk() {
|
||||
const fm = new FormData()
|
||||
fm.append('file', this.file)
|
||||
postImageFile(fm).then((res) => {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
addFileData('ops-custom-icon', { data: { name: values.name, url: res.file_name } }).then(() => {
|
||||
this.$message.success('上传成功!')
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
deleteIcon(e, icon) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
deleteFileData('ops-custom-icon', icon.id).then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
this.handleCancel()
|
||||
this.getFileData()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -176,7 +343,7 @@ export default {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.custom-icon-select-popover-content {
|
||||
max-height: 400px;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
.category {
|
||||
font-size: 14px;
|
||||
@@ -197,12 +364,43 @@ export default {
|
||||
padding: 5px 5px 2px 5px;
|
||||
margin: 0 2px 6px;
|
||||
color: #666;
|
||||
position: relative;
|
||||
.custom-icon-select-popover-item-label {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #eeeeee;
|
||||
.custom-icon-select-popover-content-img-box > i {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.custom-icon-select-popover-content-img-box {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
|
||||
> i {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
font-size: 12px;
|
||||
&:hover {
|
||||
color: #2f54eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
@@ -212,6 +410,8 @@ export default {
|
||||
}
|
||||
.custom-icon-select-popover-icon-type {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
> div {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -224,6 +424,16 @@ export default {
|
||||
.selected {
|
||||
border-color: #2f54eb;
|
||||
}
|
||||
.ant-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-icon-select-confirm-popover .ant-popover-inner-content {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -234,15 +444,39 @@ export default {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eeeeee;
|
||||
border: 1px solid #d9d9d9;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
> i {
|
||||
> i,
|
||||
> img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
> img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
> i {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
.custom-icon-select-form {
|
||||
.custom-icon-select-form-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d9d9d9;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
max-width: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -222,6 +222,9 @@ export default {
|
||||
renderIcon({ icon, selectedIcon, customIcon = undefined, name = undefined, typeId = undefined, routeName }) {
|
||||
if (typeId) {
|
||||
if (customIcon) {
|
||||
if (customIcon.split('$$')[2]) {
|
||||
return <img style={{ maxHeight: '14px', maxWidth: '14px', marginRight: '10px' }} src={`/api/common-setting/v1/file/${customIcon.split('$$')[3]}`}></img >
|
||||
}
|
||||
return <ops-icon
|
||||
style={{
|
||||
color: customIcon.split('$$')[1],
|
||||
|
@@ -4,6 +4,7 @@ const appConfig = {
|
||||
buildAclToModules: true, // 是否在各个应用下 内联权限管理
|
||||
ssoLogoutURL: '/api/sso/logout',
|
||||
showDocs: false,
|
||||
useEncryption: true,
|
||||
}
|
||||
|
||||
export default appConfig
|
||||
|
@@ -78,6 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchPermissonHistory } from '@/modules/acl/api/history'
|
||||
@@ -251,23 +252,25 @@ export default {
|
||||
},
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return q ? newQueryParams : _queryParams
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
@@ -283,7 +286,7 @@ export default {
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id }
|
||||
this.queryParams = { ...this.queryParams, ...queryParams, app_id: this.app_id }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
|
@@ -212,7 +212,12 @@ export default {
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
...queryParams,
|
||||
app_id: this.app_id,
|
||||
scope: this.checked ? 'resource_group' : 'resource',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -245,9 +250,11 @@ export default {
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -255,16 +262,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -179,7 +179,7 @@ export default {
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: 'resource_type' }
|
||||
this.queryParams = { ...this.queryParams, ...queryParams, app_id: this.app_id, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -206,9 +206,11 @@ export default {
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -216,16 +218,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -220,7 +220,12 @@ export default {
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
...queryParams,
|
||||
app_id: this.app_id,
|
||||
scope: this.checked ? 'role_relation' : 'role',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -237,9 +242,11 @@ export default {
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -247,16 +254,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
@@ -280,7 +287,7 @@ export default {
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -147,7 +147,6 @@ export default {
|
||||
expand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
},
|
||||
date: undefined,
|
||||
checked: false,
|
||||
@@ -188,7 +187,6 @@ export default {
|
||||
handleReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.date = undefined
|
||||
this.$emit('searchFormReset')
|
||||
|
@@ -200,7 +200,7 @@ export default {
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.queryParams = { ...this.queryParams, ...queryParams }
|
||||
this.queryParams.app_id = this.app_id
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
@@ -238,7 +238,7 @@ export default {
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,25 +281,27 @@ export default {
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
q += `,${key}:${_queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
q += `${key}:${_queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return q ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -79,6 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import debounce from 'lodash/debounce'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
@@ -347,7 +348,7 @@ export default {
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.queryParams = { ...this.queryParams, ...queryParams }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
@@ -387,23 +388,24 @@ export default {
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return q ? newQueryParams : _queryParams
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -267,7 +267,7 @@ export default {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.queryParams = { ...this.queryParams, ...queryParams, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -317,9 +317,11 @@ export default {
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -327,16 +329,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -217,7 +217,7 @@ export default {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: 'resource_type' }
|
||||
this.queryParams = { ...this.queryParams, ...queryParams, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -253,9 +253,11 @@ export default {
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -263,16 +265,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -231,7 +231,7 @@ export default {
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.queryParams = { ...this.queryParams, ...queryParams, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -247,9 +247,10 @@ export default {
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
let q = _queryParams.q ? _queryParams.q : ''
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
@@ -257,16 +258,16 @@ export default {
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
if (q) q += `,${key}:${_queryParams[key]}`
|
||||
else q += `${key}:${_queryParams[key]}`
|
||||
delete _queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return flag ? newQueryParams : _queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
|
@@ -241,7 +241,7 @@ export default {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.queryParams = { ...this.queryParams, ...queryParams }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
@@ -286,7 +286,7 @@ export default {
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,25 +326,27 @@ export default {
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
const _queryParams = _.cloneDeep(queryParams)
|
||||
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
for (const key in _queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
_queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
q += `,${key}:${_queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
q += `${key}:${_queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
const newQueryParams = { ..._queryParams, q }
|
||||
return q ? newQueryParams : _queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
|
@@ -70,7 +70,7 @@
|
||||
show-overflow
|
||||
>
|
||||
<!-- 1 -->
|
||||
<vxe-table-column type="checkbox" fixed="left"></vxe-table-column>
|
||||
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>
|
||||
|
||||
<!-- 2 -->
|
||||
|
||||
@@ -186,49 +186,6 @@ export default {
|
||||
pageSize: 50,
|
||||
},
|
||||
tableData: [],
|
||||
tableColumns: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '资源名',
|
||||
field: 'name',
|
||||
minWidth: '150px',
|
||||
showOverflow: 'tooltip',
|
||||
fixed: 'left',
|
||||
slots: {
|
||||
header: 'name_header',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
minWidth: '100px',
|
||||
field: 'user',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
minWidth: '220px',
|
||||
field: 'created_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '最后修改时间',
|
||||
minWidth: '220px',
|
||||
field: 'updated_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
width: '200px',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
btnName: '新增资源',
|
||||
isGroup: false,
|
||||
allResourceTypes: [],
|
||||
|
@@ -16,6 +16,7 @@
|
||||
class="ops-stripe-table"
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
show-overflow
|
||||
highlight-hover-row
|
||||
:height="`${windowHeight - 165}px`"
|
||||
size="small"
|
||||
@@ -24,15 +25,17 @@
|
||||
<a-icon type="lock" v-if="row.block" />
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<template>
|
||||
<a-space>
|
||||
<a :disabled="isAclAdmin ? false : true" @click="handleEdit(row)">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-tooltip title="权限汇总">
|
||||
<a @click="handlePermCollect(row)"><a-icon type="solution"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-popconfirm :title="`确认删除【${row.nickname || row.username}】?`" @confirm="deleteUser(row.uid)">
|
||||
<a :style="{ color: 'red' }"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</a-spin>
|
||||
@@ -73,11 +76,14 @@ export default {
|
||||
title: '加入时间',
|
||||
field: 'date_joined',
|
||||
minWidth: '160px',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
title: '锁定',
|
||||
field: 'block',
|
||||
minWidth: '100px',
|
||||
width: '150px',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'block_default',
|
||||
},
|
||||
@@ -85,8 +91,9 @@ export default {
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '180px',
|
||||
width: '150px',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
@@ -155,9 +162,6 @@ export default {
|
||||
handleEdit(record) {
|
||||
this.$refs.userForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteUser(record.uid)
|
||||
},
|
||||
handleOk() {
|
||||
this.searchName = ''
|
||||
this.search()
|
||||
@@ -165,9 +169,9 @@ export default {
|
||||
handleCreate() {
|
||||
this.$refs.userForm.handleCreate()
|
||||
},
|
||||
deleteUser(attrId) {
|
||||
deleteUserById(attrId).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
deleteUser(uid) {
|
||||
deleteUserById(uid).then((res) => {
|
||||
this.$message.success(`删除成功!`)
|
||||
this.handleOk()
|
||||
})
|
||||
},
|
||||
|
@@ -25,18 +25,20 @@ export function addCI(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function updateCI(id, params) {
|
||||
export function updateCI(id, params, isShowMessage = true) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
data: params,
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteCI(ciId) {
|
||||
export function deleteCI(ciId, isShowMessage = true) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/${ciId}`,
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
isShowMessage
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div class="ci-type-grant">
|
||||
<vxe-table size="mini" stripe class="ops-stripe-table" :data="filterTableData" :max-height="`${tableHeight}px`">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="filterTableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
@@ -37,6 +45,7 @@ import _ from 'lodash'
|
||||
import { permMap } from './constants.js'
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import ReadCheckbox from './readCheckbox.vue'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeGrant',
|
||||
@@ -55,10 +64,13 @@ export default {
|
||||
type: String,
|
||||
default: 'ci_type',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filterTableData() {
|
||||
console.log(_.cloneDeep(this.tableData))
|
||||
const _tableData = this.tableData.filter((data) => {
|
||||
const _intersection = _.intersection(
|
||||
Object.keys(data),
|
||||
@@ -90,6 +102,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
async handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user