Compare commits

...

141 Commits

Author SHA1 Message Date
pycook
330b64edb3 chore: release v2.4.9 2024-07-26 17:03:00 +08:00
Leo Song
63a3074cb7 Merge pull request #585 from veops/fix_ui_240726
fix: discovery card eye btn
2024-07-26 16:50:43 +08:00
songlh
31b8cf49dc fix: discovery card eye btn 2024-07-26 16:49:28 +08:00
Leo Song
b01c335456 Merge pull request #584 from veops/dev_ui_240726
feat(ui): update auto discovery
2024-07-26 10:41:19 +08:00
songlh
002fef09e2 feat(ui): update auto discovery 2024-07-26 10:40:37 +08:00
pycook
175778a162 perf(api): auto discovery (#582) 2024-07-25 17:45:26 +08:00
Leo Song
5050a1bef5 Merge pull request #581 from veops/dev_ui_240722
feat: add accounts config
2024-07-22 17:39:18 +08:00
songlh
46a6cf67d6 feat: add accounts config 2024-07-22 17:38:48 +08:00
Leo Song
4e857c2775 Merge pull request #580 from veops/dev_ui_240716
feat: add history export
2024-07-16 13:46:40 +08:00
songlh
835df1bdeb feat: add history export 2024-07-16 13:45:31 +08:00
ivonGwy
579339d13c change pic 2024-07-15 16:23:34 +08:00
ivonGwy
629967ce82 change pic 2024-07-15 16:22:20 +08:00
pycook
3a00bfd236 chore: update docker compose 2024-07-11 14:29:34 +08:00
pycook
2e97ebd895 chore: release v2.4.8 2024-07-10 19:43:01 +08:00
Leo Song
eb6a813cbc Merge pull request #578 from veops/dev_ui_24071002
feat(ui): update
2024-07-10 19:19:08 +08:00
songlh
ff78face48 feat(ui): update 2024-07-10 19:18:22 +08:00
pycook
d55433c438 fix(api): computed attributes for multi values (#577) 2024-07-10 19:18:03 +08:00
Leo Song
daf0254616 Merge pull request #575 from veops/dev_ui_240710
fix: topoview search error
2024-07-10 10:12:11 +08:00
songlh
6b32009955 fix: topoview search error 2024-07-10 10:11:40 +08:00
Leo Song
d53288c1fb Merge pull request #574 from veops/dev_ui_240709
feat: update auto discovery
2024-07-09 09:45:25 +08:00
songlh
586d820a08 feat: update auto discovery 2024-07-09 09:44:28 +08:00
pycook
6776be4599 fix(api): auto discovery update
fix(api): auto discovery update
2024-07-08 18:03:21 +08:00
pycook
ff2b8ea198 perf(api): relationships built by attribute values (#572) 2024-07-08 11:42:18 +08:00
Leo Song
ed46a1e1c1 Merge pull request #571 from veops/dev_ui_240703
feat: add http attr mapping
2024-07-03 18:49:23 +08:00
songlh
0dc614fb46 feat: add http attr mapping 2024-07-03 18:47:55 +08:00
pycook
bc66d33ce0 fix(api): auto discovery configuration save password
fix(api): auto discovery configuration save password
2024-07-02 21:32:30 +08:00
pycook
d5db68d7d0 feat(api): auto discovery supports mapping (#569) 2024-07-02 20:19:50 +08:00
Leo Song
b22b8b286b Merge pull request #568 from veops/dev_ui_240628
dev_ui_240628
2024-06-28 17:43:30 +08:00
songlh
dd4f3b0e9c feat: update model export 2024-06-28 17:42:20 +08:00
songlh
688f4e0ea4 fix(ui): load ci type error 2024-06-28 17:42:10 +08:00
pycook
c1813f525d chore: update docker compose 2024-06-27 21:33:19 +08:00
pycook
b405e28498 chore: release v2.4.7 2024-06-27 21:30:02 +08:00
pycook
fa32758462 perf(api): CIType templates download (#567) 2024-06-27 20:54:38 +08:00
Leo Song
29995b660a Merge pull request #566 from veops/dev_ui_0627
feat: update model config
2024-06-27 19:41:58 +08:00
songlh
b96fc06a62 feat: update model config 2024-06-27 19:41:24 +08:00
Leo Song
c7f30b63ff Merge pull request #565 from veops/dev_ui_0625
fix(ui): auto discovery
2024-06-25 17:36:00 +08:00
songlh
5bff69a8a8 fix(ui): auto discovery 2024-06-25 17:35:29 +08:00
pycook
d5e60fab88 chore: update ui dockerfile 2024-06-24 21:40:38 +08:00
Jared Tan
190f452118 chore: fix UI docker build and makes UI/API docker build parallel execution (#563)
* try fix UI docker build

* parallel execution

* polish

* polish

* polish

* update
2024-06-24 21:00:57 +08:00
Leo Song
98a4824364 Merge pull request #564 from veops/dev_ui_ad_0624
feat: update ad ui
2024-06-24 14:26:33 +08:00
songlh
c0f9baea79 feat: update ad ui 2024-06-24 14:25:56 +08:00
pycook
d4b661c77f fix(api): commands cmdb-patch 2024-06-21 18:22:56 +08:00
pycook
75cd7bde77 fix(api): auto discovery permission 2024-06-21 12:47:12 +08:00
Leo Song
ec912d3a65 Merge pull request #562 from veops/fix_ui_2.4.6
fix(ui): some bugs
2024-06-21 11:49:53 +08:00
songlh
42f02b4986 fix(ui): some bugs 2024-06-21 11:49:12 +08:00
simontigers
a13b999820 Merge pull request #561 from veops/dev_common_perm
fix(api): auto_discovery add new perms
2024-06-21 10:25:35 +08:00
simontigers
5f53b0dd0e fix(api): auto_discovery add new perms 2024-06-21 10:25:13 +08:00
Leo Song
df22085ff9 Merge pull request #560 from veops/fix_ui_topology
fix(ui): topology view error
2024-06-20 22:21:27 +08:00
LH_R
06148b402d fix(ui): topology view error 2024-06-20 22:20:25 +08:00
pycook
3fe020505a chore: release v2.4.6 2024-06-20 20:31:10 +08:00
pycook
b34e83124f perf(api): auto discovery has been upgraded (#559) 2024-06-20 20:30:04 +08:00
Leo Song
cdc52d3f80 Merge pull request #558 from veops/dev_ui_ad
fix: build error
2024-06-20 20:03:40 +08:00
LH_R
b3a80d5678 fix: build error 2024-06-20 19:54:15 +08:00
Leo Song
a2e3061bba Merge pull request #557 from veops/dev_ui_ad
feat(ui): auto discovery
2024-06-20 17:29:06 +08:00
songlh
a8eb5126ea feat(ui): auto discovery 2024-06-20 17:28:09 +08:00
pycook
adac2129fc chore: update Dockerfile-UI 2024-06-20 13:20:42 +08:00
pycook
e660c901ce chore: update Dockerfile-UI 2024-06-20 11:07:57 +08:00
pycook
ff002c0a1e chore: update Dockerfile-UI 2024-06-20 09:47:56 +08:00
Leo Song
88593d6da7 Merge pull request #555 from veops/fix_ui_lint
fix(ui): lint error
2024-06-18 11:42:53 +08:00
songlh
6fa0dd5bc5 fix(ui): lint error 2024-06-18 11:42:22 +08:00
Jared Tan
3200942373 polish ci and remove es build (#553) 2024-06-18 10:31:33 +08:00
pycook
4fd705cc59 feat(api): add table c_ad_ci_type_relations 2024-06-18 10:22:04 +08:00
Jared Tan
74827ce187 add workflow (#552) 2024-06-18 09:29:00 +08:00
Leo Song
4ed1eb6062 Merge pull request #551 from veops/fix_bug_538
fix: issue #538
2024-06-17 14:41:51 +08:00
songlh
7792204658 fix: issue #538 2024-06-17 14:41:24 +08:00
Leo Song
8621108906 Merge pull request #550 from veops/fix_bug_operation_history
fix: operation history table
2024-06-14 17:27:49 +08:00
songlh
6437af19b9 fix: operation history table 2024-06-14 17:27:13 +08:00
Leo Song
735ddb334c Merge pull request #542 from veops/fix_issue_540
fix: issue #540
2024-06-12 15:00:08 +08:00
songlh
4a8032202e fix: issue #540 2024-06-12 14:59:14 +08:00
Leo Song
c7acea6422 Merge pull request #539 from veops/fix_computed_code
fix: computed code area tab
2024-06-11 15:03:20 +08:00
songlh
ac4c93de8e fix: computed code area tab 2024-06-11 15:02:37 +08:00
pycook
8d044cf935 chore(docker compose): add api health check 2024-06-09 20:58:27 +08:00
pycook
54747fa789 feat(ui): update iconfont 2024-06-07 10:41:26 +08:00
pycook
545f1bb30b Dev dynamic attribute (#535)
* feat: dynamic attribute

* feat(api): dynamic attribute
2024-06-07 10:39:40 +08:00
pycook
dc77bca17c feat: dynamic attribute (#534) 2024-06-07 10:29:32 +08:00
Leo Song
4973278c5a Merge pull request #532 from veops/fix_bug_530
fix: ci topo expand error
2024-06-06 14:06:26 +08:00
songlh
d1c9361e47 fix: ci topo expand error 2024-06-06 14:05:32 +08:00
Leo Song
28c57cacd9 Merge pull request #531 from veops/dev_ui_240606
feat: update topology view
2024-06-06 11:10:35 +08:00
songlh
711dcc4bd7 feat: update topology view 2024-06-06 11:08:58 +08:00
simontigers
491d3cce00 Merge pull request #529 from veops/fix_decorator_perms_role_required
fix: decorator_perms_role_required
2024-06-04 19:23:58 +08:00
simontigers
27354a3927 fix: decorator_perms_role_required 2024-06-04 19:23:22 +08:00
Leo Song
78495eb976 Merge pull request #528 from veops/feat/dev_ui_240604
feat(ui): update model relation
2024-06-04 12:05:42 +08:00
songlh
ae900c7d3b feat(ui): update model relation 2024-06-04 12:04:26 +08:00
pycook
50134e6a0b feat(api): attribute association supports multiple groups (#527) 2024-06-04 11:34:54 +08:00
pycook
65ef58dea9 feat: update docker-compose 2024-05-30 13:18:28 +08:00
pycook
0a2e7aa99f feat: put the mysql password in .env 2024-05-30 13:08:18 +08:00
pycook
8875e75883 fix(acl): add relation 2024-05-30 09:33:30 +08:00
pycook
2f03639c57 chore: release v2.4.5 2024-05-29 13:32:40 +08:00
pycook
49bc5d94a9 fix(api): topology view read permission 2024-05-29 11:34:02 +08:00
pycook
39354e1293 feat(ui): update components CMDBExprDrawer 2024-05-28 20:16:39 +08:00
pycook
d3714f3ecf feat(ui): relation-graph upgrade to 2.1.42 2024-05-28 20:11:53 +08:00
pycook
729a616282 feat(ui): topology view (#525) 2024-05-28 20:03:10 +08:00
fxiang21
2d3a290aa3 fix: cmdb-inner-secrets-init bug 2024-05-28 19:57:16 +08:00
pycook
9e885a5b12 feat(api): i18n update 2024-05-28 18:08:15 +08:00
simontigers
f5822d7cba Merge pull request #524 from simontigers/common_cmdb_app_perm
fix: cmdb app perms
2024-05-28 17:55:08 +08:00
hu.sima
21ea553e74 fix: cmdb app perms 2024-05-28 17:54:51 +08:00
pycook
e63038d1b6 feat(api): topology view (#523)
* feat(api): topology views crud

* feat(api): topology view

* feat(api): topology view api done

* feat(api): topology view is done
2024-05-28 17:50:09 +08:00
simontigers
d56806f511 Merge pull request #521 from simontigers/common_cmdb_app_perm
feat: CMDB add TopologyView resource
2024-05-28 16:21:25 +08:00
simontigers
7ac7fdc08e feat: CMDB add TopologyView resource 2024-05-28 16:20:56 +08:00
pycook
ba11707146 feat(ui): resource views router 2024-05-21 17:58:06 +08:00
pycook
d49dc8a067 fix(api): hot loading is blocked in development mode 2024-05-21 13:14:40 +08:00
pycook
6bfb34fe2a Dev UI 240520 (#517)
* feat(ui): Model configuration supports search models

* fix(ui): Jump to the first subscription by default
2024-05-20 14:10:33 +08:00
thexqn
2c7ed8c32d chore: Update local.md with instructions for setting up MySQL and Redis services (#515) 2024-05-20 13:39:05 +08:00
pycook
5b275af54e fix(api): exception when calling webhook (#516) 2024-05-20 13:22:54 +08:00
pycook
dde7ec6246 feat(ui): Resources and Preference support grouping 2024-05-19 21:55:36 +08:00
pycook
9181817e96 feat(api): my preference support grouping (#513) 2024-05-18 22:55:01 +08:00
pycook
46b54bb7f2 fix(ui): some bugs (#512) 2024-05-17 12:07:56 +08:00
pycook
fe63310c4e Dev api 240517 (#511)
* fix(api): list values delete

* fix(acl): role rebuild cache
2024-05-17 11:20:53 +08:00
pycook
27c733aa2c docs: update sql 2024-05-16 20:59:30 +08:00
pycook
2a8e9e684e fix(ui): issue#490 2024-05-02 21:28:06 +08:00
pycook
095190a785 fix(api): unique constraint (#505) 2024-05-02 21:22:40 +08:00
pycook
ef25c94b5d fix(api): permissions for CIType group editing 2024-04-29 15:18:47 +08:00
pycook
06ae1bcf13 docs: update build_api_key 2024-04-29 15:11:12 +08:00
pycook
9ead4e7d8d chore: release v2.4.4 2024-04-29 14:44:33 +08:00
pycook
994a28dd25 feat(ui): baseline rollback (#502) 2024-04-29 10:10:07 +08:00
simontigers
74b587e46c Merge pull request #501 from simontigers/common_decorator_perms
fix: role base app perm
2024-04-29 09:27:36 +08:00
hu.sima
091cd882bd fix: role base app perm 2024-04-29 09:26:23 +08:00
simontigers
73093db467 Merge pull request #500 from simontigers/common_decorator_perms
fix(api): decorator_perms_role_required
2024-04-28 19:43:22 +08:00
hu.sima
66e268ce68 fix(api): decorator_perms_role_required 2024-04-28 19:41:50 +08:00
simontigers
a41d1a5e97 Merge pull request #499 from simontigers/common_decorator_perms
feat(api): role perm
2024-04-28 19:22:43 +08:00
hu.sima
b4b728fe28 feat(api): role perm 2024-04-28 19:22:10 +08:00
pycook
d16462d8b7 feat(api): ci baseline rollback (#498) 2024-04-28 19:19:14 +08:00
kdyq007
de7d98c0b4 feat(api): Add sorting function to ci list attribute (#495)
Co-authored-by: sherlock <sherlock@gmail.com>
2024-04-27 09:20:24 +08:00
pycook
51332c7236 feat(ui): CI change logs related itsm 2024-04-24 20:09:59 +08:00
dagongren
bf1076fe4a feat:update cs && update style (#488) 2024-04-23 12:20:27 +08:00
dagongren
3454a98cfb fix(cmdb-ui):service tree search (#487) 2024-04-19 13:32:12 +08:00
dagongren
506dcbb40e fix(cmdb-ui):fix service tree change table page (#486) 2024-04-19 11:46:51 +08:00
dagongren
5ac4517187 style (#482) 2024-04-18 10:49:39 +08:00
pycook
761e98884b chore: add volumes cmdb_cache-data in docker-compose 2024-04-18 10:02:57 +08:00
pycook
073654624e fix(api): commands cmdb-init-cache 2024-04-17 21:37:18 +08:00
dagongren
df54244ff1 fix(cmdb-ui):service tree key (#480) 2024-04-17 20:42:16 +08:00
pycook
27e9919198 chore: release v2.4.3 2024-04-17 19:35:35 +08:00
dagongren
dc8b1a5de2 feat(cmdb-ui):citype show attr && service tree search (#479) 2024-04-17 17:59:21 +08:00
pycook
d8a7728f1d feat(api): custom attribute display (#478) 2024-04-17 17:50:46 +08:00
simontigers
82881965fb Merge pull request #474 from simontigers/common_check_new_columns
Common check new columns
2024-04-16 15:35:15 +08:00
hu.sima
1bb62022f1 fix(api): check new column support enum change 2024-04-16 15:34:03 +08:00
hu.sima
ed445a8d82 fix(api): secrets_shares Import ERROR 2024-04-16 15:33:36 +08:00
pycook
3626b1a97e feat(api): service tree search by keywords (#471) 2024-04-15 20:04:56 +08:00
loveiwei
32529fba9b fix: support sealing and unsealing secret in multiple process(more than one workers started by gunicorn) (#469)
* fix: 解决在麒麟系统上使用docker安装时使用celery -D启动 celery 可能出现的问题

* fix: 解决在麒麟系统上使用docker安装时使用celery -D启动 celery 可能出现的问题

* fix: NoneType happend while unsealing the secret funtion, cancel the address check while unseal and seal

* fix: unseal secret function

* fix: remove depens_on in docker-compose

* fix: support sealing and unsealing secret in multiple process(more than one workers started by gunicorn)
2024-04-15 18:08:47 +08:00
dagongren
a042b4fe39 fix(cmdb-ui):ci detail relation repeatly ciid (#468) 2024-04-15 13:50:50 +08:00
dagongren
a0631414dc style: global static.less (#467) 2024-04-12 15:18:52 +08:00
232 changed files with 23487 additions and 9732 deletions

6
.env Normal file
View File

@@ -0,0 +1,6 @@
MYSQL_ROOT_PASSWORD='123456'
MYSQL_HOST='mysql'
MYSQL_PORT=3306
MYSQL_USER='cmdb'
MYSQL_DATABASE='cmdb'
MYSQL_PASSWORD='123456'

0
.github/config.yml vendored
View File

View File

@@ -0,0 +1,79 @@
name: docker-images-build-and-release
on:
push:
branches:
- master
tags: ["v*"]
pull_request:
branches:
- master
env:
# Use docker.io for Docker Hub if empty
REGISTRY_SERVER_ADDRESS: ghcr.io/veops
TAG: ${{ github.sha }}
jobs:
setup-environment:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
release-api-images:
runs-on: ubuntu-latest
needs: [setup-environment]
permissions:
contents: read
packages: write
timeout-minutes: 90
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to GitHub Package Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push CMDB-API Docker image
uses: docker/build-push-action@v6
with:
file: docker/Dockerfile-API
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-api:${{ env.TAG }}
release-ui-images:
runs-on: ubuntu-latest
needs: [setup-environment]
permissions:
contents: read
packages: write
timeout-minutes: 90
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to GitHub Package Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push CMDB-UI Docker image
uses: docker/build-push-action@v6
with:
file: docker/Dockerfile-UI
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY_SERVER_ADDRESS }}/cmdb-ui:${{ env.TAG }}

View File

@@ -1,6 +1,4 @@
MYSQL_ROOT_PASSWORD ?= root include ./Makefile.variable
MYSQL_PORT ?= 3306
REDIS_PORT ?= 6379
default: help default: help
help: ## display this help help: ## display this help
@@ -50,3 +48,25 @@ clean: ## remove unwanted files like .pyc's
lint: ## check style with flake8 lint: ## check style with flake8
flake8 --exclude=env . flake8 --exclude=env .
.PHONY: lint .PHONY: lint
api-docker-build:
export DOCKER_CLI_EXPERIMENTAL=enabled ;\
! ( docker buildx ls | grep multi-platform-builder ) && docker buildx create --use --platform=$(BUILD_ARCH) --name multi-platform-builder ;\
docker buildx build \
--builder multi-platform-builder \
--platform=$(BUILD_ARCH) \
--tag $(REGISTRY)/cmdb-api:$(CMDB_DOCKER_VERSION) \
--tag $(REGISTRY)/cmdb-api:latest \
-f docker/Dockerfile-API \
.
ui-docker-build:
export DOCKER_CLI_EXPERIMENTAL=enabled ;\
! ( docker buildx ls | grep multi-platform-builder ) && docker buildx create --use --platform=$(BUILD_ARCH) --name multi-platform-builder ;\
docker buildx build \
--builder multi-platform-builder \
--platform=$(BUILD_ARCH) \
--tag $(REGISTRY)/cmdb-ui:$(CMDB_DOCKER_VERSION) \
--tag $(REGISTRY)/cmdb-ui:latest \
-f docker/Dockerfile-UI \
.

21
Makefile.variable Normal file
View File

@@ -0,0 +1,21 @@
SHELL := /bin/bash -o pipefail
MYSQL_ROOT_PASSWORD ?= root
MYSQL_PORT ?= 3306
REDIS_PORT ?= 6379
LATEST_TAG_DIFF:=$(shell git describe --tags --abbrev=8)
LATEST_COMMIT:=$(VERSION)-dev-$(shell git rev-parse --short=8 HEAD)
BUILD_ARCH ?= linux/amd64,linux/arm64
# Set your version by env or using latest tags from git
CMDB_VERSION?=$(LATEST_TAG_DIFF)
ifeq ($(CMDB_VERSION),)
#fall back to last commit
CMDB_VERSION=$(LATEST_COMMIT)
endif
COMMIT_VERSION:=$(LATEST_COMMIT)
CMDB_DOCKER_VERSION:=${CMDB_VERSION}
CMDB_CHART_VERSION:=$(shell echo ${CMDB_VERSION} | sed 's/^v//g' )
REGISTRY ?= local

View File

@@ -73,7 +73,8 @@
## 安装 ## 安装
### Docker 一键快速构建 ### Docker 一键快速构建
> 方法一
[//]: # (> 方法一)
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2) - 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
- 第二步: 拷贝项目 - 第二步: 拷贝项目
```shell ```shell
@@ -83,13 +84,20 @@ git clone https://github.com/veops/cmdb.git
``` ```
docker compose up -d docker compose up -d
``` ```
> 方法二, 该方法适用于linux系统
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2) [//]: # (> 方法二, 该方法适用于linux系统)
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
```shell [//]: # (- 第一步: 先安装 Docker 环境, 以及Docker Compose &#40;v2&#41;)
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
sh install.sh install [//]: # (- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`)
```
[//]: # (```shell)
[//]: # (curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh)
[//]: # (sh install.sh install)
[//]: # (```)
### [本地开发环境搭建](docs/local.md) ### [本地开发环境搭建](docs/local.md)
@@ -105,4 +113,7 @@ sh install.sh install
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_ _**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
![公众号: 维易科技OneOps](docs/images/wechat.png)
<p align="center">
<img src="docs/images/wechat.png" alt="公众号: 维易科技OneOps" />
</p>

View File

@@ -36,7 +36,7 @@ Flask-Caching = ">=1.0.0"
environs = "==4.2.0" environs = "==4.2.0"
marshmallow = "==2.20.2" marshmallow = "==2.20.2"
# async tasks # async tasks
celery = ">=5.3.1" celery = "==5.3.1"
celery_once = "==3.0.1" celery_once = "==3.0.1"
more-itertools = "==5.0.0" more-itertools = "==5.0.0"
kombu = ">=5.3.1" kombu = ">=5.3.1"
@@ -67,6 +67,7 @@ colorama = ">=0.4.6"
pycryptodomex = ">=3.19.0" pycryptodomex = ">=3.19.0"
lz4 = ">=4.3.2" lz4 = ">=4.3.2"
python-magic = "==0.4.27" python-magic = "==0.4.27"
jsonpath = "==0.82.2"
[dev-packages] [dev-packages]
# Testing # Testing

View File

@@ -55,9 +55,12 @@ def cmdb_init_cache():
for cr in ci_relations: for cr in ci_relations:
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id}) relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
if cr.ancestor_ids: if cr.ancestor_ids:
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id}) relations2.setdefault('{},{}'.format(cr.ancestor_ids, cr.first_ci_id), {}).update(
{cr.second_ci_id: cr.second_ci.type_id})
for i in relations: for i in relations:
relations[i] = json.dumps(relations[i]) relations[i] = json.dumps(relations[i])
for i in relations2:
relations2[i] = json.dumps(relations2[i])
if relations: if relations:
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION) rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
if relations2: if relations2:
@@ -125,7 +128,7 @@ def cmdb_init_acl():
perms = [PermEnum.READ] perms = [PermEnum.READ]
elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION: elif resource_type == ResourceTypeEnum.CI_TYPE_RELATION:
perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT] perms = [PermEnum.ADD, PermEnum.DELETE, PermEnum.GRANT]
elif resource_type == ResourceTypeEnum.RELATION_VIEW: elif resource_type in (ResourceTypeEnum.RELATION_VIEW, ResourceTypeEnum.TOPOLOGY_VIEW):
perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT] perms = [PermEnum.READ, PermEnum.UPDATE, PermEnum.DELETE, PermEnum.GRANT]
ResourceTypeCRUD.add(app_id, resource_type, '', perms) ResourceTypeCRUD.add(app_id, resource_type, '', perms)
@@ -187,6 +190,7 @@ def cmdb_counter():
login_user(UserCache.get('worker')) login_user(UserCache.get('worker'))
i = 0 i = 0
today = datetime.date.today()
while True: while True:
try: try:
db.session.remove() db.session.remove()
@@ -197,6 +201,10 @@ def cmdb_counter():
CMDBCounterCache.flush_adc_counter() CMDBCounterCache.flush_adc_counter()
i = 0 i = 0
if datetime.date.today() != today:
CMDBCounterCache.clear_ad_exec_history()
today = datetime.date.today()
CMDBCounterCache.flush_sub_counter() CMDBCounterCache.flush_sub_counter()
i += 1 i += 1
@@ -323,7 +331,7 @@ def cmdb_inner_secrets_init(address):
""" """
init inner secrets for password feature init inner secrets for password feature
""" """
res, ok = KeyManage(backend=InnerKVManger).init() res, ok = KeyManage(backend=InnerKVManger()).init()
if not ok: if not ok:
if res.get("status") == "failed": if res.get("status") == "failed":
KeyManage.print_response(res) KeyManage.print_response(res)
@@ -357,13 +365,13 @@ def cmdb_inner_secrets_unseal(address):
""" """
unseal the secrets feature unseal the secrets feature
""" """
if not valid_address(address): # if not valid_address(address):
return # return
address = "{}/api/v0.1/secrets/unseal".format(address.strip("/")) address = "{}/api/v0.1/secrets/unseal".format(address.strip("/"))
for i in range(global_key_threshold): for i in range(global_key_threshold):
token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False) token = click.prompt(f'Enter unseal token {i + 1}', hide_input=True, confirmation_prompt=False)
assert token is not None assert token is not None
resp = requests.post(address, headers={"Unseal-Token": token}) resp = requests.post(address, headers={"Unseal-Token": token}, timeout=5)
if resp.status_code == 200: if resp.status_code == 200:
KeyManage.print_response(resp.json()) KeyManage.print_response(resp.json())
if resp.json().get("status") in ["success", "skip"]: if resp.json().get("status") in ["success", "skip"]:
@@ -490,3 +498,64 @@ def cmdb_agent_init():
click.echo("Key : {}".format(click.style(user.key, bg='red'))) click.echo("Key : {}".format(click.style(user.key, bg='red')))
click.echo("Secret: {}".format(click.style(user.secret, bg='red'))) click.echo("Secret: {}".format(click.style(user.secret, bg='red')))
@click.command()
@click.option(
'-v',
'--version',
help='input cmdb version, e.g. 2.4.6',
required=True,
)
@with_appcontext
def cmdb_patch(version):
"""
CMDB upgrade patch
"""
version = version[1:] if version.lower().startswith("v") else version
try:
if version >= '2.4.6':
from api.models.cmdb import CITypeRelation
for cr in CITypeRelation.get_by(to_dict=False):
if hasattr(cr, 'parent_attr_id') and cr.parent_attr_id and not cr.parent_attr_ids:
parent_attr_ids, child_attr_ids = [cr.parent_attr_id], [cr.child_attr_id]
cr.update(parent_attr_ids=parent_attr_ids, child_attr_ids=child_attr_ids, commit=False)
db.session.commit()
from api.models.cmdb import AutoDiscoveryCIType, AutoDiscoveryCITypeRelation
from api.lib.cmdb.cache import CITypeCache, AttributeCache
for adt in AutoDiscoveryCIType.get_by(to_dict=False):
if adt.relation:
if not AutoDiscoveryCITypeRelation.get_by(ad_type_id=adt.type_id):
peer_type = CITypeCache.get(list(adt.relation.values())[0]['type_name'])
peer_type_id = peer_type and peer_type.id
peer_attr = AttributeCache.get(list(adt.relation.values())[0]['attr_name'])
peer_attr_id = peer_attr and peer_attr.id
if peer_type_id and peer_attr_id:
AutoDiscoveryCITypeRelation.create(ad_type_id=adt.type_id,
ad_key=list(adt.relation.keys())[0],
peer_type_id=peer_type_id,
peer_attr_id=peer_attr_id,
commit=False)
if hasattr(adt, 'interval') and adt.interval and not adt.cron:
adt.cron = "*/{} * * * *".format(adt.interval // 60 or 1)
db.session.commit()
if version >= "2.4.7":
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.models.cmdb import AutoDiscoveryRule
for i in DEFAULT_INNER:
existed = AutoDiscoveryRule.get_by(name=i['name'], first=True, to_dict=False)
if existed is not None:
if "en" in i['option'] and 'en' not in (existed.option or {}):
option = copy.deepcopy(existed.option)
option['en'] = i['option']['en']
existed.update(option=option, commit=False)
db.session.commit()
except Exception as e:
print("cmdb patch failed: {}".format(e))

View File

@@ -6,6 +6,7 @@ from werkzeug.datastructures import MultiDict
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm from api.lib.common_setting.employee import EmployeeAddForm, GrantEmployeeACLPerm
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.utils import CheckNewColumn
from api.models.common_setting import Employee, Department from api.models.common_setting import Employee, Department
@@ -209,57 +210,7 @@ def common_check_new_columns():
""" """
add new columns to tables add new columns to tables
""" """
from api.extensions import db CheckNewColumn().run()
from sqlalchemy import inspect, text
def get_model_by_table_name(_table_name):
registry = getattr(db.Model, 'registry', None)
class_registry = getattr(registry, '_class_registry', None)
for _model in class_registry.values():
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
return _model
return None
def add_new_column(target_table_name, new_column):
column_type = new_column.type.compile(engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
if new_column.comment:
sql += f" comment '{new_column.comment}'"
if column_type == 'JSON':
pass
elif default_value:
if column_type.startswith('VAR') or column_type.startswith('Text'):
if default_value is None or len(default_value) == 0:
pass
else:
sql += f" DEFAULT {default_value}"
sql = text(sql)
db.session.execute(sql)
engine = db.get_engine()
inspector = inspect(engine)
table_names = inspector.get_table_names()
for table_name in table_names:
existed_columns = inspector.get_columns(table_name)
existed_column_name_list = [c['name'] for c in existed_columns]
model = get_model_by_table_name(table_name)
if model is None:
continue
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
for column in model_columns:
if column.name not in existed_column_name_list:
try:
add_new_column(table_name, column)
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
except Exception as e:
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
current_app.logger.error(e)
@click.command() @click.command()

View File

@@ -229,7 +229,7 @@ class AttributeManager(object):
is_choice = True if choice_value or kwargs.get('choice_web_hook') or kwargs.get('choice_other') else False is_choice = True if choice_value or kwargs.get('choice_web_hook') or kwargs.get('choice_other') else False
name = kwargs.pop("name") name = kwargs.pop("name")
if name in BUILTIN_KEYWORDS: if name in BUILTIN_KEYWORDS or kwargs.get('alias') in BUILTIN_KEYWORDS:
return abort(400, ErrFormat.attribute_name_cannot_be_builtin) return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
while kwargs.get('choice_other'): while kwargs.get('choice_other'):

View File

@@ -1,34 +1,51 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy
import datetime import datetime
import json import json
import jsonpath
import os import os
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
from api.lib.cmdb.cache import CITypeAttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.const import AutoDiscoveryType
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search
from api.lib.mixin import DBMixin
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import AESCrypto
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryRule
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
from sqlalchemy import func from sqlalchemy import func
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import CLOUD_MAP
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import AutoDiscoveryMappingCache
from api.lib.cmdb.cache import CITypeAttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.const import AutoDiscoveryType
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.custom_dashboard import SystemConfigManager
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.mixin import DBMixin
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import AESCrypto
from api.models.cmdb import AutoDiscoveryAccount
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import AutoDiscoveryCounter
from api.models.cmdb import AutoDiscoveryExecHistory
from api.models.cmdb import AutoDiscoveryRule
from api.models.cmdb import AutoDiscoveryRuleSyncHistory
from api.tasks.cmdb import build_relations_for_ad_accept
from api.tasks.cmdb import write_ad_rule_sync_history
PWD = os.path.abspath(os.path.dirname(__file__)) PWD = os.path.abspath(os.path.dirname(__file__))
app_cli = CMDBApp()
def parse_plugin_script(script): def parse_plugin_script(script):
@@ -96,14 +113,30 @@ class AutoDiscoveryRuleCRUD(DBMixin):
else: else:
self.cls.create(**rule) self.cls.create(**rule)
def _can_add(self, **kwargs): def _can_add(self, valid=True, **kwargs):
self.cls.get_by(name=kwargs['name']) and abort(400, ErrFormat.adr_duplicate.format(kwargs['name'])) self.cls.get_by(name=kwargs['name']) and abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
if kwargs.get('is_plugin') and kwargs.get('plugin_script'): if kwargs.get('is_plugin') and kwargs.get('plugin_script') and valid:
kwargs = check_plugin_script(**kwargs) kwargs = check_plugin_script(**kwargs)
acl = ACLManager(app_cli.app_name)
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.create_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.create_plugin))
kwargs['owner'] = current_user.uid
return kwargs return kwargs
def _can_update(self, **kwargs): def _can_update(self, valid=True, **kwargs):
existed = self.cls.get_by_id(kwargs['_id']) or abort( existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id']))) 404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
@@ -115,6 +148,22 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if other and other.id != existed.id: if other and other.id != existed.id:
return abort(400, ErrFormat.adr_duplicate.format(kwargs['name'])) return abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
if existed.is_plugin and valid:
acl = ACLManager(app_cli.app_name)
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.update_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.update_plugin))
return existed return existed
def update(self, _id, **kwargs): def update(self, _id, **kwargs):
@@ -122,21 +171,44 @@ class AutoDiscoveryRuleCRUD(DBMixin):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'): if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs) kwargs = check_plugin_script(**kwargs)
for item in AutoDiscoveryCIType.get_by(adr_id=_id, to_dict=False):
item.update(updated_at=datetime.datetime.now())
return super(AutoDiscoveryRuleCRUD, self).update(_id, filter_none=False, **kwargs) return super(AutoDiscoveryRuleCRUD, self).update(_id, filter_none=False, **kwargs)
def _can_delete(self, **kwargs): def _can_delete(self, **kwargs):
if AutoDiscoveryCIType.get_by(adr_id=kwargs['_id'], first=True): if AutoDiscoveryCIType.get_by(adr_id=kwargs['_id'], first=True):
return abort(400, ErrFormat.adr_referenced) return abort(400, ErrFormat.adr_referenced)
return self._can_update(**kwargs) existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
if existed.is_plugin:
acl = ACLManager(app_cli.app_name)
has_perm = True
try:
if not acl.has_permission(app_cli.op.Auto_Discovery,
app_cli.resource_type_name,
app_cli.op.delete_plugin) and not is_app_admin(app_cli.app_name):
has_perm = False
except Exception:
if not is_app_admin(app_cli.app_name):
return abort(403, ErrFormat.role_required.format(app_cli.admin_name))
if not has_perm:
return abort(403, ErrFormat.no_permission.format(
app_cli.op.Auto_Discovery, app_cli.op.delete_plugin))
return existed
class AutoDiscoveryCITypeCRUD(DBMixin): class AutoDiscoveryCITypeCRUD(DBMixin):
cls = AutoDiscoveryCIType cls = AutoDiscoveryCIType
@classmethod @classmethod
def get_all(cls): def get_all(cls, type_ids=None):
return cls.cls.get_by(to_dict=False) res = cls.cls.get_by(to_dict=False)
return [i for i in res if type_ids is None or i.type_id in type_ids]
@classmethod @classmethod
def get_by_id(cls, _id): def get_by_id(cls, _id):
@@ -147,25 +219,59 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
return cls.cls.get_by(type_id=type_id, to_dict=False) return cls.cls.get_by(type_id=type_id, to_dict=False)
@classmethod @classmethod
def get(cls, ci_id, oneagent_id, last_update_at=None): def get_ad_attributes(cls, type_id):
result = []
adts = cls.get_by_type_id(type_id)
for adt in adts:
adr = AutoDiscoveryRuleCRUD.get_by_id(adt.adr_id)
if not adr:
continue
if adr.type == AutoDiscoveryType.HTTP:
for i in DEFAULT_INNER:
if adr.name == i['name']:
attrs = AutoDiscoveryHTTPManager.get_attributes(
i['en'], (adt.extra_option or {}).get('category')) or []
result.extend([i.get('name') for i in attrs])
break
elif adr.type == AutoDiscoveryType.SNMP:
attributes = AutoDiscoverySNMPManager.get_attributes()
result.extend([i.get('name') for i in (attributes or [])])
else:
result.extend([i.get('name') for i in (adr.attributes or [])])
return sorted(list(set(result)))
@classmethod
def get(cls, ci_id, oneagent_id, oneagent_name, last_update_at=None):
"""
OneAgent sync rules
:param ci_id:
:param oneagent_id:
:param oneagent_name:
:param last_update_at:
:return:
"""
result = [] result = []
rules = cls.cls.get_by(to_dict=True) rules = cls.cls.get_by(to_dict=True)
for rule in rules: for rule in rules:
if rule.get('relation'): if not rule['enabled']:
continue continue
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'): if isinstance(rule.get("extra_option"), dict):
if not (current_user.username == "cmdb_agent" or current_user.uid == rule['uid']): decrypt_account(rule['extra_option'], rule['uid'])
if rule['extra_option'].get('_reference'):
rule['extra_option'].pop('password', None)
rule['extra_option'].pop('secret', None) rule['extra_option'].pop('secret', None)
else: rule['extra_option'].update(
rule['extra_option']['secret'] = AESCrypto.decrypt(rule['extra_option']['secret']) AutoDiscoveryAccountCRUD().get_config_by_id(rule['extra_option']['_reference']))
if oneagent_id and rule['agent_id'] == oneagent_id: if oneagent_id and rule['agent_id'] == oneagent_id:
result.append(rule) result.append(rule)
elif rule['query_expr']: elif rule['query_expr']:
query = rule['query_expr'].lstrip('q').lstrip('=') query = rule['query_expr'].lstrip('q').lstrip('=')
s = search(query, fl=['_id'], count=1000000) s = ci_search(query, fl=['_id'], count=1000000)
try: try:
response, _, _, _, _, _ = s.search() response, _, _, _, _, _ = s.search()
except SearchError as e: except SearchError as e:
@@ -176,25 +282,32 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
result.append(rule) result.append(rule)
break break
elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']: elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']:
try:
if not int(oneagent_id, 16): # excludes master
continue
except Exception:
pass
adr = AutoDiscoveryRuleCRUD.get_by_id(rule['adr_id']) adr = AutoDiscoveryRuleCRUD.get_by_id(rule['adr_id'])
if not adr: if not adr:
continue continue
if adr.type in (AutoDiscoveryType.SNMP, AutoDiscoveryType.HTTP): if adr.type in (AutoDiscoveryType.SNMP, AutoDiscoveryType.HTTP):
continue continue
if not rule['updated_at']:
continue
result.append(rule) result.append(rule)
ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or ""
new_last_update_at = "" new_last_update_at = ""
for i in result: for i in result:
i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict() i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict()
i['adr'].pop("attributes", None)
__last_update_at = max([i['updated_at'] or "", i['created_at'] or "", __last_update_at = max([i['updated_at'] or "", i['created_at'] or "",
i['adr']['created_at'] or "", i['adr']['updated_at'] or ""]) i['adr']['created_at'] or "", i['adr']['updated_at'] or "", ad_rules_updated_at])
if new_last_update_at < __last_update_at: if new_last_update_at < __last_update_at:
new_last_update_at = __last_update_at new_last_update_at = __last_update_at
write_ad_rule_sync_history.apply_async(args=(result, oneagent_id, oneagent_name, datetime.datetime.now()),
queue=CMDB_QUEUE)
if not last_update_at or new_last_update_at > last_update_at: if not last_update_at or new_last_update_at > last_update_at:
return result, new_last_update_at return result, new_last_update_at
else: else:
@@ -213,7 +326,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
agent_id = agent_id.strip() agent_id = agent_id.strip()
q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}" q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}"
s = search(q.format(current_user.username, agent_id.strip())) s = ci_search(q.format(current_user.username, agent_id.strip()))
try: try:
response, _, _, _, _, _ = s.search() response, _, _, _, _, _ = s.search()
if response: if response:
@@ -222,7 +335,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
current_app.logger.warning(e) current_app.logger.warning(e)
return abort(400, str(e)) return abort(400, str(e))
s = search(q.format(current_user.nickname, agent_id.strip())) s = ci_search(q.format(current_user.nickname, agent_id.strip()))
try: try:
response, _, _, _, _, _ = s.search() response, _, _, _, _, _ = s.search()
if response: if response:
@@ -236,7 +349,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if query_expr.startswith('q='): if query_expr.startswith('q='):
query_expr = query_expr[2:] query_expr = query_expr[2:]
s = search(query_expr, count=1000000) s = ci_search(query_expr, count=1000000)
try: try:
response, _, _, _, _, _ = s.search() response, _, _, _, _, _ = s.search()
for i in response: for i in response:
@@ -254,19 +367,39 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
def _can_add(**kwargs): def _can_add(**kwargs):
if kwargs.get('adr_id'): if kwargs.get('adr_id'):
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort( adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id']))) 404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
# if not adr.is_plugin: if adr.type == AutoDiscoveryType.HTTP:
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False) kwargs.setdefault('extra_option', dict())
# if other: en_name = None
# ci_type = CITypeCache.get(other.type_id) for i in DEFAULT_INNER:
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias)) if i['name'] == adr.name:
en_name = i['en']
break
if en_name and kwargs['extra_option'].get('category'):
for item in CLOUD_MAP[en_name]:
if item["collect_key_map"].get(kwargs['extra_option']['category']):
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
kwargs['extra_option']['category']]
kwargs["extra_option"]["provider"] = en_name
break
if adr.type == AutoDiscoveryType.COMPONENTS and kwargs.get('extra_option'):
for i in DEFAULT_INNER:
if i['name'] == adr.name:
kwargs['extra_option']['collect_key'] = i['option'].get('collect_key')
break
if kwargs.get('is_plugin') and kwargs.get('plugin_script'): if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs) kwargs = check_plugin_script(**kwargs)
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'): encrypt_account(kwargs.get('extra_option'))
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
ci_type = CITypeCache.get(kwargs['type_id'])
unique = AttributeCache.get(ci_type.unique_id)
if unique and unique.name not in (kwargs.get('attributes') or {}).values():
current_app.logger.warning((unique.name, kwargs.get('attributes'), ci_type.alias))
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
kwargs['uid'] = current_user.uid kwargs['uid'] = current_user.uid
@@ -276,11 +409,44 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
existed = self.cls.get_by_id(kwargs['_id']) or abort( existed = self.cls.get_by_id(kwargs['_id']) or abort(
404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id']))) 404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id'])))
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr')) adr = AutoDiscoveryRule.get_by_id(existed.adr_id) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(existed.adr_id)))
if adr.type == AutoDiscoveryType.HTTP:
kwargs.setdefault('extra_option', dict())
en_name = None
for i in DEFAULT_INNER:
if i['name'] == adr.name:
en_name = i['en']
break
if en_name and kwargs['extra_option'].get('category'):
for item in CLOUD_MAP[en_name]:
if item["collect_key_map"].get(kwargs['extra_option']['category']):
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
kwargs['extra_option']['category']]
kwargs["extra_option"]["provider"] = en_name
break
if adr.type == AutoDiscoveryType.COMPONENTS and kwargs.get('extra_option'):
for i in DEFAULT_INNER:
if i['name'] == adr.name:
kwargs['extra_option']['collect_key'] = i['option'].get('collect_key')
break
if 'attributes' in kwargs:
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
ci_type = CITypeCache.get(existed.type_id)
unique = AttributeCache.get(ci_type.unique_id)
if unique and unique.name not in (kwargs.get('attributes') or {}).values():
current_app.logger.warning((unique.name, kwargs.get('attributes'), ci_type.alias))
return abort(400, ErrFormat.ad_not_unique_key.format(unique.name))
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'): if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
if current_user.uid != existed.uid: if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission) return abort(403, ErrFormat.adt_secret_no_permission)
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('password'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
return existed return existed
@@ -289,10 +455,22 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'): if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs) kwargs = check_plugin_script(**kwargs)
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'): encrypt_account(kwargs.get('extra_option'))
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
return super(AutoDiscoveryCITypeCRUD, self).update(_id, filter_none=False, **kwargs) inst = self._can_update(_id=_id, **kwargs)
if len(kwargs) == 1 and 'enabled' in kwargs: # enable or disable
pass
elif inst.agent_id != kwargs.get('agent_id') or inst.query_expr != kwargs.get('query_expr'):
for item in AutoDiscoveryRuleSyncHistory.get_by(adt_id=inst.id, to_dict=False):
item.delete(commit=False)
db.session.commit()
SystemConfigManager.create_or_update("ad_rules_updated_at",
dict(v=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
obj = inst.update(_id=_id, filter_none=False, **kwargs)
return obj
def _can_delete(self, **kwargs): def _can_delete(self, **kwargs):
if AutoDiscoveryCICRUD.get_by_adt_id(kwargs['_id']): if AutoDiscoveryCICRUD.get_by_adt_id(kwargs['_id']):
@@ -303,6 +481,61 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
return existed return existed
def delete(self, _id):
inst = self._can_delete(_id=_id)
inst.soft_delete()
for item in AutoDiscoveryRuleSyncHistory.get_by(adt_id=inst.id, to_dict=False):
item.delete(commit=False)
db.session.commit()
attributes = self.get_ad_attributes(inst.type_id)
for item in AutoDiscoveryCITypeRelationCRUD.get_by_type_id(inst.type_id):
if item.ad_key not in attributes:
item.soft_delete()
return inst
class AutoDiscoveryCITypeRelationCRUD(DBMixin):
cls = AutoDiscoveryCITypeRelation
@classmethod
def get_all(cls, type_ids=None):
res = cls.cls.get_by(to_dict=False)
return [i for i in res if type_ids is None or i.ad_type_id in type_ids]
@classmethod
def get_by_type_id(cls, type_id, to_dict=False):
return cls.cls.get_by(ad_type_id=type_id, to_dict=to_dict)
def upsert(self, ad_type_id, relations):
existed = self.cls.get_by(ad_type_id=ad_type_id, to_dict=False)
existed = {(i.ad_key, i.peer_type_id, i.peer_attr_id): i for i in existed}
new = []
for r in relations:
k = (r.get('ad_key'), r.get('peer_type_id'), r.get('peer_attr_id'))
if len(list(filter(lambda x: x, k))) == 3 and k not in existed:
self.cls.create(ad_type_id=ad_type_id, **r)
new.append(k)
for deleted in set(existed.keys()) - set(new):
existed[deleted].soft_delete()
return self.get_by_type_id(ad_type_id, to_dict=True)
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
class AutoDiscoveryCICRUD(DBMixin): class AutoDiscoveryCICRUD(DBMixin):
cls = AutoDiscoveryCI cls = AutoDiscoveryCI
@@ -337,7 +570,6 @@ class AutoDiscoveryCICRUD(DBMixin):
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id) adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
for adt in adts: for adt in adts:
attr_names |= set((adt.attributes or {}).values()) attr_names |= set((adt.attributes or {}).values())
return [attr for attr in attributes if attr['name'] in attr_names] return [attr for attr in attributes if attr['name'] in attr_names]
@classmethod @classmethod
@@ -391,16 +623,24 @@ class AutoDiscoveryCICRUD(DBMixin):
changed = False changed = False
if existed is not None: if existed is not None:
if existed.instance != kwargs['instance']: if existed.instance != kwargs['instance']:
instance = copy.deepcopy(existed.instance) or {}
instance.update(kwargs['instance'])
kwargs['instance'] = instance
existed.update(filter_none=False, **kwargs) existed.update(filter_none=False, **kwargs)
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
stdout="update resource: {}".format(kwargs.get('unique_value')))
changed = True changed = True
else: else:
existed = self.cls.create(**kwargs) existed = self.cls.create(**kwargs)
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
stdout="add resource: {}".format(kwargs.get('unique_value')))
changed = True changed = True
if adt.auto_accept and changed: if adt.auto_accept and changed:
try: try:
self.accept(existed) self.accept(existed)
except Exception as e: except Exception as e:
current_app.logger.error(e)
return abort(400, str(e)) return abort(400, str(e))
elif changed: elif changed:
existed.update(is_accept=False, accept_time=None, accept_by=None, filter_none=False) existed.update(is_accept=False, accept_time=None, accept_by=None, filter_none=False)
@@ -420,6 +660,13 @@ class AutoDiscoveryCICRUD(DBMixin):
inst.delete() inst.delete()
adt = AutoDiscoveryCIType.get_by_id(inst.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
AutoDiscoveryExecHistoryCRUD().add(type_id=inst.type_id,
stdout="delete resource: {}".format(inst.unique_value))
self._after_delete(inst) self._after_delete(inst)
return inst return inst
@@ -435,6 +682,13 @@ class AutoDiscoveryCICRUD(DBMixin):
not is_app_admin("cmdb") and validate_permission(ci_type.name, ResourceTypeEnum.CI, PermEnum.DELETE, "cmdb") not is_app_admin("cmdb") and validate_permission(ci_type.name, ResourceTypeEnum.CI, PermEnum.DELETE, "cmdb")
existed.delete() existed.delete()
adt = AutoDiscoveryCIType.get_by_id(existed.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
AutoDiscoveryExecHistoryCRUD().add(type_id=type_id,
stdout="delete resource: {}".format(unique_value))
# TODO: delete ci # TODO: delete ci
@classmethod @classmethod
@@ -445,36 +699,24 @@ class AutoDiscoveryCICRUD(DBMixin):
adt = AutoDiscoveryCITypeCRUD.get_by_id(adc.adt_id) or abort(404, ErrFormat.adt_not_found) adt = AutoDiscoveryCITypeCRUD.get_by_id(adc.adt_id) or abort(404, ErrFormat.adt_not_found)
ci_id = None ci_id = None
if adt.attributes:
ci_dict = {adt.attributes[k]: v for k, v in adc.instance.items() if k in adt.attributes}
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, **ci_dict)
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False) ad_key2attr = adt.attributes or {}
for r_adt in relation_adts: if ad_key2attr:
if not r_adt.relation or ci_id is None: ci_dict = {ad_key2attr[k]: None if not v and isinstance(v, (list, dict)) else v
continue for k, v in adc.instance.items() if k in ad_key2attr}
for ad_key in r_adt.relation: extra_option = adt.extra_option or {}
if not adc.instance.get(ad_key): mapping, path_mapping = AutoDiscoveryHTTPManager.get_predefined_value_mapping(
continue extra_option.get('provider'), extra_option.get('category'))
cmdb_key = r_adt.relation[ad_key] if mapping:
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'), ci_dict = {k: (mapping.get(k) or {}).get(str(v), v) for k, v in ci_dict.items()}
adc.instance.get(ad_key)) if path_mapping:
s = search(query) ci_dict = {k: jsonpath.jsonpath(v, path_mapping[k]) if k in path_mapping else v
try: for k, v in ci_dict.items()}
response, _, _, _, _, _ = s.search() ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, _is_admin=True, **ci_dict)
except SearchError as e: AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
current_app.logger.warning(e) stdout="accept resource: {}".format(adc.unique_value))
return abort(400, str(e))
relation_ci_id = response and response[0]['_id'] build_relations_for_ad_accept.apply_async(args=(adc.to_dict(), ci_id, ad_key2attr), queue=CMDB_QUEUE)
if relation_ci_id:
try:
CIRelationManager.add(ci_id, relation_ci_id)
except:
try:
CIRelationManager.add(relation_ci_id, ci_id)
except:
pass
adc.update(is_accept=True, adc.update(is_accept=True,
accept_by=nickname or current_user.nickname, accept_by=nickname or current_user.nickname,
@@ -485,17 +727,75 @@ class AutoDiscoveryCICRUD(DBMixin):
class AutoDiscoveryHTTPManager(object): class AutoDiscoveryHTTPManager(object):
@staticmethod @staticmethod
def get_categories(name): def get_categories(name):
return (ClOUD_MAP.get(name) or {}).get('categories') or [] categories = (CLOUD_MAP.get(name) or {}) or []
for item in copy.deepcopy(categories):
item.pop('map', None)
item.pop('collect_key_map', None)
@staticmethod return categories
def get_attributes(name, category):
tpt = ((ClOUD_MAP.get(name) or {}).get('map') or {}).get(category) def get_resources(self, name):
if tpt and os.path.exists(os.path.join(PWD, tpt)): en_name = None
with open(os.path.join(PWD, tpt)) as f: for i in DEFAULT_INNER:
return json.loads(f.read()) if i['name'] == name:
en_name = i['en']
break
if en_name:
categories = self.get_categories(en_name)
return [j for i in categories for j in i['items']]
return [] return []
@staticmethod
def get_attributes(provider, resource):
for item in (CLOUD_MAP.get(provider) or {}):
for _resource in (item.get('map') or {}):
if _resource == resource:
tpt = item['map'][_resource]
if isinstance(tpt, dict):
tpt = tpt.get('template')
if tpt and os.path.exists(os.path.join(PWD, tpt)):
with open(os.path.join(PWD, tpt)) as f:
return json.loads(f.read())
return []
@staticmethod
def get_mapping(provider, resource):
for item in (CLOUD_MAP.get(provider) or {}):
for _resource in (item.get('map') or {}):
if _resource == resource:
mapping = item['map'][_resource]
if not isinstance(mapping, dict):
return {}
name = mapping.get('mapping')
mapping = AutoDiscoveryMappingCache.get(name)
if isinstance(mapping, dict):
return {mapping[key][provider]['key'].split('.')[0]: key for key in mapping if
(mapping[key].get(provider) or {}).get('key')}
return {}
@staticmethod
def get_predefined_value_mapping(provider, resource):
for item in (CLOUD_MAP.get(provider) or {}):
for _resource in (item.get('map') or {}):
if _resource == resource:
mapping = item['map'][_resource]
if not isinstance(mapping, dict):
return {}, {}
name = mapping.get('mapping')
mapping = AutoDiscoveryMappingCache.get(name)
if isinstance(mapping, dict):
return ({key: mapping[key][provider].get('map') for key in mapping if
mapping[key].get(provider, {}).get('map')},
{key: mapping[key][provider]['key'].split('.', 1)[1] for key in mapping if
((mapping[key].get(provider) or {}).get('key') or '').split('.')[1:]})
return {}, {}
class AutoDiscoverySNMPManager(object): class AutoDiscoverySNMPManager(object):
@@ -506,3 +806,191 @@ class AutoDiscoverySNMPManager(object):
return json.loads(f.read()) return json.loads(f.read())
return [] return []
class AutoDiscoveryComponentsManager(object):
@staticmethod
def get_attributes(name):
if os.path.exists(os.path.join(PWD, "templates/{}.json".format(name))):
with open(os.path.join(PWD, "templates/{}.json".format(name))) as f:
return json.loads(f.read())
return []
class AutoDiscoveryRuleSyncHistoryCRUD(DBMixin):
cls = AutoDiscoveryRuleSyncHistory
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
def upsert(self, **kwargs):
existed = self.cls.get_by(adt_id=kwargs.get('adt_id'),
oneagent_id=kwargs.get('oneagent_id'),
oneagent_name=kwargs.get('oneagent_name'),
first=True,
to_dict=False)
if existed is not None:
existed.update(**kwargs)
else:
self.cls.create(**kwargs)
class AutoDiscoveryExecHistoryCRUD(DBMixin):
cls = AutoDiscoveryExecHistory
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
class AutoDiscoveryCounterCRUD(DBMixin):
cls = AutoDiscoveryCounter
def get(self, type_id):
res = self.cls.get_by(type_id=type_id, first=True, to_dict=True)
if res is None:
return dict(rule_count=0, exec_target_count=0, instance_count=0, accept_count=0,
this_month_count=0, this_week_count=0, last_month_count=0, last_week_count=0)
return res
def _can_add(self, **kwargs):
pass
def _can_update(self, **kwargs):
pass
def _can_delete(self, **kwargs):
pass
def encrypt_account(config):
if isinstance(config, dict):
if config.get('secret'):
config['secret'] = AESCrypto.encrypt(config['secret'])
if config.get('password'):
config['password'] = AESCrypto.encrypt(config['password'])
def decrypt_account(config, uid):
if isinstance(config, dict):
if config.get('password'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == uid):
config.pop('password', None)
else:
try:
config['password'] = AESCrypto.decrypt(config['password'])
except Exception as e:
current_app.logger.error('decrypt account failed: {}'.format(e))
if config.get('secret'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == uid):
config.pop('secret', None)
else:
try:
config['secret'] = AESCrypto.decrypt(config['secret'])
except Exception as e:
current_app.logger.error('decrypt account failed: {}'.format(e))
class AutoDiscoveryAccountCRUD(DBMixin):
cls = AutoDiscoveryAccount
def get(self, adr_id):
res = self.cls.get_by(adr_id=adr_id, to_dict=True)
for i in res:
decrypt_account(i.get('config'), i['uid'])
return res
def get_config_by_id(self, _id):
res = self.cls.get_by_id(_id)
if not res:
return {}
config = res.to_dict().get('config') or {}
decrypt_account(config, res.uid)
return config
def _can_add(self, **kwargs):
encrypt_account(kwargs.get('config'))
kwargs['uid'] = current_user.uid
return kwargs
def upsert(self, adr_id, accounts):
existed_all = self.cls.get_by(adr_id=adr_id, to_dict=False)
account_names = {i['name'] for i in accounts}
name_changed = dict()
for account in accounts:
existed = None
if account.get('id'):
existed = self.cls.get_by_id(account.get('id'))
if existed is None:
continue
account.pop('id')
name_changed[existed.name] = account.get('name')
else:
account = self._can_add(**account)
if existed is not None:
if current_user.uid == existed.uid:
config = copy.deepcopy(existed.config) or {}
config.update(account.get('config') or {})
account['config'] = config
existed.update(**account)
else:
self.cls.create(adr_id=adr_id, **account)
for item in existed_all:
if name_changed.get(item.name, item.name) not in account_names:
if current_user.uid == item.uid:
item.soft_delete()
def _can_update(self, **kwargs):
existed = self.cls.get_by_id(kwargs['_id']) or abort(404, ErrFormat.not_found)
if isinstance(kwargs.get('config'), dict) and kwargs['config'].get('secret'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
if isinstance(kwargs.get('config'), dict) and kwargs['config'].get('password'):
if current_user.uid != existed.uid:
return abort(403, ErrFormat.adt_secret_no_permission)
return existed
def update(self, _id, **kwargs):
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)
encrypt_account(kwargs.get('config'))
inst = self._can_update(_id=_id, **kwargs)
obj = inst.update(_id=_id, filter_none=False, **kwargs)
return obj
def _can_delete(self, **kwargs):
pass

View File

@@ -2,15 +2,38 @@
from api.lib.cmdb.const import AutoDiscoveryType from api.lib.cmdb.const import AutoDiscoveryType
DEFAULT_HTTP = [ PRIVILEGED_USERS = ("cmdb_agent", "worker", "admin")
dict(name="阿里云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aliyun'}}), DEFAULT_INNER = [
dict(name="腾讯云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False, dict(name="阿里云", en="aliyun", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-tengxunyun'}}), option={'icon': {'name': 'caise-aliyun'}, "en": "aliyun"}),
dict(name="华为云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False, dict(name="腾讯云", en="tencentcloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-huaweiyun'}}), option={'icon': {'name': 'caise-tengxunyun'}, "en": "tencentcloud"}),
dict(name="AWS", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False, dict(name="华为云", en="huaweicloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aws'}}), option={'icon': {'name': 'caise-huaweiyun'}, "en": "huaweicloud"}),
dict(name="AWS", en="aws", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aws'}, "en": "aws"}),
dict(name="VCenter", en="vcenter", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'cmdb-vcenter'}, "category": "private_cloud", "en": "vcenter"}),
dict(name="KVM", en="kvm", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'ops-KVM'}, "category": "private_cloud", "en": "kvm"}),
dict(name="Nginx", en="nginx", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-nginx'}, "en": "nginx", "collect_key": "nginx"}),
dict(name="Apache", en="apache", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-apache'}, "en": "apache", "collect_key": "apache"}),
dict(name="Tomcat", en="tomcat", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-tomcat'}, "en": "tomcat", "collect_key": "tomcat"}),
dict(name="MySQL", en="mysql", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-mySQL'}, "en": "mysql", "collect_key": "mysql"}),
dict(name="MSSQL", en="mssql", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-SQLServer'}, "en": "mssql", "collect_key": "sqlserver"}),
dict(name="Oracle", en="oracle", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-oracle'}, "en": "oracle", "collect_key": "oracle"}),
dict(name="Redis", en="redis", type=AutoDiscoveryType.COMPONENTS, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-redis'}, "en": "redis", "collect_key": "redis"}),
dict(name="交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False, dict(name="交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-jiaohuanji'}}), option={'icon': {'name': 'caise-jiaohuanji'}}),
@@ -22,32 +45,307 @@ DEFAULT_HTTP = [
option={'icon': {'name': 'caise-dayinji'}}), option={'icon': {'name': 'caise-dayinji'}}),
] ]
ClOUD_MAP = { CLOUD_MAP = {
"aliyun": { "aliyun": [
"categories": ["云服务器 ECS"], {
"map": { "category": "计算",
"云服务器 ECS": "templates/aliyun_ecs.json", "items": ["云服务器 ECS", "云服务器 Disk"],
} "map": {
}, "云服务器 ECS": {"template": "templates/aliyun_ecs.json", "mapping": "ecs"},
"云服务器 Disk": {"template": "templates/aliyun_ecs_disk.json", "mapping": "evs"},
"tencentcloud": { },
"categories": ["云服务器 CVM"], "collect_key_map": {
"map": { "云服务器 ECS": "ali.ecs",
"云服务器 CVM": "templates/tencent_cvm.json", "云服务器 Disk": "ali.ecs_disk",
} },
}, },
{
"huaweicloud": { "category": "网络与CDN",
"categories": ["云服务器 ECS"], "items": [
"map": { "内容分发CDN",
"云服务器 ECS": "templates/huaweicloud_ecs.json", "负载均衡SLB",
} "专有网络VPC",
}, "交换机Switch",
],
"aws": { "map": {
"categories": ["云服务器 EC2"], "内容分发CDN": {"template": "templates/aliyun_cdn.json", "mapping": "CDN"},
"map": { "负载均衡SLB": {"template": "templates/aliyun_slb.json", "mapping": "loadbalancer"},
"云服务器 EC2": "templates/aws_ec2.json", "专有网络VPC": {"template": "templates/aliyun_vpc.json", "mapping": "vpc"},
} "交换机Switch": {"template": "templates/aliyun_switch.json", "mapping": "vswitch"},
}, },
"collect_key_map": {
"内容分发CDN": "ali.cdn",
"负载均衡SLB": "ali.slb",
"专有网络VPC": "ali.vpc",
"交换机Switch": "ali.switch",
},
},
{
"category": "存储",
"items": ["块存储EBS", "对象存储OSS"],
"map": {
"块存储EBS": {"template": "templates/aliyun_ebs.json", "mapping": "evs"},
"对象存储OSS": {"template": "templates/aliyun_oss.json", "mapping": "objectStorage"},
},
"collect_key_map": {
"块存储EBS": "ali.ebs",
"对象存储OSS": "ali.oss",
},
},
{
"category": "数据库",
"items": ["云数据库RDS MySQL", "云数据库RDS PostgreSQL", "云数据库 Redis"],
"map": {
"云数据库RDS MySQL": {"template": "templates/aliyun_rds_mysql.json", "mapping": "mysql"},
"云数据库RDS PostgreSQL": {"template": "templates/aliyun_rds_postgre.json", "mapping": "postgresql"},
"云数据库 Redis": {"template": "templates/aliyun_redis.json", "mapping": "redis"},
},
"collect_key_map": {
"云数据库RDS MySQL": "ali.rds_mysql",
"云数据库RDS PostgreSQL": "ali.rds_postgre",
"云数据库 Redis": "ali.redis",
},
},
],
"tencentcloud": [
{
"category": "计算",
"items": ["云服务器 CVM"],
"map": {
"云服务器 CVM": {"template": "templates/tencent_cvm.json", "mapping": "ecs"},
},
"collect_key_map": {
"云服务器 CVM": "tencent.cvm",
},
},
{
"category": "CDN与边缘",
"items": ["内容分发CDN"],
"map": {
"内容分发CDN": {"template": "templates/tencent_cdn.json", "mapping": "CDN"},
},
"collect_key_map": {
"内容分发CDN": "tencent.cdn",
},
},
{
"category": "网络",
"items": ["负载均衡CLB", "私有网络VPC", "子网"],
"map": {
"负载均衡CLB": {"template": "templates/tencent_clb.json", "mapping": "loadbalancer"},
"私有网络VPC": {"template": "templates/tencent_vpc.json", "mapping": "vpc"},
"子网": {"template": "templates/tencent_subnet.json", "mapping": "vswitch"},
},
"collect_key_map": {
"负载均衡CLB": "tencent.clb",
"私有网络VPC": "tencent.vpc",
"子网": "tencent.subnet",
},
},
{
"category": "存储",
"items": ["云硬盘CBS", "对象存储COS"],
"map": {
"云硬盘CBS": {"template": "templates/tencent_cbs.json", "mapping": "evs"},
"对象存储COS": {"template": "templates/tencent_cos.json", "mapping": "objectStorage"},
},
"collect_key_map": {
"云硬盘CBS": "tencent.cbs",
"对象存储COS": "tencent.cos",
},
},
{
"category": "数据库",
"items": ["云数据库 MySQL", "云数据库 PostgreSQL", "云数据库 Redis"],
"map": {
"云数据库 MySQL": {"template": "templates/tencent_rdb.json", "mapping": "mysql"},
"云数据库 PostgreSQL": {"template": "templates/tencent_postgres.json", "mapping": "postgresql"},
"云数据库 Redis": {"template": "templates/tencent_redis.json", "mapping": "redis"},
},
"collect_key_map": {
"云数据库 MySQL": "tencent.rdb",
"云数据库 PostgreSQL": "tencent.rds_postgres",
"云数据库 Redis": "tencent.redis",
},
},
],
"huaweicloud": [
{
"category": "计算",
"items": ["云服务器 ECS"],
"map": {
"云服务器 ECS": {"template": "templates/huaweicloud_ecs.json", "mapping": "ecs"},
},
"collect_key_map": {
"云服务器 ECS": "huawei.ecs",
},
},
{
"category": "CDN与智能边缘",
"items": ["内容分发网络CDN"],
"map": {
"内容分发网络CDN": {"template": "templates/huawei_cdn.json", "mapping": "CDN"},
},
"collect_key_map": {
"内容分发网络CDN": "huawei.cdn",
},
},
{
"category": "网络",
"items": ["弹性负载均衡ELB", "虚拟私有云VPC", "子网"],
"map": {
"弹性负载均衡ELB": {"template": "templates/huawei_elb.json", "mapping": "loadbalancer"},
"虚拟私有云VPC": {"template": "templates/huawei_vpc.json", "mapping": "vpc"},
"子网": {"template": "templates/huawei_subnet.json", "mapping": "vswitch"},
},
"collect_key_map": {
"弹性负载均衡ELB": "huawei.elb",
"虚拟私有云VPC": "huawei.vpc",
"子网": "huawei.subnet",
},
},
{
"category": "存储",
"items": ["云硬盘EVS", "对象存储OBS"],
"map": {
"云硬盘EVS": {"template": "templates/huawei_evs.json", "mapping": "evs"},
"对象存储OBS": {"template": "templates/huawei_obs.json", "mapping": "objectStorage"},
},
"collect_key_map": {
"云硬盘EVS": "huawei.evs",
"对象存储OBS": "huawei.obs",
},
},
{
"category": "数据库",
"items": ["云数据库RDS MySQL", "云数据库RDS PostgreSQL"],
"map": {
"云数据库RDS MySQL": {"template": "templates/huawei_rds_mysql.json", "mapping": "mysql"},
"云数据库RDS PostgreSQL": {"template": "templates/huawei_rds_postgre.json", "mapping": "postgresql"},
},
"collect_key_map": {
"云数据库RDS MySQL": "huawei.rds_mysql",
"云数据库RDS PostgreSQL": "huawei.rds_postgre",
},
},
{
"category": "应用中间件",
"items": ["分布式缓存Redis"],
"map": {
"分布式缓存Redis": {"template": "templates/huawei_dcs.json", "mapping": "redis"},
},
"collect_key_map": {
"分布式缓存Redis": "huawei.dcs",
},
},
],
"aws": [
{
"category": "计算",
"items": ["云服务器 EC2"],
"map": {
"云服务器 EC2": {"template": "templates/aws_ec2.json", "mapping": "ecs"},
},
"collect_key_map": {
"云服务器 EC2": "aws.ec2",
},
},
{"category": "网络与CDN", "items": [], "map": {}, "collect_key_map": {}},
],
"vcenter": [
{
"category": "计算",
"items": [
"主机",
"虚拟机",
"主机集群"
],
"map": {
"主机": "templates/vsphere_host.json",
"虚拟机": "templates/vsphere_vm.json",
"主机集群": "templates/vsphere_cluster.json",
},
"collect_key_map": {
"主机": "vsphere.host",
"虚拟机": "vsphere.vm",
"主机集群": "vsphere.cluster",
},
},
{
"category": "网络",
"items": [
"网络",
"标准交换机",
"分布式交换机",
],
"map": {
"网络": "templates/vsphere_network.json",
"标准交换机": "templates/vsphere_standard_switch.json",
"分布式交换机": "templates/vsphere_distributed_switch.json",
},
"collect_key_map": {
"网络": "vsphere.network",
"标准交换机": "vsphere.standard_switch",
"分布式交换机": "vsphere.distributed_switch",
},
},
{
"category": "存储",
"items": ["数据存储", "数据存储集群"],
"map": {
"数据存储": "templates/vsphere_datastore.json",
"数据存储集群": "templates/vsphere_storage_pod.json",
},
"collect_key_map": {
"数据存储": "vsphere.datastore",
"数据存储集群": "vsphere.storage_pod",
},
},
{
"category": "其他",
"items": ["资源池", "数据中心", "文件夹"],
"map": {
"资源池": "templates/vsphere_pool.json",
"数据中心": "templates/vsphere_datacenter.json",
"文件夹": "templates/vsphere_folder.json",
},
"collect_key_map": {
"资源池": "vsphere.pool",
"数据中心": "vsphere.datacenter",
"文件夹": "vsphere.folder",
},
},
],
"kvm": [
{
"category": "计算",
"items": ["虚拟机"],
"map": {
"虚拟机": "templates/kvm_vm.json",
},
"collect_key_map": {
"虚拟机": "kvm.vm",
},
},
{
"category": "存储",
"items": ["存储"],
"map": {
"存储": "templates/kvm_storage.json",
},
"collect_key_map": {
"存储": "kvm.storage",
},
},
{
"category": "network",
"items": ["网络"],
"map": {
"网络": "templates/kvm_network.json",
},
"collect_key_map": {
"网络": "kvm.network",
},
},
],
} }

View File

@@ -2,12 +2,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime
import os
import yaml
from flask import current_app from flask import current_app
from api.extensions import cache from api.extensions import cache
from api.extensions import db from api.extensions import db
from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.models.cmdb import Attribute from api.models.cmdb import Attribute, AutoDiscoveryExecHistory
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import AutoDiscoveryCounter
from api.models.cmdb import AutoDiscoveryRuleSyncHistory
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIType from api.models.cmdb import CIType
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
@@ -448,7 +457,67 @@ class CMDBCounterCache(object):
cache.set(cls.KEY2, result, timeout=0) cache.set(cls.KEY2, result, timeout=0)
return result res = db.session.query(AutoDiscoveryCI.created_at,
AutoDiscoveryCI.updated_at,
AutoDiscoveryCI.adt_id,
AutoDiscoveryCI.type_id,
AutoDiscoveryCI.is_accept).filter(AutoDiscoveryCI.deleted.is_(False))
today = datetime.datetime.today()
this_month = datetime.datetime(today.year, today.month, 1)
last_month = this_month - datetime.timedelta(days=1)
last_month = datetime.datetime(last_month.year, last_month.month, 1)
this_week = today - datetime.timedelta(days=datetime.date.weekday(today))
this_week = datetime.datetime(this_week.year, this_week.month, this_week.day)
last_week = this_week - datetime.timedelta(days=7)
last_week = datetime.datetime(last_week.year, last_week.month, last_week.day)
result = dict()
for i in res:
if i.type_id not in result:
result[i.type_id] = dict(instance_count=0, accept_count=0,
this_month_count=0, this_week_count=0, last_month_count=0, last_week_count=0)
adts = AutoDiscoveryCIType.get_by(type_id=i.type_id, to_dict=False)
result[i.type_id]['rule_count'] = len(adts) + AutoDiscoveryCITypeRelation.get_by(
ad_type_id=i.type_id, only_query=True).count()
result[i.type_id]['exec_target_count'] = len(
set([i.oneagent_id for adt in adts for i in db.session.query(
AutoDiscoveryRuleSyncHistory.oneagent_id).filter(
AutoDiscoveryRuleSyncHistory.adt_id == adt.id)]))
result[i.type_id]['instance_count'] += 1
if i.is_accept:
result[i.type_id]['accept_count'] += 1
if last_month <= i.created_at < this_month:
result[i.type_id]['last_month_count'] += 1
elif i.created_at >= this_month:
result[i.type_id]['this_month_count'] += 1
if last_week <= i.created_at < this_week:
result[i.type_id]['last_week_count'] += 1
elif i.created_at >= this_week:
result[i.type_id]['this_week_count'] += 1
for type_id in result:
existed = AutoDiscoveryCounter.get_by(type_id=type_id, first=True, to_dict=False)
if existed is None:
AutoDiscoveryCounter.create(type_id=type_id, **result[type_id])
else:
existed.update(**result[type_id])
for i in AutoDiscoveryCounter.get_by(to_dict=False):
if i.type_id not in result:
i.delete()
@classmethod
def clear_ad_exec_history(cls):
ci_types = CIType.get_by(to_dict=False)
for ci_type in ci_types:
for i in AutoDiscoveryExecHistory.get_by(type_id=ci_type.id, only_query=True).order_by(
AutoDiscoveryExecHistory.id.desc()).offset(50000):
i.delete(commit=False)
db.session.commit()
@classmethod @classmethod
def get_adc_counter(cls): def get_adc_counter(cls):
@@ -479,3 +548,20 @@ class CMDBCounterCache(object):
@classmethod @classmethod
def get_sub_counter(cls): def get_sub_counter(cls):
return cache.get(cls.KEY3) or cls.flush_sub_counter() return cache.get(cls.KEY3) or cls.flush_sub_counter()
class AutoDiscoveryMappingCache(object):
PREFIX = 'CMDB::AutoDiscovery::Mapping::{}'
@classmethod
def get(cls, name):
res = cache.get(cls.PREFIX.format(name)) or {}
if not res:
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "auto_discovery/mapping/{}.yaml".format(name))
if os.path.exists(path):
with open(path, 'r') as f:
mapping = yaml.safe_load(f)
res = mapping.get('mapping') or {}
res and cache.set(cls.PREFIX.format(name), res, timeout=0)
return res

View File

@@ -4,12 +4,12 @@
import copy import copy
import datetime import datetime
import json import json
import threading
import redis_lock import redis_lock
import threading
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
from sqlalchemy.orm import aliased
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
@@ -28,6 +28,7 @@ from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
@@ -217,13 +218,13 @@ class CIManager(object):
@classmethod @classmethod
def get_ad_statistics(cls): def get_ad_statistics(cls):
return CMDBCounterCache.get_adc_counter() return CMDBCounterCache.get_adc_counter() or {}
@staticmethod @staticmethod
def ci_is_exist(unique_key, unique_value, type_id): def ci_is_exist(unique_key, unique_value, type_id):
""" """
:param unique_key: is a attribute :param unique_key: is an attribute
:param unique_value: :param unique_value:
:param type_id: :param type_id:
:return: :return:
@@ -264,9 +265,11 @@ class CIManager(object):
for attr_id in constraint.attr_ids: for attr_id in constraint.attr_ids:
value_table = TableMap(attr_name=id2name[attr_id]).table value_table = TableMap(attr_name=id2name[attr_id]).table
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id, values = value_table.get_by(attr_id=attr_id,
to_dict=False, value=ci_dict.get(id2name[attr_id]) or None,
value=ci_dict.get(id2name[attr_id]) or None)]) only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
_ci_ids = set([i.ci_id for i in values])
if ci_ids is None: if ci_ids is None:
ci_ids = _ci_ids ci_ids = _ci_ids
else: else:
@@ -316,8 +319,8 @@ class CIManager(object):
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id))) 400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
unique_value = None unique_value = None
if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and # primary key is not auto inc id
not ci_dict.get(unique_key.name)): # primary key is not auto inc id if not (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID):
unique_value = ci_dict.get(unique_key.name) or ci_dict.get(unique_key.alias) or ci_dict.get(unique_key.id) unique_value = ci_dict.get(unique_key.name) 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)) unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
@@ -379,17 +382,15 @@ class CIManager(object):
for _, attr in attrs: for _, attr in attrs:
if attr.is_computed: if attr.is_computed:
computed_attrs.append(attr.to_dict()) computed_attrs.append(attr.to_dict())
ci_dict[attr.name] = None
elif attr.is_password: elif attr.is_password:
if attr.name in ci_dict: if attr.name in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.name) password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
if attr.re_check and password_dict.get(attr.id): if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, password_dict[attr.id]) value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id) cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
@@ -416,10 +417,14 @@ class CIManager(object):
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id, key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr) ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
try: try:
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery) ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id) record_id, has_dynamic = value_manager.create_or_update_attr_value(
ci, ci_dict, key2attr, ticket_id=ticket_id)
except BadRequest as e: except BadRequest as e:
if existed is None: if existed is None:
cls.delete(ci.id) cls.delete(ci.id)
@@ -429,7 +434,7 @@ class CIManager(object):
for attr_id in password_dict: for attr_id in password_dict:
record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id) record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id)
if record_id: # has change if record_id or has_dynamic: # has changed
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
if ref_ci_dict: # add relations if ref_ci_dict: # add relations
@@ -437,14 +442,17 @@ class CIManager(object):
return ci.id return ci.id
def update(self, ci_id, _is_admin=False, ticket_id=None, **ci_dict): def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
raw_dict = copy.deepcopy(ci_dict)
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id) attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
raw_dict = copy.deepcopy(ci_dict)
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
for _, attr in attrs: for _, attr in attrs:
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
@@ -457,17 +465,15 @@ class CIManager(object):
for _, attr in attrs: for _, attr in attrs:
if attr.is_computed: if attr.is_computed:
computed_attrs.append(attr.to_dict()) computed_attrs.append(attr.to_dict())
ci_dict[attr.name] = None
elif attr.is_password: elif attr.is_password:
if attr.name in ci_dict: if attr.name in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.name) password_dict[attr.id] = (ci_dict.pop(attr.name), attr.is_dynamic)
elif attr.alias in ci_dict: elif attr.alias in ci_dict:
password_dict[attr.id] = ci_dict.pop(attr.alias) password_dict[attr.id] = (ci_dict.pop(attr.alias), attr.is_dynamic)
if attr.re_check and password_dict.get(attr.id): if attr.re_check and password_dict.get(attr.id):
value_manager.check_re(attr.re_check, password_dict[attr.id]) value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {} limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
@@ -480,6 +486,10 @@ class CIManager(object):
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name} ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name, key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
ci_attr2type_attr=ci_attr2type_attr) ci_attr2type_attr=ci_attr2type_attr)
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
if limit_attrs: if limit_attrs:
for k in copy.deepcopy(ci_dict): for k in copy.deepcopy(ci_dict):
if k not in limit_attrs: if k not in limit_attrs:
@@ -489,7 +499,8 @@ class CIManager(object):
ci_dict.pop(k) ci_dict.pop(k)
try: try:
record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr, ticket_id=ticket_id) record_id, has_dynamic = value_manager.create_or_update_attr_value(
ci, ci_dict, key2attr, ticket_id=ticket_id)
except BadRequest as e: except BadRequest as e:
raise e raise e
@@ -497,19 +508,25 @@ class CIManager(object):
for attr_id in password_dict: for attr_id in password_dict:
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id) record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
if record_id: # has change if record_id or has_dynamic: # has changed
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE) if not __sync:
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
else:
ci_cache(ci_id, OperateType.UPDATE, record_id)
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k} ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
if ref_ci_dict: if ref_ci_dict:
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE) if not __sync:
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
else:
ci_relation_add(ref_ci_dict, ci.id)
@staticmethod @staticmethod
def update_unique_value(ci_id, unique_name, unique_value): def update_unique_value(ci_id, unique_name, unique_value):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
key2attr = {unique_name: AttributeCache.get(unique_name)} key2attr = {unique_name: AttributeCache.get(unique_name)}
record_id = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr) record_id, _ = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr)
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
@@ -724,7 +741,7 @@ class CIManager(object):
fields=None, value_tables=None, unique_required=False, excludes=None): fields=None, value_tables=None, unique_required=False, excludes=None):
""" """
:param ci_ids: list of CI instance ID, eg. ['1', '2'] :param ci_ids: list of CI instance ID, e.g. ['1', '2']
:param ret_key: name, id or alias :param ret_key: name, id or alias
:param fields: :param fields:
:param value_tables: :param value_tables:
@@ -749,6 +766,7 @@ class CIManager(object):
@classmethod @classmethod
def save_password(cls, ci_id, attr_id, value, record_id, type_id): def save_password(cls, ci_id, attr_id, value, record_id, type_id):
value, is_dynamic = value
changed = None changed = None
encrypt_value = None encrypt_value = None
value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD] value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD]
@@ -765,14 +783,18 @@ class CIManager(object):
if existed is None: if existed is None:
if value: if value:
value_table.create(ci_id=ci_id, attr_id=attr_id, value=encrypt_value) value_table.create(ci_id=ci_id, attr_id=attr_id, value=encrypt_value)
changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)] if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.ADD, '', PASSWORD_DEFAULT_SHOW, type_id)]
elif existed.value != encrypt_value: elif existed.value != encrypt_value:
if value: if value:
existed.update(ci_id=ci_id, attr_id=attr_id, value=encrypt_value) existed.update(ci_id=ci_id, attr_id=attr_id, value=encrypt_value)
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW, PASSWORD_DEFAULT_SHOW, type_id)] if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.UPDATE, PASSWORD_DEFAULT_SHOW,
PASSWORD_DEFAULT_SHOW, type_id)]
else: else:
existed.delete() existed.delete()
changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)] if not is_dynamic:
changed = [(ci_id, attr_id, OperateType.DELETE, PASSWORD_DEFAULT_SHOW, '', type_id)]
if current_app.config.get('SECRETS_ENGINE') == 'vault': if current_app.config.get('SECRETS_ENGINE') == 'vault':
vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN')) vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN'))
@@ -825,6 +847,149 @@ class CIManager(object):
return data.get('v') return data.get('v')
def baseline(self, ci_ids, before_date):
"""
return CI changes
:param ci_ids:
:param before_date:
:return:
"""
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
if not ci_list:
return dict()
ci2changed = dict()
changed = AttributeHistoryManger.get_records_for_attributes(
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
for records in changed:
for change in records[1]:
if change['is_computed'] or change['is_password']:
continue
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
continue
ci2changed.setdefault(change['ci_id'], {})
item = (change['old'],
change['new'],
change.get('is_list'),
change.get('value_type'),
change['operate_type'])
if change.get('is_list'):
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
else:
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
type2show_name = {}
result = []
for ci in ci_list:
list_attr2item = {}
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
if not alias_name:
continue
if alias_name == ci.get('unique_alias'):
continue
if ci.get('_type') not in type2show_name:
ci_type = CITypeCache.get(ci.get('_type'))
show_id = ci_type.show_id or ci_type.unique_id
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
if isinstance(v, list):
for old, new, is_list, value_type, operate_type in v:
if alias_name not in list_attr2item:
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
attr_name=alias_name,
value_type=value_type,
is_list=is_list,
ci_type=ci.get('ci_type'),
unique_alias=ci.get('unique_alias'),
unique_value=ci.get(ci['unique_alias']),
cur=copy.deepcopy(ci.get(alias_name)),
to=ci.get(alias_name) or [])
old = ValueTypeMap.deserialize[value_type](old) if old else old
new = ValueTypeMap.deserialize[value_type](new) if new else new
if operate_type == OperateType.ADD:
list_attr2item[alias_name]['to'].remove(new)
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
list_attr2item[alias_name]['to'].append(old)
continue
old, value_type = v[0], v[3]
old = ValueTypeMap.deserialize[value_type](old) if old else old
if isinstance(old, (datetime.datetime, datetime.date)):
old = str(old)
if ci.get(alias_name) != old:
item = dict(instance=ci.get(type2show_name[ci['_type']]),
attr_name=alias_name,
value_type=value_type,
ci_type=ci.get('ci_type'),
unique_alias=ci.get('unique_alias'),
unique_value=ci.get(ci['unique_alias']),
cur=ci.get(alias_name),
to=old)
result.append(item)
for alias_name, item in list_attr2item.items():
if sorted(item['cur'] or []) != sorted(item['to'] or []):
result.append(item)
return result
def baseline_cis(self, ci_ids, before_date, fl=None):
"""
return CI changes
:param ci_ids:
:param before_date:
:param fl:
:return:
"""
ci_list = self.get_cis_by_ids(ci_ids, fields=fl)
if not ci_list:
return []
id2ci = {ci['_id']: ci for ci in ci_list}
changed = AttributeHistoryManger.get_records_for_attributes(
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
for records in changed:
for change in records[1]:
if change['is_computed'] or change['is_password']:
continue
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
continue
if change['is_list']:
old, new, value_type, operate_type, ci_id, attr_name = (
change['old'], change['new'], change['value_type'], change['operate_type'],
change['ci_id'], change['attr_name'])
old = ValueTypeMap.deserialize[value_type](old) if old else old
new = ValueTypeMap.deserialize[value_type](new) if new else new
if operate_type == OperateType.ADD and new in (id2ci[ci_id][attr_name] or []):
id2ci[ci_id][attr_name].remove(new)
elif operate_type == OperateType.DELETE and old not in id2ci[ci_id][attr_name]:
id2ci[ci_id][attr_name].append(old)
else:
id2ci[change['ci_id']][change['attr_name']] = change['old']
return list(id2ci.values())
def rollback(self, ci_id, before_date):
baseline_ci = self.baseline([ci_id], before_date)
payload = dict()
for item in baseline_ci:
payload[item.get('attr_name')] = item.get('to')
if payload:
payload['ci_type'] = baseline_ci[0]['ci_type']
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
self.update(ci_id, **payload)
return payload
class CIRelationManager(object): class CIRelationManager(object):
""" """
@@ -935,6 +1100,18 @@ class CIRelationManager(object):
return ci_ids, level2ids return ci_ids, level2ids
@classmethod
def get_parent_ids(cls, ci_ids):
cis = db.session.query(CIRelation.first_ci_id, CIRelation.second_ci_id, CI.type_id).join(
CI, CI.id == CIRelation.first_ci_id).filter(
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
result = {}
for ci in cis:
result.setdefault(ci.second_ci_id, []).append((ci.first_ci_id, ci.type_id))
return result
@staticmethod @staticmethod
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation): def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
db.session.commit() db.session.commit()
@@ -960,7 +1137,14 @@ class CIRelationManager(object):
return abort(400, ErrFormat.relation_constraint.format("1-N")) return abort(400, ErrFormat.relation_constraint.format("1-N"))
@classmethod @classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True): def add(cls, first_ci_id, second_ci_id,
more=None,
relation_type_id=None,
ancestor_ids=None,
valid=True,
apply_async=True,
source=None,
uid=None):
first_ci = CIManager.confirm_ci_existed(first_ci_id) first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id) second_ci = CIManager.confirm_ci_existed(second_ci_id)
@@ -972,9 +1156,10 @@ class CIRelationManager(object):
first=True) first=True)
if existed is not None: if existed is not None:
if existed.relation_type_id != relation_type_id and relation_type_id is not None: if existed.relation_type_id != relation_type_id and relation_type_id is not None:
existed.update(relation_type_id=relation_type_id) source = existed.source or source
existed.update(relation_type_id=relation_type_id, source=source)
CIRelationHistoryManager().add(existed, OperateType.UPDATE) CIRelationHistoryManager().add(existed, OperateType.UPDATE, uid=uid)
else: else:
if relation_type_id is None: if relation_type_id is None:
type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id, type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id,
@@ -985,7 +1170,7 @@ class CIRelationManager(object):
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format( relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
first_ci.ci_type.name, second_ci.ci_type.name))) first_ci.ci_type.name, second_ci.ci_type.name)))
if current_app.config.get('USE_ACL') and valid: if current_app.config.get('USE_ACL') and valid and current_user.username != 'worker':
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name, resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
second_ci.ci_type.name) second_ci.ci_type.name)
if not ACLManager().has_permission( if not ACLManager().has_permission(
@@ -1004,11 +1189,13 @@ class CIRelationManager(object):
existed = CIRelation.create(first_ci_id=first_ci_id, existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id, second_ci_id=second_ci_id,
relation_type_id=relation_type_id, relation_type_id=relation_type_id,
ancestor_ids=ancestor_ids) ancestor_ids=ancestor_ids,
source=source)
CIRelationHistoryManager().add(existed, OperateType.ADD) CIRelationHistoryManager().add(existed, OperateType.ADD, uid=uid)
if apply_async:
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE) ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
else:
ci_relation_cache(first_ci_id, second_ci_id, ancestor_ids)
if more is not None: if more is not None:
existed.upadte(more=more) existed.upadte(more=more)
@@ -1016,10 +1203,10 @@ class CIRelationManager(object):
return existed.id return existed.id
@staticmethod @staticmethod
def delete(cr_id): def delete(cr_id, apply_async=True):
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id))) cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
if current_app.config.get('USE_ACL'): if current_app.config.get('USE_ACL') and current_user.username != 'worker':
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name) resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
if not ACLManager().has_permission( if not ACLManager().has_permission(
resource_name, resource_name,
@@ -1032,8 +1219,12 @@ class CIRelationManager(object):
his_manager = CIRelationHistoryManager() his_manager = CIRelationHistoryManager()
his_manager.add(cr, operate_type=OperateType.DELETE) his_manager.add(cr, operate_type=OperateType.DELETE)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE) if apply_async:
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE) ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
else:
ci_relation_delete(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids)
delete_id_filter(cr.second_ci_id)
return cr_id return cr_id
@@ -1048,8 +1239,23 @@ class CIRelationManager(object):
if cr is not None: if cr is not None:
cls.delete(cr.id) cls.delete(cr.id)
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE) # ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE) # delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
return cr
@classmethod
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
to_dict=False,
first=True)
if cr is not None:
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
cls.delete(cr.id, apply_async=apply_async)
return cr return cr
@@ -1088,56 +1294,140 @@ class CIRelationManager(object):
for ci_id in ci_ids: for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids) cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
@classmethod
def delete_relations_by_source(cls, source,
first_ci_id=None, second_ci_type_id=None,
second_ci_id=None, first_ci_type_id=None,
added=None):
existed = []
if first_ci_id is not None and second_ci_type_id is not None:
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
source=source, first_ci_id=first_ci_id, only_query=True).join(
CI, CIRelation.second_ci_id == CI.id).filter(CI.type_id == second_ci_type_id)]
if second_ci_id is not None and first_ci_type_id is not None:
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
source=source, second_ci_id=second_ci_id, only_query=True).join(
CI, CIRelation.first_ci_id == CI.id).filter(CI.type_id == first_ci_type_id)]
deleted = set(existed) - set(added or [])
for first, second in deleted:
cls.delete_3(first, second, apply_async=False)
@classmethod @classmethod
def build_by_attribute(cls, ci_dict): def build_by_attribute(cls, ci_dict):
type_id = ci_dict['_type'] type_id = ci_dict['_type']
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter( child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
CITypeRelation.parent_attr_id.isnot(None)) CITypeRelation.parent_attr_ids.isnot(None))
for item in child_items: for item in child_items:
parent_attr = AttributeCache.get(item.parent_attr_id) relations = None
child_attr = AttributeCache.get(item.child_attr_id) for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
attr_value = ci_dict.get(parent_attr.name) _relations = set()
value_table = TableMap(attr=child_attr).table parent_attr = AttributeCache.get(parent_attr_id)
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join( child_attr = AttributeCache.get(child_attr_id)
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id): attr_value = ci_dict.get(parent_attr.name)
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False) value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
_relations.add((ci_dict['_id'], child.ci_id))
if relations is None:
relations = _relations
else:
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
first_ci_id=ci_dict['_id'],
second_ci_type_id=item.child_id,
added=relations)
for parent_ci_id, child_ci_id in (relations or []):
cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter( parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
CITypeRelation.child_attr_id.isnot(None)) CITypeRelation.child_attr_ids.isnot(None))
for item in parent_items: for item in parent_items:
parent_attr = AttributeCache.get(item.parent_attr_id) relations = None
child_attr = AttributeCache.get(item.child_attr_id) for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
attr_value = ci_dict.get(child_attr.name) _relations = set()
value_table = TableMap(attr=parent_attr).table parent_attr = AttributeCache.get(parent_attr_id)
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join( child_attr = AttributeCache.get(child_attr_id)
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id): attr_value = ci_dict.get(child_attr.name)
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False) value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
_relations.add((parent.ci_id, ci_dict['_id']))
if relations is None:
relations = _relations
else:
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
second_ci_id=ci_dict['_id'],
first_ci_type_id=item.parent_id,
added=relations)
for parent_ci_id, child_ci_id in (relations or []):
cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
@classmethod @classmethod
def rebuild_all_by_attribute(cls, ci_type_relation): def rebuild_all_by_attribute(cls, ci_type_relation, uid):
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id']) relations = None
child_attr = AttributeCache.get(ci_type_relation['child_attr_id']) for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
if not parent_attr or not child_attr: ci_type_relation['child_attr_ids'] or []):
return
parent_value_table = TableMap(attr=parent_attr).table _relations = set()
child_value_table = TableMap(attr=child_attr).table parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
if not parent_attr or not child_attr:
continue
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join( parent_value_table = TableMap(attr=parent_attr).table
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id']) child_value_table = TableMap(attr=child_attr).table
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
child_value2ci_ids = {} parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
for child in child_values: CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id) child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
for parent in parent_values: child_value2ci_ids = {}
for child_ci_id in child_value2ci_ids.get(parent.value, []): for child in child_values:
try: child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
cls.add(parent.ci_id, child_ci_id, valid=False)
except: for parent in parent_values:
pass for child_ci_id in child_value2ci_ids.get(parent.value, []):
_relations.add((parent.ci_id, child_ci_id))
if relations is None:
relations = _relations
else:
relations &= _relations
t1 = aliased(CI)
t2 = aliased(CI)
query = db.session.query(CIRelation).join(t1, t1.id == CIRelation.first_ci_id).join(
t2, t2.id == CIRelation.second_ci_id).filter(t1.type_id == ci_type_relation['parent_id']).filter(
t2.type_id == ci_type_relation['child_id'])
for i in query:
db.session.delete(i)
ci_relation_delete(i.first_ci_id, i.second_ci_id, i.ancestor_ids)
try:
db.session.commit()
except Exception as e:
current_app.logger.error(e)
db.session.rollback()
for parent_ci_id, child_ci_id in (relations or []):
try:
cls.add(parent_ci_id, child_ci_id,
valid=False,
apply_async=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES,
uid=uid)
except Exception as e:
current_app.logger.error(e)
class CITriggerManager(object): class CITriggerManager(object):

View File

@@ -1,11 +1,9 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy import copy
import toposort import toposort
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import session
from flask_login import current_user from flask_login import current_user
from toposort import toposort_flatten from toposort import toposort_flatten
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
@@ -28,12 +26,15 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.relation_type import RelationTypeManager from api.lib.cmdb.relation_type import RelationTypeManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.value import AttributeValueManager from api.lib.cmdb.value import AttributeValueManager
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import kwargs_required from api.lib.decorator import kwargs_required
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIFilterPerms from api.models.cmdb import CIFilterPerms
from api.models.cmdb import CIType from api.models.cmdb import CIType
@@ -53,6 +54,7 @@ from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import RelationType from api.models.cmdb import RelationType
from api.models.cmdb import TopologyView
class CITypeManager(object): class CITypeManager(object):
@@ -81,7 +83,7 @@ class CITypeManager(object):
self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))} self.cls.id, self.cls.icon, self.cls.name).filter(self.cls.deleted.is_(False))}
@staticmethod @staticmethod
def get_ci_types(type_name=None, like=True): def get_ci_types(type_name=None, like=True, type_ids=None):
resources = None resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)]) resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)])
@@ -90,8 +92,14 @@ class CITypeManager(object):
CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name)) CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name))
res = list() res = list()
for type_dict in ci_types: for type_dict in ci_types:
if type_ids is not None and type_dict['id'] not in type_ids:
continue
attr = AttributeCache.get(type_dict["unique_id"]) attr = AttributeCache.get(type_dict["unique_id"])
type_dict["unique_key"] = attr and attr.name type_dict["unique_key"] = attr and attr.name
if type_dict.get('show_id'):
attr = AttributeCache.get(type_dict["show_id"])
type_dict["show_name"] = attr and attr.name
type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id']) type_dict['parent_ids'] = CITypeInheritanceManager.get_parents(type_dict['id'])
if resources is None or type_dict['name'] in resources: if resources is None or type_dict['name'] in resources:
res.append(type_dict) res.append(type_dict)
@@ -127,7 +135,9 @@ class CITypeManager(object):
def add(cls, **kwargs): def add(cls, **kwargs):
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}: if ErrFormat.ci_type_config not in {i['name'] for i in ACLManager().get_resources(ResourceTypeEnum.PAGE)}:
return abort(403, ErrFormat.no_permission2) app_cli = CMDBApp()
validate_permission(app_cli.op.Model_Configuration, app_cli.resource_type_name,
app_cli.op.create_CIType, app_cli.app_name)
unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None) unique_key = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define) unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
@@ -192,7 +202,7 @@ class CITypeManager(object):
CITypeAttributeManager.update(type_id, [attr]) CITypeAttributeManager.update(type_id, [attr])
ci_type2 = ci_type.to_dict() ci_type2 = ci_type.to_dict()
new = ci_type.update(**kwargs) new = ci_type.update(**kwargs, filter_none=False)
CITypeCache.clean(type_id) CITypeCache.clean(type_id)
@@ -221,6 +231,9 @@ class CITypeManager(object):
if CI.get_by(type_id=type_id, first=True, to_dict=False) is not None: 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) return abort(400, ErrFormat.ci_exists_and_cannot_delete_type)
if CITypeInheritance.get_by(parent_id=type_id, first=True):
return abort(400, ErrFormat.ci_type_inheritance_cannot_delete)
relation_views = PreferenceRelationView.get_by(to_dict=False) relation_views = PreferenceRelationView.get_by(to_dict=False)
for rv in relation_views: for rv in relation_views:
for item in (rv.cr_ids or []): for item in (rv.cr_ids or []):
@@ -244,10 +257,26 @@ class CITypeManager(object):
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False): for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(ad_type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(peer_type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False): for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
item.delete(commit=False) item.soft_delete(commit=False)
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False): for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.soft_delete(commit=False)
try:
from api.models.cmdb import CITypeReconciliation
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
item.soft_delete(commit=False)
except Exception:
pass
for item in TopologyView.get_by(central_node_type=type_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)
db.session.commit() db.session.commit()
@@ -265,6 +294,12 @@ class CITypeManager(object):
class CITypeInheritanceManager(object): class CITypeInheritanceManager(object):
cls = CITypeInheritance cls = CITypeInheritance
@classmethod
def get_all(cls, type_ids=None):
res = cls.cls.get_by(to_dict=True)
return [i for i in res if type_ids is None or (i['parent_id'] in type_ids and i['child_id'] in type_ids)]
@classmethod @classmethod
def get_parents(cls, type_id): def get_parents(cls, type_id):
return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)] return [i.parent_id for i in cls.cls.get_by(child_id=type_id, to_dict=False)]
@@ -360,7 +395,7 @@ class CITypeGroupManager(object):
cls = CITypeGroup cls = CITypeGroup
@staticmethod @staticmethod
def get(need_other=None, config_required=True): def get(need_other=None, config_required=True, type_ids=None, ci_types=None):
resources = None resources = None
if current_app.config.get('USE_ACL'): if current_app.config.get('USE_ACL'):
resources = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI) resources = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI)
@@ -374,6 +409,8 @@ class CITypeGroupManager(object):
for group in groups: for group in groups:
for t in sorted(CITypeGroupItem.get_by(group_id=group['id']), key=lambda x: x['order'] or 0): for t in sorted(CITypeGroupItem.get_by(group_id=group['id']), key=lambda x: x['order'] or 0):
ci_type = CITypeCache.get(t['type_id']).to_dict() ci_type = CITypeCache.get(t['type_id']).to_dict()
if type_ids is not None and ci_type['id'] not in type_ids:
continue
if resources is None or (ci_type and ci_type['name'] in resources): if resources is None or (ci_type and ci_type['name'] in resources):
ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None ci_type['permissions'] = resources[ci_type['name']] if resources is not None else None
ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False ci_type['inherited'] = True if CITypeInheritanceManager.get_parents(ci_type['id']) else False
@@ -381,7 +418,7 @@ class CITypeGroupManager(object):
group_types.add(t["type_id"]) group_types.add(t["type_id"])
if need_other: if need_other:
ci_types = CITypeManager.get_ci_types() ci_types = CITypeManager.get_ci_types(type_ids=type_ids) if ci_types is None else ci_types
other_types = dict(ci_types=[]) other_types = dict(ci_types=[])
for ci_type in ci_types: for ci_type in ci_types:
if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources): if ci_type["id"] not in group_types and (resources is None or ci_type['name'] in resources):
@@ -411,9 +448,6 @@ class CITypeGroupManager(object):
existed = CITypeGroup.get_by_id(gid) or abort( existed = CITypeGroup.get_by_id(gid) or abort(
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid))) 404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
if name is not None and name != existed.name: if name is not None and name != existed.name:
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
existed.update(name=name) existed.update(name=name)
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0]) max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
@@ -505,6 +539,8 @@ class CITypeAttributeManager(object):
attrs = CITypeAttributesCache.get(_type_id) attrs = CITypeAttributesCache.get(_type_id)
for attr in sorted(attrs, key=lambda x: (x.order, x.id)): for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse) attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
if not attr_dict:
continue
attr_dict["is_required"] = attr.is_required attr_dict["is_required"] = attr.is_required
attr_dict["order"] = attr.order attr_dict["order"] = attr.order
attr_dict["default_show"] = attr.default_show attr_dict["default_show"] = attr.default_show
@@ -513,7 +549,6 @@ class CITypeAttributeManager(object):
if not has_config_perm: if not has_config_perm:
attr_dict.pop('choice_web_hook', None) attr_dict.pop('choice_web_hook', None)
attr_dict.pop('choice_other', None) attr_dict.pop('choice_other', None)
if attr_dict['id'] not in id2pos: if attr_dict['id'] not in id2pos:
id2pos[attr_dict['id']] = len(result) id2pos[attr_dict['id']] = len(result)
result.append(attr_dict) result.append(attr_dict)
@@ -578,7 +613,6 @@ class CITypeAttributeManager(object):
if existed is not None: if existed is not None:
continue continue
current_app.logger.debug(attr_id)
CITypeAttribute.create(type_id=type_id, attr_id=attr_id, **kwargs) CITypeAttribute.create(type_id=type_id, attr_id=attr_id, **kwargs)
attr = AttributeCache.get(attr_id) attr = AttributeCache.get(attr_id)
@@ -672,14 +706,27 @@ class CITypeAttributeManager(object):
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True) item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
item and item.soft_delete(commit=False) item and item.soft_delete(commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) + for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)): CITypeRelation.get_by(child_id=type_id, to_dict=False)):
item.soft_delete(commit=False) if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.parent_attr_ids.index(attr_id)
elif item.child_id == type_id and attr_id in (item.child_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.child_attr_ids.index(attr_id)
else:
continue
item.update(parent_attr_ids=item_dict['parent_attr_ids'].pop(pop_idx),
child_attr_ids=item_dict['child_attr_ids'].pop(pop_idx),
commit=False)
db.session.commit() db.session.commit()
CITypeAttributeCache.clean(type_id, attr_id) CITypeAttributeCache.clean(type_id, attr_id)
if ci_type.show_id == attr_id:
ci_type.update(show_id=None, filter_none=False)
CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id, CITypeHistoryManager.add(CITypeOperateType.DELETE_ATTRIBUTE, type_id, attr_id=attr.id,
change=attr and attr.to_dict()) change=attr and attr.to_dict())
@@ -732,21 +779,29 @@ class CITypeRelationManager(object):
""" """
@staticmethod @staticmethod
def get(): def get(type_ids=None):
res = CITypeRelation.get_by(to_dict=False) res = CITypeRelation.get_by(to_dict=False)
type2attributes = dict() type2attributes = dict()
result = []
for idx, item in enumerate(res): for idx, item in enumerate(res):
_item = item.to_dict() _item = item.to_dict()
if type_ids is not None and _item['parent_id'] not in type_ids and _item['child_id'] not in type_ids:
continue
res[idx] = _item res[idx] = _item
res[idx]['parent'] = item.parent.to_dict() res[idx]['parent'] = item.parent.to_dict()
if item.parent_id not in type2attributes: if item.parent_id not in type2attributes:
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)] type2attributes[item.parent_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.parent_id)]
res[idx]['child'] = item.child.to_dict() res[idx]['child'] = item.child.to_dict()
if item.child_id not in type2attributes: if item.child_id not in type2attributes:
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)] type2attributes[item.child_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.child_id)]
res[idx]['relation_type'] = item.relation_type.to_dict() res[idx]['relation_type'] = item.relation_type.to_dict()
return res, type2attributes result.append(res[idx])
return result, type2attributes
@staticmethod @staticmethod
def get_child_type_ids(type_id, level): def get_child_type_ids(type_id, level):
@@ -778,8 +833,8 @@ class CITypeRelationManager(object):
ci_type_dict["relation_type"] = relation_inst.relation_type.name ci_type_dict["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint ci_type_dict["constraint"] = relation_inst.constraint
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids
return ci_type_dict return ci_type_dict
@@ -812,6 +867,72 @@ class CITypeRelationManager(object):
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents] return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
@staticmethod
def get_relations_by_type_id(type_id):
nodes, edges = [], []
node_ids, edge_tuples = set(), set()
ci_type = CITypeCache.get(type_id)
if ci_type is None:
return nodes, edges
ci_type_dict = ci_type.to_dict()
ci_type_dict.setdefault('level', [0])
nodes.append(ci_type_dict)
node_ids.add(ci_type.id)
def _find(_id, lv):
lv += 1
for i in CITypeRelation.get_by(parent_id=_id, to_dict=False):
if i.child_id not in node_ids:
node_ids.add(i.child_id)
node = i.child.to_dict()
node.setdefault('level', []).append(lv)
nodes.append(node)
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
edge_tuples.add((i.parent_id, i.child_id))
_find(i.child_id, lv)
continue
elif (i.parent_id, i.child_id) not in edge_tuples:
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=False))
edge_tuples.add((i.parent_id, i.child_id))
_find(i.child_id, lv)
for _node in nodes:
if _node['id'] == i.child_id and lv not in _node['level']:
_node['level'].append(lv)
def _reverse_find(_id, lv):
lv -= 1
for i in CITypeRelation.get_by(child_id=_id, to_dict=False):
if i.parent_id not in node_ids:
node_ids.add(i.parent_id)
node = i.parent.to_dict()
node.setdefault('level', []).append(lv)
nodes.append(node)
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
edge_tuples.add((i.parent_id, i.child_id))
_reverse_find(i.parent_id, lv)
continue
elif (i.parent_id, i.child_id) not in edge_tuples:
edges.append(dict(from_id=i.parent_id, to_id=i.child_id, text=i.relation_type.name, reverse=True))
edge_tuples.add((i.parent_id, i.child_id))
_reverse_find(i.parent_id, lv)
for _node in nodes:
if _node['id'] == i.child_id and lv not in _node['level']:
_node['level'].append(lv)
level = 0
_reverse_find(ci_type.id, level)
level = 0
_find(ci_type.id, level)
return nodes, edges
@staticmethod @staticmethod
def _get(parent_id, child_id): def _get(parent_id, child_id):
return CITypeRelation.get_by(parent_id=parent_id, return CITypeRelation.get_by(parent_id=parent_id,
@@ -825,7 +946,7 @@ class CITypeRelationManager(object):
@classmethod @classmethod
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many, def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
parent_attr_id=None, child_attr_id=None): parent_attr_ids=None, child_attr_ids=None):
p = CITypeManager.check_is_existed(parent) p = CITypeManager.check_is_existed(parent)
c = CITypeManager.check_is_existed(child) c = CITypeManager.check_is_existed(child)
@@ -840,21 +961,22 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e)) current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error) return abort(400, ErrFormat.circular_dependency_error)
old_parent_attr_id = None old_parent_attr_ids, old_child_attr_ids = None, None
existed = cls._get(p.id, c.id) existed = cls._get(p.id, c.id)
if existed is not None: if existed is not None:
old_parent_attr_id = existed.parent_attr_id old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids)
old_child_attr_ids = copy.deepcopy(existed.child_attr_ids)
existed = existed.update(relation_type_id=relation_type_id, existed = existed.update(relation_type_id=relation_type_id,
constraint=constraint, constraint=constraint,
parent_attr_id=parent_attr_id, parent_attr_ids=parent_attr_ids,
child_attr_id=child_attr_id, child_attr_ids=child_attr_ids,
filter_none=False) filter_none=False)
else: else:
existed = CITypeRelation.create(parent_id=p.id, existed = CITypeRelation.create(parent_id=p.id,
child_id=c.id, child_id=c.id,
relation_type_id=relation_type_id, relation_type_id=relation_type_id,
parent_attr_id=parent_attr_id, parent_attr_ids=parent_attr_ids,
child_attr_id=child_attr_id, child_attr_ids=child_attr_ids,
constraint=constraint) constraint=constraint)
if current_app.config.get("USE_ACL"): if current_app.config.get("USE_ACL"):
@@ -868,10 +990,10 @@ class CITypeRelationManager(object):
current_user.username, current_user.username,
ResourceTypeEnum.CI_TYPE_RELATION) ResourceTypeEnum.CI_TYPE_RELATION)
if parent_attr_id and parent_attr_id != old_parent_attr_id: if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or
if parent_attr_id and parent_attr_id != existed.parent_attr_id: (child_attr_ids and child_attr_ids != old_child_attr_ids)):
from api.tasks.cmdb import rebuild_relation_for_attribute_changed from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict())) rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(), current_user.uid))
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id, CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id)) change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
@@ -928,7 +1050,8 @@ class CITypeAttributeGroupManager(object):
parent_ids = CITypeInheritanceManager.base(type_id) parent_ids = CITypeInheritanceManager.base(type_id)
groups = [] groups = []
id2type = {i: CITypeCache.get(i).alias for i in parent_ids} id2type = {i: CITypeCache.get(i) for i in parent_ids}
id2type = {k: v.alias for k, v in id2type.items() if v}
for _type_id in parent_ids + [type_id]: for _type_id in parent_ids + [type_id]:
_groups = CITypeAttributeGroup.get_by(type_id=_type_id) _groups = CITypeAttributeGroup.get_by(type_id=_type_id)
_groups = sorted(_groups, key=lambda x: x["order"] or 0) _groups = sorted(_groups, key=lambda x: x["order"] or 0)
@@ -1121,17 +1244,16 @@ class CITypeAttributeGroupManager(object):
if isinstance(_from, int): if isinstance(_from, int):
from_group = CITypeAttributeGroup.get_by_id(_from) from_group = CITypeAttributeGroup.get_by_id(_from)
else: else:
from_group = CITypeAttributeGroup.get_by(name=_from, first=True, to_dict=False) from_group = CITypeAttributeGroup.get_by(name=_from, type_id=type_id, first=True, to_dict=False)
from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from))) from_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_from)))
if isinstance(_to, int): if isinstance(_to, int):
to_group = CITypeAttributeGroup.get_by_id(_to) to_group = CITypeAttributeGroup.get_by_id(_to)
else: else:
to_group = CITypeAttributeGroup.get_by(name=_to, first=True, to_dict=False) to_group = CITypeAttributeGroup.get_by(name=_to, type_id=type_id, first=True, to_dict=False)
to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to))) to_group or abort(404, ErrFormat.ci_type_attribute_group_not_found.format("id={}".format(_to)))
from_order, to_order = from_group.order, to_group.order from_order, to_order = from_group.order, to_group.order
from_group.update(order=to_order) from_group.update(order=to_order)
to_group.update(order=from_order) to_group.update(order=from_order)
@@ -1165,8 +1287,8 @@ class CITypeTemplateManager(object):
id2obj_dicts[added_id].get('child_id'), id2obj_dicts[added_id].get('child_id'),
id2obj_dicts[added_id].get('relation_type_id'), id2obj_dicts[added_id].get('relation_type_id'),
id2obj_dicts[added_id].get('constraint'), id2obj_dicts[added_id].get('constraint'),
id2obj_dicts[added_id].get('parent_attr_id'), id2obj_dicts[added_id].get('parent_attr_ids'),
id2obj_dicts[added_id].get('child_attr_id'), id2obj_dicts[added_id].get('child_attr_ids'),
) )
else: else:
obj = cls.create(flush=True, **id2obj_dicts[added_id]) obj = cls.create(flush=True, **id2obj_dicts[added_id])
@@ -1202,13 +1324,14 @@ class CITypeTemplateManager(object):
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]] attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
attrs = [] attrs = []
for i in copy.deepcopy(attributes): for i in copy.deepcopy(attributes):
if i.pop('inherited', None):
continue
i.pop('default_show', None) i.pop('default_show', None)
i.pop('is_required', None) i.pop('is_required', None)
i.pop('order', None) i.pop('order', None)
i.pop('choice_web_hook', None) i.pop('choice_web_hook', None)
i.pop('choice_other', None) i.pop('choice_other', None)
i.pop('order', None) i.pop('order', None)
i.pop('inherited', None)
i.pop('inherited_from', None) i.pop('inherited_from', None)
choice_value = i.pop('choice_value', None) choice_value = i.pop('choice_value', None)
if not choice_value: if not choice_value:
@@ -1227,7 +1350,11 @@ class CITypeTemplateManager(object):
def _import_ci_types(self, ci_types, attr_id_map): def _import_ci_types(self, ci_types, attr_id_map):
for i in ci_types: for i in ci_types:
i.pop("unique_key", None) i.pop("unique_key", None)
i.pop("show_name", None)
i.pop("parent_ids", None)
i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id']) i['unique_id'] = attr_id_map.get(i['unique_id'], i['unique_id'])
if i.get('show_id'):
i['show_id'] = attr_id_map.get(i['show_id'], i['show_id'])
i['uid'] = current_user.uid i['uid'] = current_user.uid
return self.__import(CIType, ci_types) return self.__import(CIType, ci_types)
@@ -1262,7 +1389,7 @@ class CITypeTemplateManager(object):
return self.__import(RelationType, relation_types) return self.__import(RelationType, relation_types)
@staticmethod @staticmethod
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map): def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map, attr_id_map):
for i in ci_type_relations: for i in ci_type_relations:
i.pop('parent', None) i.pop('parent', None)
i.pop('child', None) i.pop('child', None)
@@ -1272,15 +1399,32 @@ class CITypeTemplateManager(object):
i['child_id'] = type_id_map.get(i['child_id'], i['child_id']) i['child_id'] = type_id_map.get(i['child_id'], i['child_id'])
i['relation_type_id'] = relation_type_id_map.get(i['relation_type_id'], i['relation_type_id']) i['relation_type_id'] = relation_type_id_map.get(i['relation_type_id'], i['relation_type_id'])
i['parent_attr_ids'] = [attr_id_map.get(attr_id, attr_id) for attr_id in i.get('parent_attr_ids') or []]
i['child_attr_ids'] = [attr_id_map.get(attr_id, attr_id) for attr_id in i.get('child_attr_ids') or []]
try: try:
CITypeRelationManager.add(i.get('parent_id'), CITypeRelationManager.add(i.get('parent_id'),
i.get('child_id'), i.get('child_id'),
i.get('relation_type_id'), i.get('relation_type_id'),
i.get('constraint'), i.get('constraint'),
parent_attr_ids=i.get('parent_attr_ids', []),
child_attr_ids=i.get('child_attr_ids', []),
) )
except BadRequest: except Exception:
pass pass
@staticmethod
def _import_ci_type_inheritance(ci_type_inheritance, type_id_map):
for i in ci_type_inheritance:
i['parent_id'] = type_id_map.get(i['parent_id'])
i['child_id'] = type_id_map.get(i['child_id'])
if i['parent_id'] and i['child_id']:
try:
CITypeInheritanceManager.add([i.get('parent_id')], i.get('child_id'))
except BadRequest:
pass
@staticmethod @staticmethod
def _import_type_attributes(type2attributes, type_id_map, attr_id_map): def _import_type_attributes(type2attributes, type_id_map, attr_id_map):
for type_id in type2attributes: for type_id in type2attributes:
@@ -1292,6 +1436,9 @@ class CITypeTemplateManager(object):
handled = set() handled = set()
for attr in type2attributes[type_id]: for attr in type2attributes[type_id]:
if attr.get('inherited'):
continue
payload = dict(type_id=type_id_map.get(int(type_id), type_id), payload = dict(type_id=type_id_map.get(int(type_id), type_id),
attr_id=attr_id_map.get(attr['id'], attr['id']), attr_id=attr_id_map.get(attr['id'], attr['id']),
default_show=attr['default_show'], default_show=attr['default_show'],
@@ -1355,6 +1502,9 @@ class CITypeTemplateManager(object):
for rule in rules: for rule in rules:
ci_type = CITypeCache.get(rule.pop('type_name', None)) ci_type = CITypeCache.get(rule.pop('type_name', None))
if ci_type is None:
continue
adr = rule.pop('adr', {}) or {} adr = rule.pop('adr', {}) or {}
if ci_type: if ci_type:
@@ -1367,10 +1517,10 @@ class CITypeTemplateManager(object):
if ad_rule: if ad_rule:
rule['adr_id'] = ad_rule.id rule['adr_id'] = ad_rule.id
ad_rule.update(**adr) ad_rule.update(valid=False, **adr)
elif adr: elif adr:
ad_rule = AutoDiscoveryRuleCRUD().add(**adr) ad_rule = AutoDiscoveryRuleCRUD().add(valid=False, **adr)
rule['adr_id'] = ad_rule.id rule['adr_id'] = ad_rule.id
else: else:
continue continue
@@ -1390,6 +1540,9 @@ class CITypeTemplateManager(object):
if ((i.extra_option or {}).get('alias') or None) == ( if ((i.extra_option or {}).get('alias') or None) == (
(rule.get('extra_option') or {}).get('alias') or None): (rule.get('extra_option') or {}).get('alias') or None):
existed = True existed = True
rule.pop('extra_option', None)
rule.pop('enabled', None)
rule.pop('cron', None)
AutoDiscoveryCITypeCRUD().update(i.id, **rule) AutoDiscoveryCITypeCRUD().update(i.id, **rule)
break break
@@ -1399,6 +1552,23 @@ class CITypeTemplateManager(object):
except Exception as e: except Exception as e:
current_app.logger.warning("import auto discovery rules failed: {}".format(e)) current_app.logger.warning("import auto discovery rules failed: {}".format(e))
@staticmethod
def _import_auto_discovery_relation_rules(rules):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
for rule in rules:
ad_ci_type = CITypeCache.get(rule.pop('ad_type_name', None))
peer_ci_type = CITypeCache.get(rule.pop('peer_type_name', None))
peer_attr = AttributeCache.get(rule.pop('peer_attr_name', None))
if ad_ci_type and peer_attr and peer_ci_type:
if not AutoDiscoveryCITypeRelation.get_by(
ad_type_id=ad_ci_type.id, ad_key=rule.get('ad_key'),
peer_attr_id=peer_attr.id, peer_type_id=peer_ci_type.id):
AutoDiscoveryCITypeRelationCRUD().add(ad_type_id=ad_ci_type.id,
ad_key=rule.get('ad_key'),
peer_attr_id=peer_attr.id,
peer_type_id=peer_ci_type.id)
@staticmethod @staticmethod
def _import_icons(icons): def _import_icons(icons):
from api.lib.common_setting.upload_file import CommonFileCRUD from api.lib.common_setting.upload_file import CommonFileCRUD
@@ -1410,6 +1580,8 @@ class CITypeTemplateManager(object):
current_app.logger.warning("save icon failed: {}".format(e)) current_app.logger.warning("save icon failed: {}".format(e))
def import_template(self, tpt): def import_template(self, tpt):
db.session.commit()
import time import time
s = time.time() s = time.time()
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {}) attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
@@ -1428,9 +1600,14 @@ class CITypeTemplateManager(object):
current_app.logger.info('import relation_types cost: {}'.format(time.time() - s)) current_app.logger.info('import relation_types cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_id_map) self._import_ci_type_relations(tpt.get('ci_type_relations') or [],
ci_type_id_map, relation_type_id_map, attr_id_map)
current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s)) current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s))
s = time.time()
self._import_ci_type_inheritance(tpt.get('ci_type_inheritance') or [], ci_type_id_map)
current_app.logger.info('import ci_type_inheritance cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map) self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map)
current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s)) current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s))
@@ -1443,26 +1620,73 @@ class CITypeTemplateManager(object):
self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or []) self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or [])
current_app.logger.info('import ci_type_auto_discovery_rules cost: {}'.format(time.time() - s)) current_app.logger.info('import ci_type_auto_discovery_rules cost: {}'.format(time.time() - s))
s = time.time()
self._import_auto_discovery_relation_rules(tpt.get('ci_type_auto_discovery_relation_rules') or [])
current_app.logger.info('import ci_type_auto_discovery_relation_rules cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_icons(tpt.get('icons') or {}) self._import_icons(tpt.get('icons') or {})
current_app.logger.info('import icons cost: {}'.format(time.time() - s)) current_app.logger.info('import icons cost: {}'.format(time.time() - s))
@staticmethod @staticmethod
def export_template(): def export_template(type_ids=None):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.common_setting.upload_file import CommonFileCRUD from api.lib.common_setting.upload_file import CommonFileCRUD
ci_types = CITypeManager.get_ci_types(type_ids=type_ids)
extend_type_ids = []
for ci_type in ci_types:
if ci_type.get('parent_ids'):
extend_type_ids.extend(CITypeInheritanceManager.base(ci_type['id']))
extend_type_ids = list(set(extend_type_ids) - set(type_ids))
ci_type_relations = CITypeRelationManager.get(type_ids=type_ids)[0]
for i in ci_type_relations:
if i['parent_id'] not in type_ids:
extend_type_ids.append(i['parent_id'])
if i['child_id'] not in type_ids:
extend_type_ids.append(i['child_id'])
ad_relation_rules = AutoDiscoveryCITypeRelationCRUD.get_all(type_ids=type_ids)
rules = []
for r in ad_relation_rules:
if r.peer_type_id not in type_ids:
extend_type_ids.append(r.peer_type_id)
r = r.to_dict()
r['ad_type_name'] = CITypeCache.get(r.pop('ad_type_id')).name
peer_type_id = r.pop("peer_type_id")
peer_type_name = CITypeCache.get(peer_type_id).name
if not peer_type_name:
peer_type = CITypeCache.get(peer_type_id)
peer_type_name = peer_type and peer_type.name
r['peer_type_name'] = peer_type_name
peer_attr_id = r.pop("peer_attr_id")
peer_attr = AttributeCache.get(peer_attr_id)
r['peer_attr_name'] = peer_attr and peer_attr.name
rules.append(r)
ci_type_auto_discovery_relation_rules = rules
if extend_type_ids:
extend_type_ids = list(set(extend_type_ids))
type_ids.extend(extend_type_ids)
ci_types.extend(CITypeManager.get_ci_types(type_ids=extend_type_ids))
tpt = dict( tpt = dict(
ci_types=CITypeManager.get_ci_types(), ci_types=ci_types,
ci_type_groups=CITypeGroupManager.get(),
relation_types=[i.to_dict() for i in RelationTypeManager.get_all()], relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
ci_type_relations=CITypeRelationManager.get()[0], ci_type_relations=ci_type_relations,
ci_type_inheritance=CITypeInheritanceManager.get_all(type_ids=type_ids),
ci_type_auto_discovery_rules=list(), ci_type_auto_discovery_rules=list(),
ci_type_auto_discovery_relation_rules=ci_type_auto_discovery_relation_rules,
type2attributes=dict(), type2attributes=dict(),
type2attribute_group=dict(), type2attribute_group=dict(),
icons=dict() icons=dict()
) )
tpt['ci_type_groups'] = CITypeGroupManager.get(ci_types=tpt['ci_types'], type_ids=type_ids)
def get_icon_value(icon): def get_icon_value(icon):
try: try:
@@ -1470,12 +1694,16 @@ class CITypeTemplateManager(object):
except: except:
return "" return ""
ad_rules = AutoDiscoveryCITypeCRUD.get_all() type_id2name = {i['id']: i['name'] for i in tpt['ci_types']}
ad_rules = AutoDiscoveryCITypeCRUD.get_all(type_ids=type_ids)
rules = [] rules = []
for r in ad_rules: for r in ad_rules:
r = r.to_dict() r = r.to_dict()
ci_type = CITypeCache.get(r.pop('type_id'))
r['type_name'] = ci_type and ci_type.name if r.get('extra_option') and '_reference' in r['extra_option']:
r['extra_option'].pop('_reference')
r['type_name'] = type_id2name.get(r.pop('type_id'))
if r.get('adr_id'): if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id')) adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name r['adr_name'] = adr and adr.name
@@ -1509,65 +1737,6 @@ class CITypeTemplateManager(object):
return tpt return tpt
@staticmethod
def export_template_by_type(type_id):
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found2.format("id={}".format(type_id)))
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.common_setting.upload_file import CommonFileCRUD
tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False),
ci_type_auto_discovery_rules=list(),
type2attributes=dict(),
type2attribute_group=dict(),
icons=dict()
)
def get_icon_value(icon):
try:
return CommonFileCRUD().get_file_binary_str(icon)
except:
return ""
ad_rules = AutoDiscoveryCITypeCRUD.get_by_type_id(ci_type.id)
rules = []
for r in ad_rules:
r = r.to_dict()
r['type_name'] = ci_type and ci_type.name
if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name
r['adr'] = adr and adr.to_dict() or {}
icon_url = r['adr'].get('option', {}).get('icon', {}).get('url')
if icon_url and icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
rules.append(r)
tpt['ci_type_auto_discovery_rules'] = rules
for ci_type in tpt['ci_types']:
if ci_type['icon'] and len(ci_type['icon'].split('$$')) > 3:
icon_url = ci_type['icon'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
for attr in tpt['type2attributes'][ci_type['id']]:
for i in (attr.get('choice_value') or []):
if (i[1] or {}).get('icon', {}).get('url') and len(i[1]['icon']['url'].split('$$')) > 3:
icon_url = i[1]['icon']['url'].split('$$')[3]
if icon_url not in tpt['icons']:
tpt['icons'][icon_url] = get_icon_value(icon_url)
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
return tpt
class CITypeUniqueConstraintManager(object): class CITypeUniqueConstraintManager(object):
@staticmethod @staticmethod

View File

@@ -41,20 +41,23 @@ class OperateType(BaseEnum):
class CITypeOperateType(BaseEnum): class CITypeOperateType(BaseEnum):
ADD = "0" # 新增模型 ADD = "0" # add CIType
UPDATE = "1" # 修改模型 UPDATE = "1" # update CIType
DELETE = "2" # 删除模型 DELETE = "2" # delete CIType
ADD_ATTRIBUTE = "3" # 新增属性 ADD_ATTRIBUTE = "3"
UPDATE_ATTRIBUTE = "4" # 修改属性 UPDATE_ATTRIBUTE = "4"
DELETE_ATTRIBUTE = "5" # 删除属性 DELETE_ATTRIBUTE = "5"
ADD_TRIGGER = "6" # 新增触发器 ADD_TRIGGER = "6"
UPDATE_TRIGGER = "7" # 修改触发器 UPDATE_TRIGGER = "7"
DELETE_TRIGGER = "8" # 删除触发器 DELETE_TRIGGER = "8"
ADD_UNIQUE_CONSTRAINT = "9" # 新增联合唯一 ADD_UNIQUE_CONSTRAINT = "9"
UPDATE_UNIQUE_CONSTRAINT = "10" # 修改联合唯一 UPDATE_UNIQUE_CONSTRAINT = "10"
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一 DELETE_UNIQUE_CONSTRAINT = "11"
ADD_RELATION = "12" # 新增关系 ADD_RELATION = "12"
DELETE_RELATION = "13" # 删除关系 DELETE_RELATION = "13"
ADD_RECONCILIATION = "14"
UPDATE_RECONCILIATION = "15"
DELETE_RECONCILIATION = "16"
class RetKey(BaseEnum): class RetKey(BaseEnum):
@@ -70,6 +73,7 @@ class ResourceTypeEnum(BaseEnum):
RELATION_VIEW = "RelationView" # read/update/delete/grant RELATION_VIEW = "RelationView" # read/update/delete/grant
CI_FILTER = "CIFilter" # read CI_FILTER = "CIFilter" # read
PAGE = "page" # read PAGE = "page" # read
TOPOLOGY_VIEW = "TopologyView" # read/update/delete/grant
class PermEnum(BaseEnum): class PermEnum(BaseEnum):
@@ -89,7 +93,8 @@ class RoleEnum(BaseEnum):
class AutoDiscoveryType(BaseEnum): class AutoDiscoveryType(BaseEnum):
AGENT = "agent" AGENT = "agent"
SNMP = "snmp" SNMP = "snmp"
HTTP = "http" HTTP = "http" # cloud
COMPONENTS = "components"
class AttributeDefaultValueEnum(BaseEnum): class AttributeDefaultValueEnum(BaseEnum):
@@ -98,12 +103,22 @@ class AttributeDefaultValueEnum(BaseEnum):
AUTO_INC_ID = "$auto_inc_id" AUTO_INC_ID = "$auto_inc_id"
class ExecuteStatusEnum(BaseEnum):
COMPLETED = '0'
FAILED = '1'
RUNNING = '2'
class RelationSourceEnum(BaseEnum):
ATTRIBUTE_VALUES = "0"
AUTO_DISCOVERY = "1"
CMDB_QUEUE = "one_cmdb_async" CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION" REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2" REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'} BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id'}
L_TYPE = None L_TYPE = None
L_CI = None L_CI = None

View File

@@ -13,6 +13,7 @@ from api.lib.cmdb.const import OperateType
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.models.cmdb import CI
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import AttributeHistory from api.models.cmdb import AttributeHistory
from api.models.cmdb import CIRelationHistory from api.models.cmdb import CIRelationHistory
@@ -26,7 +27,7 @@ from api.models.cmdb import OperationRecord
class AttributeHistoryManger(object): class AttributeHistoryManger(object):
@staticmethod @staticmethod
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id, def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
ci_id=None, attr_id=None): ci_id=None, attr_id=None, ci_ids=None, more=False):
records = db.session.query(OperationRecord, AttributeHistory).join( records = db.session.query(OperationRecord, AttributeHistory).join(
AttributeHistory, OperationRecord.id == AttributeHistory.record_id) AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
@@ -48,6 +49,9 @@ class AttributeHistoryManger(object):
if ci_id is not None: if ci_id is not None:
records = records.filter(AttributeHistory.ci_id == ci_id) records = records.filter(AttributeHistory.ci_id == ci_id)
if ci_ids and isinstance(ci_ids, list):
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
if attr_id is not None: if attr_id is not None:
records = records.filter(AttributeHistory.attr_id == attr_id) records = records.filter(AttributeHistory.attr_id == attr_id)
@@ -62,6 +66,12 @@ class AttributeHistoryManger(object):
if attr_hist['attr']: if attr_hist['attr']:
attr_hist['attr_name'] = attr_hist['attr'].name attr_hist['attr_name'] = attr_hist['attr'].name
attr_hist['attr_alias'] = attr_hist['attr'].alias attr_hist['attr_alias'] = attr_hist['attr'].alias
if more:
attr_hist['is_list'] = attr_hist['attr'].is_list
attr_hist['is_computed'] = attr_hist['attr'].is_computed
attr_hist['is_password'] = attr_hist['attr'].is_password
attr_hist['default'] = attr_hist['attr'].default
attr_hist['value_type'] = attr_hist['attr'].value_type
attr_hist.pop("attr") attr_hist.pop("attr")
if record_id not in res: if record_id not in res:
@@ -161,6 +171,7 @@ class AttributeHistoryManger(object):
record = i.OperationRecord record = i.OperationRecord
item = dict(attr_name=attr.name, item = dict(attr_name=attr.name,
attr_alias=attr.alias, attr_alias=attr.alias,
value_type=attr.value_type,
operate_type=hist.operate_type, operate_type=hist.operate_type,
username=user and user.nickname, username=user and user.nickname,
old=hist.old, old=hist.old,
@@ -221,8 +232,8 @@ class AttributeHistoryManger(object):
class CIRelationHistoryManager(object): class CIRelationHistoryManager(object):
@staticmethod @staticmethod
def add(rel_obj, operate_type=OperateType.ADD): def add(rel_obj, operate_type=OperateType.ADD, uid=None):
record = OperationRecord.create(uid=current_user.uid) record = OperationRecord.create(uid=uid or current_user.uid)
CIRelationHistory.create(relation_id=rel_obj.id, CIRelationHistory.create(relation_id=rel_obj.id,
record_id=record.id, record_id=record.id,
@@ -271,7 +282,7 @@ class CITypeHistoryManager(object):
return numfound, result return numfound, result
@staticmethod @staticmethod
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None): def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
if type_id is None and attr_id is not None: if type_id is None and attr_id is not None:
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)] type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
@@ -284,6 +295,7 @@ class CITypeHistoryManager(object):
uid=current_user.uid, uid=current_user.uid,
attr_id=attr_id, attr_id=attr_id,
trigger_id=trigger_id, trigger_id=trigger_id,
rc_id=rc_id,
unique_constraint_id=unique_constraint_id, unique_constraint_id=unique_constraint_id,
change=change) change=change)
@@ -295,7 +307,7 @@ class CITriggerHistoryManager(object):
def get(page, page_size, type_id=None, trigger_id=None, operate_type=None): def get(page, page_size, type_id=None, trigger_id=None, operate_type=None):
query = CITriggerHistory.get_by(only_query=True) query = CITriggerHistory.get_by(only_query=True)
if type_id: if type_id:
query = query.filter(CITriggerHistory.type_id == type_id) query = query.join(CI, CI.id == CITriggerHistory.ci_id).filter(CI.type_id == type_id)
if trigger_id: if trigger_id:
query = query.filter(CITriggerHistory.trigger_id == trigger_id) query = query.filter(CITriggerHistory.trigger_id == trigger_id)

View File

@@ -25,6 +25,8 @@ from api.lib.cmdb.resp_format import ErrFormat
from api.lib.exception import AbortException from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceCITypeOrder from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceRelationView
@@ -43,22 +45,46 @@ class PreferenceManager(object):
def get_types(instance=False, tree=False): def get_types(instance=False, tree=False):
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order) ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
type2group = {}
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
types = db.session.query(PreferenceShowAttributes.type_id).filter( types = db.session.query(PreferenceShowAttributes.type_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by( PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.type_id).all() if instance else [] PreferenceShowAttributes.type_id).all() if instance else []
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate( types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
ci_type_order) if not i.is_tree}.get(x.type_id, 1)) ci_type_order) if not i.is_tree}.get(x.type_id, 1))
group_types = []
other_types = []
group2idx = {}
type_ids = set()
for ci_type in types:
type_id = ci_type.type_id
type_ids.add(type_id)
type_dict = CITypeCache.get(type_id).to_dict()
if type_id not in type2group:
other_types.append(type_dict)
else:
group = type2group[type_id]
if group['id'] not in group2idx:
group_types.append(type2group[type_id])
group2idx[group['id']] = len(group_types) - 1
group_types[group2idx[group['id']]].setdefault('ci_types', []).append(type_dict)
if other_types:
group_types.append(dict(ci_types=other_types))
tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else [] tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else []
tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate( tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
ci_type_order) if i.is_tree}.get(x.type_id, 1)) ci_type_order) if i.is_tree}.get(x.type_id, 1))
type_ids = [i.type_id for i in types + tree_types] tree_types = [CITypeCache.get(_type.type_id).to_dict() for _type in tree_types]
if types and tree_types: for _type in tree_types:
type_ids = set(type_ids) type_ids.add(_type['id'])
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids] return dict(group_types=group_types, tree_types=tree_types, type_ids=list(type_ids))
@staticmethod @staticmethod
def get_types2(instance=False, tree=False): def get_types2(instance=False, tree=False):
@@ -297,6 +323,10 @@ class PreferenceManager(object):
for type_id in id2type: for type_id in id2type:
id2type[type_id] = CITypeCache.get(type_id).to_dict() id2type[type_id] = CITypeCache.get(type_id).to_dict()
id2type[type_id]['unique_name'] = AttributeCache.get(id2type[type_id]['unique_id']).name
if id2type[type_id]['show_id']:
show_attr = AttributeCache.get(id2type[type_id]['show_id'])
id2type[type_id]['show_name'] = show_attr and show_attr.name
return result, id2type, sorted(name2id, key=lambda x: x[1]) return result, id2type, sorted(name2id, key=lambda x: x[1])

View File

@@ -35,7 +35,7 @@ class ErrFormat(CommonErrFormat):
"Only creators and administrators are allowed to delete attributes!") # 目前只允许 属性创建人、管理员 删除属性! "Only creators and administrators are allowed to delete attributes!") # 目前只允许 属性创建人、管理员 删除属性!
# 属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type # 属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type
attribute_name_cannot_be_builtin = _l( attribute_name_cannot_be_builtin = _l(
"Attribute field names cannot be built-in fields: id, _id, ci_id, type, _type, ci_type") "Attribute field names cannot be built-in fields: id, _id, ci_id, type, _type, ci_type, ticket_id")
attribute_choice_other_invalid = _l( attribute_choice_other_invalid = _l(
"Predefined value: Other model request parameters are illegal!") # 预定义值: 其他模型请求参数不合法! "Predefined value: Other model request parameters are illegal!") # 预定义值: 其他模型请求参数不合法!
@@ -62,6 +62,7 @@ class ErrFormat(CommonErrFormat):
"The model cannot be deleted because the CI already exists") # 因为CI已经存在不能删除模型 "The model cannot be deleted because the CI already exists") # 因为CI已经存在不能删除模型
ci_exists_and_cannot_delete_inheritance = _l( ci_exists_and_cannot_delete_inheritance = _l(
"The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在不能删除继承关系 "The inheritance cannot be deleted because the CI already exists") # 因为CI已经存在不能删除继承关系
ci_type_inheritance_cannot_delete = _l("The model is inherited and cannot be deleted") # 该模型被继承, 不能删除
# 因为关系视图 {} 引用了该模型,不能删除模型 # 因为关系视图 {} 引用了该模型,不能删除模型
ci_relation_view_exists_and_cannot_delete_type = _l( ci_relation_view_exists_and_cannot_delete_type = _l(
@@ -78,6 +79,8 @@ class ErrFormat(CommonErrFormat):
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued") unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器 ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在 ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在 record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识 cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识
@@ -138,3 +141,12 @@ class ErrFormat(CommonErrFormat):
password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {} password_save_failed = _l("Failed to save password: {}") # 保存密码失败: {}
password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {} password_load_failed = _l("Failed to get password: {}") # 获取密码失败: {}
cron_time_format_invalid = _l("Scheduling time format error") # 调度时间格式错误
reconciliation_title = _l("CMDB data reconciliation results") # CMDB数据合规检查结果
reconciliation_body = _l("Number of {} illegal: {}") # "{} 不合规数: {}"
topology_exists = _l("Topology view {} already exists") # 拓扑视图 {} 已经存在
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
# 因为该分组下定义了拓扑视图,不能删除
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")

View File

@@ -17,10 +17,12 @@ def search(query=None,
count=1, count=1,
sort=None, sort=None,
excludes=None, excludes=None,
use_id_filter=False): use_id_filter=False,
use_ci_filter=True):
if current_app.config.get("USE_ES"): if current_app.config.get("USE_ES"):
s = SearchFromES(query, fl, facet, page, ret_key, count, sort) s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
else: else:
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes, use_id_filter=use_id_filter) s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes,
use_id_filter=use_id_filter, use_ci_filter=use_ci_filter)
return s return s

View File

@@ -47,7 +47,8 @@ class Search(object):
excludes=None, excludes=None,
parent_node_perm_passed=False, parent_node_perm_passed=False,
use_id_filter=False, use_id_filter=False,
use_ci_filter=True): use_ci_filter=True,
only_ids=False):
self.orig_query = query self.orig_query = query
self.fl = fl or [] self.fl = fl or []
self.excludes = excludes or [] self.excludes = excludes or []
@@ -64,10 +65,12 @@ class Search(object):
self.parent_node_perm_passed = parent_node_perm_passed self.parent_node_perm_passed = parent_node_perm_passed
self.use_id_filter = use_id_filter self.use_id_filter = use_id_filter
self.use_ci_filter = use_ci_filter self.use_ci_filter = use_ci_filter
self.only_ids = only_ids
self.valid_type_names = [] self.valid_type_names = []
self.type2filter_perms = dict() self.type2filter_perms = dict()
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker" self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
self.is_app_admin = self.is_app_admin or (not self.use_ci_filter and not self.use_id_filter)
@staticmethod @staticmethod
def _operator_proc(key): def _operator_proc(key):
@@ -590,6 +593,8 @@ class Search(object):
def search(self): def search(self):
numfound, ci_ids = self._query_build_raw() numfound, ci_ids = self._query_build_raw()
ci_ids = list(map(str, ci_ids)) ci_ids = list(map(str, ci_ids))
if self.only_ids:
return ci_ids
_fl = self._fl_build() _fl = self._fl_build()

View File

@@ -8,6 +8,8 @@ from flask import current_app
from flask_login import current_user from flask_login import current_user
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
@@ -18,6 +20,8 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
from api.lib.cmdb.utils import TableMap
from api.lib.cmdb.utils import ValueTypeMap
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import CI from api.models.cmdb import CI
@@ -65,7 +69,7 @@ class Search(object):
if _l < int(level) and c == ConstraintEnum.Many2Many: if _l < int(level) and c == ConstraintEnum.Many2Many:
self.has_m2m = True self.has_m2m = True
self.type2filter_perms = None self.type2filter_perms = {}
self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker" self.is_app_admin = is_app_admin('cmdb') or current_user.username == "worker"
@@ -75,7 +79,7 @@ class Search(object):
key = [] key = []
_tmp = [] _tmp = []
for level in range(1, sorted(self.level)[-1] + 1): for level in range(1, sorted(self.level)[-1] + 1):
if len(self.descendant_ids) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]): if len(self.descendant_ids or []) >= level and self.type2filter_perms.get(self.descendant_ids[level - 1]):
id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]]) id_filter_limit, _ = self._get_ci_filter(self.type2filter_perms[self.descendant_ids[level - 1]])
else: else:
id_filter_limit = {} id_filter_limit = {}
@@ -147,9 +151,9 @@ class Search(object):
return True return True
def search(self): def search(self, only_ids=False):
use_ci_filter = len(self.descendant_ids) == self.level[0] - 1 use_ci_filter = len(self.descendant_ids or []) == self.level[0] - 1
parent_node_perm_passed = self._has_read_perm_from_parent_nodes() parent_node_perm_passed = not self.is_app_admin and self._has_read_perm_from_parent_nodes()
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids] cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
@@ -193,7 +197,8 @@ class Search(object):
sort=self.sort, sort=self.sort,
ci_ids=merge_ids, ci_ids=merge_ids,
parent_node_perm_passed=parent_node_perm_passed, parent_node_perm_passed=parent_node_perm_passed,
use_ci_filter=use_ci_filter).search() use_ci_filter=use_ci_filter,
only_ids=only_ids).search()
def _get_ci_filter(self, filter_perms, ci_filters=None): def _get_ci_filter(self, filter_perms, ci_filters=None):
ci_filters = ci_filters or [] ci_filters = ci_filters or []
@@ -236,7 +241,7 @@ class Search(object):
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2]))) type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
_tmp = [] _tmp, tmp_res = [], []
level2ids = {} level2ids = {}
for lv in range(1, self.level + 1): for lv in range(1, self.level + 1):
level2ids[lv] = [] level2ids[lv] = []
@@ -303,25 +308,26 @@ class Search(object):
if key: if key:
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]] res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
if type_ids and lv == self.level: if type_ids and lv == self.level:
__tmp = [[i for i in x if i[1] in type_ids and tmp_res = [[i for i in x if i[1] in type_ids and
(not id_filter_limit or ( (not id_filter_limit or (
key[idx] not in id_filter_limit or key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)] int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
else: else:
__tmp = [[i for i in x if (not id_filter_limit or ( tmp_res = [[i for i in x if (not id_filter_limit or (
key[idx] not in id_filter_limit or key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)] int(i[0]) in id_filter_limit)] for idx, x in
enumerate(res)]
if ci_filter_limit: if ci_filter_limit:
__tmp = [[j for j in i if j[1] not in ci_filter_limit or tmp_res = [[j for j in i if j[1] not in ci_filter_limit or
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp] int(j[0]) in ci_filter_limit[j[1]]] for i in tmp_res]
else: else:
__tmp = [] tmp_res = []
if __tmp: if tmp_res:
_tmp[idx] = [j for i in __tmp for j in i] _tmp[idx] = [j for i in tmp_res for j in i]
else: else:
_tmp[idx] = [] _tmp[idx] = []
level2ids[lv].append([]) level2ids[lv].append([])
@@ -332,3 +338,84 @@ class Search(object):
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)}) detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
return result return result
def search_full(self, type_ids):
def _get_id2name(_type_id):
ci_type = CITypeCache.get(_type_id)
attr = AttributeCache.get(ci_type.unique_id)
value_table = TableMap(attr=attr).table
serializer = ValueTypeMap.serialize[attr.value_type]
unique_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
attr = AttributeCache.get(ci_type.show_id)
if attr:
value_table = TableMap(attr=attr).table
serializer = ValueTypeMap.serialize[attr.value_type]
show_value = {i.ci_id: serializer(i.value) for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
else:
show_value = unique_value
return show_value, unique_value
self.level = int(self.level)
acl = ACLManager('cmdb')
type2filter_perms = dict()
if not self.is_app_admin:
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
level_ids = [str(i) for i in ids]
result = []
id2children = {}
id2name = _get_id2name(type_ids[0])
for i in level_ids:
item = dict(id=int(i),
type_id=type_ids[0],
isLeaf=False,
title=id2name[0].get(int(i)),
uniqueValue=id2name[1].get(int(i)),
children=[])
result.append(item)
id2children[str(i)] = item['children']
for lv in range(1, self.level):
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
else:
id_filter_limit = {}
if self.has_m2m and lv != 1:
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
else:
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
_level_ids = []
type_id = type_ids[lv]
id2name = _get_id2name(type_id)
for idx, node_path in enumerate(level_ids):
for child_id, _ in (res[idx] or []):
item = dict(id=int(child_id),
type_id=type_id,
isLeaf=True if lv == self.level - 1 else False,
title=id2name[0].get(int(child_id)),
uniqueValue=id2name[1].get(int(child_id)),
children=[])
id2children[node_path].append(item)
_node_path = "{},{}".format(node_path, child_id)
_level_ids.append(_node_path)
id2children[_node_path] = item['children']
level_ids = _level_ids
return result

View File

@@ -0,0 +1,251 @@
# -*- coding:utf-8 -*-
import json
from flask import abort
from flask import current_app
from flask_login import current_user
from werkzeug.exceptions import BadRequest
from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import TopologyView
from api.models.cmdb import TopologyViewGroup
class TopologyViewManager(object):
group_cls = TopologyViewGroup
cls = TopologyView
@classmethod
def get_name_by_id(cls, _id):
res = cls.cls.get_by_id(_id)
return res and res.name
def get_view_by_id(self, _id):
res = self.cls.get_by_id(_id)
return res and res.to_dict() or {}
@classmethod
def add_group(cls, name, order):
if order is None:
cur_max_order = cls.group_cls.get_by(only_query=True).order_by(cls.group_cls.order.desc()).first()
cur_max_order = cur_max_order and cur_max_order.order or 0
order = cur_max_order + 1
cls.group_cls.get_by(name=name, first=True, to_dict=False) and abort(
400, ErrFormat.topology_group_exists.format(name))
return cls.group_cls.create(name=name, order=order)
def update_group(self, group_id, name, view_ids):
existed = self.group_cls.get_by_id(group_id) or abort(404, ErrFormat.not_found)
if name is not None and name != existed.name:
existed.update(name=name)
for idx, view_id in enumerate(view_ids):
view = self.cls.get_by_id(view_id)
if view is not None:
view.update(group_id=group_id, order=idx)
return existed.to_dict()
@classmethod
def delete_group(cls, _id):
existed = cls.group_cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
if cls.cls.get_by(group_id=_id, first=True):
return abort(400, ErrFormat.topo_view_exists_cannot_delete_group)
existed.soft_delete()
@classmethod
def group_order(cls, group_ids):
for idx, group_id in enumerate(group_ids):
group = cls.group_cls.get_by_id(group_id)
group.update(order=idx + 1)
@classmethod
def add(cls, name, group_id, option, order=None, **kwargs):
cls.cls.get_by(name=name, first=True) and abort(400, ErrFormat.topology_exists.format(name))
if order is None:
cur_max_order = cls.cls.get_by(group_id=group_id, only_query=True).order_by(
cls.cls.order.desc()).first()
cur_max_order = cur_max_order and cur_max_order.order or 0
order = cur_max_order + 1
inst = cls.cls.create(name=name, group_id=group_id, option=option, order=order, **kwargs).to_dict()
if current_app.config.get('USE_ACL'):
try:
ACLManager().add_resource(name, ResourceTypeEnum.TOPOLOGY_VIEW)
except BadRequest:
pass
ACLManager().grant_resource_to_role(name,
current_user.username,
ResourceTypeEnum.TOPOLOGY_VIEW)
return inst
@classmethod
def update(cls, _id, **kwargs):
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
existed_name = existed.name
inst = existed.update(filter_none=False, **kwargs).to_dict()
if current_app.config.get('USE_ACL') and existed_name != kwargs.get('name') and kwargs.get('name'):
try:
ACLManager().update_resource(existed_name, kwargs['name'], ResourceTypeEnum.TOPOLOGY_VIEW)
except BadRequest:
pass
return inst
@classmethod
def delete(cls, _id):
existed = cls.cls.get_by_id(_id) or abort(404, ErrFormat.not_found)
existed.soft_delete()
if current_app.config.get("USE_ACL"):
ACLManager().del_resource(existed.name, ResourceTypeEnum.TOPOLOGY_VIEW)
@classmethod
def group_inner_order(cls, _ids):
for idx, _id in enumerate(_ids):
topology = cls.cls.get_by_id(_id)
topology.update(order=idx + 1)
@classmethod
def get_all(cls):
resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.TOPOLOGY_VIEW)])
groups = cls.group_cls.get_by(to_dict=True)
groups = sorted(groups, key=lambda x: x['order'])
group2pos = {group['id']: idx for idx, group in enumerate(groups)}
topo_views = sorted(cls.cls.get_by(to_dict=True), key=lambda x: x['order'])
other_group = dict(views=[])
for view in topo_views:
if resources is not None and view['name'] not in resources:
continue
if view['group_id']:
groups[group2pos[view['group_id']]].setdefault('views', []).append(view)
else:
other_group['views'].append(view)
if other_group['views']:
groups.append(other_group)
return groups
@staticmethod
def relation_from_ci_type(type_id):
nodes, edges = CITypeRelationManager.get_relations_by_type_id(type_id)
return dict(nodes=nodes, edges=edges)
def topology_view(self, view_id=None, preview=None):
if view_id is not None:
view = self.cls.get_by_id(view_id) or abort(404, ErrFormat.not_found)
central_node_type, central_node_instances, path = (view.central_node_type,
view.central_node_instances, view.path)
else:
central_node_type = preview.get('central_node_type')
central_node_instances = preview.get('central_node_instances')
path = preview.get('path')
nodes, links = [], []
_type = CITypeCache.get(central_node_type)
if not _type:
return dict(nodes=nodes, links=links)
type2meta = {_type.id: _type.icon}
root_ids = []
show_key = AttributeCache.get(_type.show_id or _type.unique_id)
q = (central_node_instances[2:] if central_node_instances.startswith('q=') else
central_node_instances)
s = ci_search(q, fl=['_id', show_key.name], use_id_filter=False, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
current_app.logger.info(e)
return dict(nodes=nodes, links=links)
for i in response:
root_ids.append(i['_id'])
nodes.append(dict(id=str(i['_id']), name=i[show_key.name], type_id=central_node_type))
if not root_ids:
return dict(nodes=nodes, links=links)
prefix = REDIS_PREFIX_CI_RELATION
key = list(map(str, root_ids))
id2node = {}
for level in sorted([i for i in path.keys() if int(i) > 0]):
type_ids = {int(i) for i in path[level]}
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
new_key = []
for idx, from_id in enumerate(key):
for to_id, type_id in res[idx]:
if type_id in type_ids:
links.append({'from': from_id, 'to': to_id})
id2node[to_id] = {'id': to_id, 'type_id': type_id}
new_key.append(to_id)
if type_id not in type2meta:
type2meta[type_id] = CITypeCache.get(type_id).icon
key = new_key
ci_ids = list(map(int, root_ids))
for level in sorted([i for i in path.keys() if int(i) < 0]):
type_ids = {int(i) for i in path[level]}
res = CIRelationManager.get_parent_ids(ci_ids)
_ci_ids = []
for to_id in res:
for from_id, type_id in res[to_id]:
if type_id in type_ids:
from_id, to_id = str(from_id), str(to_id)
links.append({'from': from_id, 'to': to_id})
id2node[from_id] = {'id': str(from_id), 'type_id': type_id}
_ci_ids.append(from_id)
if type_id not in type2meta:
type2meta[type_id] = CITypeCache.get(type_id).icon
ci_ids = _ci_ids
fl = set()
type_ids = {t for lv in path if lv != '0' for t in path[lv]}
type2show = {}
for type_id in type_ids:
ci_type = CITypeCache.get(type_id)
if ci_type:
attr = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
if attr:
fl.add(attr.name)
type2show[type_id] = attr.name
if id2node:
s = ci_search("_id:({})".format(';'.join(id2node.keys())), fl=list(fl),
use_id_filter=False, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError:
return dict(nodes=nodes, links=links)
for i in response:
id2node[str(i['_id'])]['name'] = i[type2show[str(i['_type'])]]
nodes.extend(id2node.values())
return dict(nodes=nodes, links=links, type2meta=type2meta)

View File

@@ -28,13 +28,31 @@ def string2int(x):
return v return v
def str2datetime(x): def str2date(x):
try: try:
return datetime.datetime.strptime(x, "%Y-%m-%d").date() return datetime.datetime.strptime(x, "%Y-%m-%d").date()
except ValueError: except ValueError:
pass pass
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S") try:
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S").date()
except ValueError:
pass
def str2datetime(x):
x = x.replace('T', ' ')
x = x.replace('Z', '')
try:
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
except ValueError:
pass
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
class ValueTypeMap(object): class ValueTypeMap(object):
@@ -44,7 +62,7 @@ class ValueTypeMap(object):
ValueTypeEnum.TEXT: lambda x: x, ValueTypeEnum.TEXT: lambda x: x,
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0], ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
ValueTypeEnum.DATETIME: str2datetime, ValueTypeEnum.DATETIME: str2datetime,
ValueTypeEnum.DATE: str2datetime, ValueTypeEnum.DATE: str2date,
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
} }

View File

@@ -94,7 +94,7 @@ class AttributeValueManager(object):
except ValueDeserializeError as e: except ValueDeserializeError as e:
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e)) return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
except ValueError: except ValueError:
return abort(400, ErrFormat.attribute_value_invalid.format(value)) return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
@staticmethod @staticmethod
def _check_is_choice(attr, value_type, value): def _check_is_choice(attr, value_type, value):
@@ -123,9 +123,9 @@ class AttributeValueManager(object):
return abort(400, ErrFormat.attribute_value_required.format(attr.alias)) return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
@staticmethod @staticmethod
def check_re(expr, value): def check_re(expr, alias, value):
if not re.compile(expr).match(str(value)): if not re.compile(expr).match(str(value)):
return abort(400, ErrFormat.attribute_value_invalid.format(value)) return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None): def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
ci = ci or {} ci = ci or {}
@@ -141,7 +141,7 @@ class AttributeValueManager(object):
v = None v = None
if attr.re_check and value: if attr.re_check and value:
self.check_re(attr.re_check, value) self.check_re(attr.re_check, attr.alias, value)
return v return v
@@ -266,6 +266,7 @@ class AttributeValueManager(object):
:return: :return:
""" """
changed = [] changed = []
has_dynamic = False
for key, value in ci_dict.items(): for key, value in ci_dict.items():
attr = key2attr.get(key) attr = key2attr.get(key)
if not attr: if not attr:
@@ -274,41 +275,66 @@ class AttributeValueManager(object):
if attr.is_list: if attr.is_list:
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False) 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] existed_values = [(ValueTypeMap.serialize[attr.value_type](i.value) if
added = set(value) - set(existed_values) i.value or i.value == 0 else i.value) for i in existed_attrs]
deleted = set(existed_values) - set(value)
for v in added:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
for v in deleted: # Comparison array starts from which position changes
existed_attr = existed_attrs[existed_values.index(v)] min_len = min(len(value), len(existed_values))
index = 0
while index < min_len:
if value[index] != existed_values[index]:
break
index += 1
# Delete first and then add to ensure id sorting
for idx in range(index, len(existed_attrs)):
existed_attr = existed_attrs[idx]
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id)) if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.DELETE, existed_values[idx], None, ci.type_id))
else:
has_dynamic = True
for idx in range(index, len(value)):
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value[idx], flush=False, commit=False)
if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.ADD, None, value[idx], ci.type_id))
else:
has_dynamic = True
else: else:
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False) existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
existed_value = existed_attr and existed_attr.value existed_value = existed_attr and existed_attr.value
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
existed_value or existed_value == 0 else existed_value)
if existed_value is None and value is not None: if existed_value is None and value is not None:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False) value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id)) if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
else:
has_dynamic = True
else: else:
if existed_value != value: if existed_value != value and existed_attr:
if value is None: if value is None:
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
else: else:
existed_attr.update(value=value, flush=False, commit=False) existed_attr.update(value=value, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id)) if not attr.is_dynamic:
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
else:
has_dynamic = True
try: if changed or has_dynamic:
db.session.commit() try:
except Exception as e: db.session.commit()
db.session.rollback() except Exception as e:
current_app.logger.warning(str(e)) db.session.rollback()
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0])) current_app.logger.warning(str(e))
return abort(400, ErrFormat.attribute_value_unknown_error.format(e.args[0]))
return self.write_change2(changed, ticket_id=ticket_id) return self.write_change2(changed, ticket_id=ticket_id), has_dynamic
else:
return None, has_dynamic
@staticmethod @staticmethod
def delete_attr_value(attr_id, ci_id, commit=True): def delete_attr_value(attr_id, ci_id, commit=True):

View File

@@ -10,6 +10,11 @@ from api.lib.perm.acl.role import RoleCRUD, RoleRelationCRUD
from api.lib.perm.acl.user import UserCRUD from api.lib.perm.acl.user import UserCRUD
def validate_app(app_id):
app = AppCache.get(app_id)
return app.id if app else None
class ACLManager(object): class ACLManager(object):
def __init__(self, app_name='acl', uid=None): def __init__(self, app_name='acl', uid=None):
self.log = current_app.logger self.log = current_app.logger
@@ -133,7 +138,8 @@ class ACLManager(object):
numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size) numfound, res = ResourceCRUD.search(q, u, self.validate_app().id, rt_id, page, page_size)
return res return res
def grant_resource(self, rid, resource_id, perms): @staticmethod
def grant_resource(rid, resource_id, perms):
PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None) PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=None)
@staticmethod @staticmethod
@@ -141,3 +147,7 @@ class ACLManager(object):
rt = AppCRUD.add(**payload) rt = AppCRUD.add(**payload)
return rt.to_dict() return rt.to_dict()
def role_has_perms(self, rid, resource_name, resource_type_name, perm):
app_id = validate_app(self.app_name)
return RoleCRUD.has_permission(rid, resource_name, resource_type_name, app_id, perm)

View File

@@ -0,0 +1,38 @@
import functools
from flask import abort, session
from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.perm.acl.acl import is_app_admin
def perms_role_required(app_name, resource_type_name, resource_name, perm, role_name=None):
def decorator_perms_role_required(func):
@functools.wraps(func)
def wrapper_required(*args, **kwargs):
acl = ACLManager(app_name)
has_perms = False
try:
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
except Exception as e:
# resource_type not exist, continue check role
if role_name:
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
abort(403, ErrFormat.role_required.format(role_name))
return func(*args, **kwargs)
else:
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
if not has_perms:
if role_name:
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):
abort(403, ErrFormat.role_required.format(role_name))
else:
abort(403, ErrFormat.resource_no_permission.format(resource_name, perm))
return func(*args, **kwargs)
return wrapper_required
return decorator_perms_role_required

View File

@@ -80,3 +80,5 @@ class ErrFormat(CommonErrFormat):
ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填 ldap_test_username_required = _l("LDAP test username required") # LDAP测试用户名必填
company_wide = _l("Company wide") # 全公司 company_wide = _l("Company wide") # 全公司
resource_no_permission = _l("No permission to access resource {}, perm {} ") # 没有权限访问 {} 资源的 {} 权限"

View File

@@ -0,0 +1,64 @@
class OperationPermission(object):
def __init__(self, resource_perms):
for _r in resource_perms:
setattr(self, _r['page'], _r['page'])
for _p in _r['perms']:
setattr(self, _p, _p)
class BaseApp(object):
resource_type_name = 'OperationPermission'
all_resource_perms = []
def __init__(self):
self.admin_name = None
self.roles = []
self.app_name = 'acl'
self.require_create_resource_type = self.resource_type_name
self.extra_create_resource_type_list = []
self.op = None
@staticmethod
def format_role(role_name, role_type, acl_rid, resource_perms, description=''):
return dict(
role_name=role_name,
role_type=role_type,
acl_rid=acl_rid,
description=description,
resource_perms=resource_perms,
)
class CMDBApp(BaseApp):
all_resource_perms = [
{"page": "Big_Screen", "page_cn": "大屏", "perms": ["read"]},
{"page": "Dashboard", "page_cn": "仪表盘", "perms": ["read"]},
{"page": "Resource_Search", "page_cn": "资源搜索", "perms": ["read"]},
{"page": "Auto_Discovery_Pool", "page_cn": "自动发现池", "perms": ["read"]},
{"page": "My_Subscriptions", "page_cn": "我的订阅", "perms": ["read"]},
{"page": "Bulk_Import", "page_cn": "批量导入", "perms": ["read"]},
{"page": "Model_Configuration", "page_cn": "模型配置",
"perms": ["read", "create_CIType", "create_CIType_group", "update_CIType_group",
"delete_CIType_group", "download_CIType"]},
{"page": "Backend_Management", "page_cn": "后台管理", "perms": ["read"]},
{"page": "Customized_Dashboard", "page_cn": "定制仪表盘", "perms": ["read"]},
{"page": "Service_Tree_Definition", "page_cn": "服务树定义", "perms": ["read"]},
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]},
{"page": "TopologyView", "page_cn": "拓扑视图",
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
"create_topology_view"],
},
]
def __init__(self):
super().__init__()
self.admin_name = 'cmdb_admin'
self.app_name = 'cmdb'
self.op = OperationPermission(self.all_resource_perms)

View File

@@ -1,5 +1,10 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from datetime import datetime from datetime import datetime
from flask import current_app
from sqlalchemy import inspect, text
from sqlalchemy.dialects.mysql import ENUM
from api.extensions import db
def get_cur_time_str(split_flag='-'): def get_cur_time_str(split_flag='-'):
@@ -23,3 +28,115 @@ class BaseEnum(object):
if not attr.startswith("_") and not callable(getattr(cls, attr)) if not attr.startswith("_") and not callable(getattr(cls, attr))
} }
return cls._ALL_ return cls._ALL_
class CheckNewColumn(object):
def __init__(self):
self.engine = db.get_engine()
self.inspector = inspect(self.engine)
self.table_names = self.inspector.get_table_names()
@staticmethod
def get_model_by_table_name(_table_name):
registry = getattr(db.Model, 'registry', None)
class_registry = getattr(registry, '_class_registry', None)
for _model in class_registry.values():
if hasattr(_model, '__tablename__') and _model.__tablename__ == _table_name:
return _model
return None
def run(self):
for table_name in self.table_names:
self.check_by_table(table_name)
def check_by_table(self, table_name):
existed_columns = self.inspector.get_columns(table_name)
enum_columns = []
existed_column_name_list = []
for c in existed_columns:
if isinstance(c['type'], ENUM):
enum_columns.append(c['name'])
existed_column_name_list.append(c['name'])
model = self.get_model_by_table_name(table_name)
if model is None:
return
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
for column in model_columns:
if column.name not in existed_column_name_list:
add_res = self.add_new_column(table_name, column)
if not add_res:
continue
current_app.logger.info(f"add new column [{column.name}] in table [{table_name}] success.")
if column.name in enum_columns:
enum_columns.remove(column.name)
self.add_new_index(table_name, column)
if len(enum_columns) > 0:
self.check_enum_column(enum_columns, existed_columns, model_columns, table_name)
def add_new_column(self, target_table_name, new_column):
try:
column_type = new_column.type.compile(self.engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + f"`{new_column.name}`" + " " + column_type
if new_column.comment:
sql += f" comment '{new_column.comment}'"
if column_type == 'JSON':
pass
elif default_value:
if column_type.startswith('VAR') or column_type.startswith('Text'):
if default_value is None or len(default_value) == 0:
pass
else:
sql += f" DEFAULT {default_value}"
sql = text(sql)
db.session.execute(sql)
return True
except Exception as e:
err = f"add_new_column [{new_column.name}] to table [{target_table_name}] err: {e}"
current_app.logger.error(err)
return False
@staticmethod
def add_new_index(target_table_name, new_column):
try:
if new_column.index:
index_name = f"{target_table_name}_{new_column.name}"
sql = "CREATE INDEX " + f"{index_name}" + " ON " + target_table_name + " (" + new_column.name + ")"
db.session.execute(sql)
current_app.logger.info(f"add new index [{index_name}] in table [{target_table_name}] success.")
return True
except Exception as e:
err = f"add_new_index [{new_column.name}] to table [{target_table_name}] err: {e}"
current_app.logger.error(err)
return False
@staticmethod
def check_enum_column(enum_columns, existed_columns, model_columns, table_name):
for column_name in enum_columns:
try:
enum_column = list(filter(lambda x: x['name'] == column_name, existed_columns))[0]
old_enum_value = enum_column.get('type', {}).enums
target_column = list(filter(lambda x: x.name == column_name, model_columns))[0]
new_enum_value = target_column.type.enums
if set(old_enum_value) == set(new_enum_value):
continue
enum_values_str = ','.join(["'{}'".format(value) for value in new_enum_value])
sql = f"ALTER TABLE {table_name} MODIFY COLUMN" + f"`{column_name}`" + f" enum({enum_values_str})"
db.session.execute(sql)
current_app.logger.info(
f"modify column [{column_name}] ENUM: {new_enum_value} in table [{table_name}] success.")
except Exception as e:
current_app.logger.error(
f"modify column ENUM [{column_name}] in table [{table_name}] err: {e}")

View File

@@ -1,7 +1,6 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from flask import abort
from sqlalchemy import func from sqlalchemy import func
from api.extensions import db from api.extensions import db
@@ -13,17 +12,23 @@ class DBMixin(object):
cls = None cls = None
@classmethod @classmethod
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False, **kwargs): def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False,
last_size=None, **kwargs):
page = get_page(page) page = get_page(page)
page_size = get_page_size(page_size) page_size = get_page_size(page_size)
if fl is None: if fl is None:
query = db.session.query(cls.cls).filter(cls.cls.deleted.is_(False)) query = db.session.query(cls.cls)
else: else:
query = db.session.query(*[getattr(cls.cls, i) for i in fl]).filter(cls.cls.deleted.is_(False)) query = db.session.query(*[getattr(cls.cls, i) for i in fl])
_query = None _query = None
if count_query: if count_query:
_query = db.session.query(func.count(cls.cls.id)).filter(cls.cls.deleted.is_(False)) _query = db.session.query(func.count(cls.cls.id))
if hasattr(cls.cls, 'deleted'):
query = query.filter(cls.cls.deleted.is_(False))
if _query:
_query = _query.filter(cls.cls.deleted.is_(False))
for k in kwargs: for k in kwargs:
if hasattr(cls.cls, k): if hasattr(cls.cls, k):
@@ -40,14 +45,15 @@ class DBMixin(object):
return _query, query return _query, query
numfound = query.count() numfound = query.count()
return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')() if not last_size:
for i in query.offset((page - 1) * page_size).limit(page_size)] return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')()
for i in query.offset((page - 1) * page_size).limit(page_size)]
def _must_be_required(self, _id): else:
existed = self.cls.get_by_id(_id) offset = numfound - last_size
existed or abort(404, "Factor [{}] does not exist".format(_id)) if offset < 0:
offset = 0
return existed return numfound, [i.to_dict() if fl is None else getattr(i, '_asdict')()
for i in query.offset(offset).limit(last_size)]
def _can_add(self, **kwargs): def _can_add(self, **kwargs):
raise NotImplementedError raise NotImplementedError

View File

@@ -153,11 +153,11 @@ class ACLManager(object):
if resource: if resource:
return ResourceCRUD.delete(resource.id, rebuild=rebuild) return ResourceCRUD.delete(resource.id, rebuild=rebuild)
def has_permission(self, resource_name, resource_type, perm, resource_id=None): def has_permission(self, resource_name, resource_type, perm, resource_id=None, rid=None):
if is_app_admin(self.app_id): if is_app_admin(self.app_id):
return True return True
role = self._get_role(current_user.username) role = self._get_role(current_user.username) if rid is None else RoleCache.get(rid)
role or abort(404, ErrFormat.role_not_found.format(current_user.username)) role or abort(404, ErrFormat.role_not_found.format(current_user.username))

View File

@@ -5,6 +5,7 @@ import msgpack
import redis_lock import redis_lock
from api.extensions import cache from api.extensions import cache
from api.extensions import db
from api.extensions import rd from api.extensions import rd
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.models.acl import App from api.models.acl import App
@@ -157,9 +158,10 @@ class RoleRelationCache(object):
PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}" PREFIX_RESOURCES2 = "RoleRelationResources2::id::{0}::AppId::{1}"
@classmethod @classmethod
def get_parent_ids(cls, rid, app_id): def get_parent_ids(cls, rid, app_id, force=False):
parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id)) parent_ids = cache.get(cls.PREFIX_PARENT.format(rid, app_id))
if not parent_ids: if not parent_ids or force:
db.session.commit()
from api.lib.perm.acl.role import RoleRelationCRUD from api.lib.perm.acl.role import RoleRelationCRUD
parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id) parent_ids = RoleRelationCRUD.get_parent_ids(rid, app_id)
cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0) cache.set(cls.PREFIX_PARENT.format(rid, app_id), parent_ids, timeout=0)
@@ -167,9 +169,10 @@ class RoleRelationCache(object):
return parent_ids return parent_ids
@classmethod @classmethod
def get_child_ids(cls, rid, app_id): def get_child_ids(cls, rid, app_id, force=False):
child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id)) child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid, app_id))
if not child_ids: if not child_ids or force:
db.session.commit()
from api.lib.perm.acl.role import RoleRelationCRUD from api.lib.perm.acl.role import RoleRelationCRUD
child_ids = RoleRelationCRUD.get_child_ids(rid, app_id) child_ids = RoleRelationCRUD.get_child_ids(rid, app_id)
cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0) cache.set(cls.PREFIX_CHILDREN.format(rid, app_id), child_ids, timeout=0)
@@ -177,14 +180,16 @@ class RoleRelationCache(object):
return child_ids return child_ids
@classmethod @classmethod
def get_resources(cls, rid, app_id): def get_resources(cls, rid, app_id, force=False):
""" """
:param rid: :param rid:
:param app_id: :param app_id:
:param force:
:return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}} :return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}}
""" """
resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id)) resources = cache.get(cls.PREFIX_RESOURCES.format(rid, app_id))
if not resources: if not resources or force:
db.session.commit()
from api.lib.perm.acl.role import RoleCRUD from api.lib.perm.acl.role import RoleCRUD
resources = RoleCRUD.get_resources(rid, app_id) resources = RoleCRUD.get_resources(rid, app_id)
if resources['id2perms'] or resources['group2perms']: if resources['id2perms'] or resources['group2perms']:
@@ -193,9 +198,10 @@ class RoleRelationCache(object):
return resources or {} return resources or {}
@classmethod @classmethod
def get_resources2(cls, rid, app_id): def get_resources2(cls, rid, app_id, force=False):
r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id)) r_g = cache.get(cls.PREFIX_RESOURCES2.format(rid, app_id))
if not r_g: if not r_g or force:
db.session.commit()
res = cls.get_resources(rid, app_id) res = cls.get_resources(rid, app_id)
id2perms = res['id2perms'] id2perms = res['id2perms']
group2perms = res['group2perms'] group2perms = res['group2perms']
@@ -224,22 +230,28 @@ class RoleRelationCache(object):
@classmethod @classmethod
@flush_db @flush_db
def rebuild(cls, rid, app_id): def rebuild(cls, rid, app_id):
cls.clean(rid, app_id) if app_id is None:
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
cls.get_parent_ids(rid, app_id)
cls.get_child_ids(rid, app_id)
resources = cls.get_resources(rid, app_id)
if resources.get('id2perms') or resources.get('group2perms'):
HasResourceRoleCache.add(rid, app_id)
else: else:
HasResourceRoleCache.remove(rid, app_id) app_ids = [app_id]
cls.get_resources2(rid, app_id)
for _app_id in app_ids:
cls.clean(rid, _app_id)
cls.get_parent_ids(rid, _app_id, force=True)
cls.get_child_ids(rid, _app_id, force=True)
resources = cls.get_resources(rid, _app_id, force=True)
if resources.get('id2perms') or resources.get('group2perms'):
HasResourceRoleCache.add(rid, _app_id)
else:
HasResourceRoleCache.remove(rid, _app_id)
cls.get_resources2(rid, _app_id, force=True)
@classmethod @classmethod
@flush_db @flush_db
def rebuild2(cls, rid, app_id): def rebuild2(cls, rid, app_id):
cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id)) cache.delete(cls.PREFIX_RESOURCES2.format(rid, app_id))
cls.get_resources2(rid, app_id) cls.get_resources2(rid, app_id, force=True)
@classmethod @classmethod
def clean(cls, rid, app_id): def clean(cls, rid, app_id):

View File

@@ -79,7 +79,8 @@ class PermissionCRUD(object):
return r and cls.get_all(r.id) return r and cls.get_all(r.id)
@staticmethod @staticmethod
def grant(rid, perms, resource_id=None, group_id=None, rebuild=True, source=AuditOperateSource.acl): def grant(rid, perms, resource_id=None, group_id=None, rebuild=True,
source=AuditOperateSource.acl, force_update=False):
app_id = None app_id = None
rt_id = None rt_id = None
@@ -106,8 +107,23 @@ class PermissionCRUD(object):
if not perms: if not perms:
perms = [i.get('name') for i in ResourceTypeCRUD.get_perms(group.resource_type_id)] perms = [i.get('name') for i in ResourceTypeCRUD.get_perms(group.resource_type_id)]
_role_permissions = [] if force_update:
revoke_role_permissions = []
existed_perms = RolePermission.get_by(rid=rid,
app_id=app_id,
group_id=group_id,
resource_id=resource_id,
to_dict=False)
for role_perm in existed_perms:
perm = PermissionCache.get(role_perm.perm_id, rt_id)
if perm and perm.name not in perms:
role_perm.soft_delete()
revoke_role_permissions.append(role_perm)
AuditCRUD.add_permission_log(app_id, AuditOperateType.revoke, rid, rt_id,
revoke_role_permissions, source=source)
_role_permissions = []
for _perm in set(perms): for _perm in set(perms):
perm = PermissionCache.get(_perm, rt_id) perm = PermissionCache.get(_perm, rt_id)
if not perm: if not perm:
@@ -274,12 +290,14 @@ class PermissionCRUD(object):
perm2resource.setdefault(_perm, []).append(resource_id) perm2resource.setdefault(_perm, []).append(resource_id)
for _perm in perm2resource: for _perm in perm2resource:
perm = PermissionCache.get(_perm, resource_type_id) perm = PermissionCache.get(_perm, resource_type_id)
existeds = RolePermission.get_by(rid=rid, if perm is None:
app_id=app_id, continue
perm_id=perm.id, exists = RolePermission.get_by(rid=rid,
__func_in___key_resource_id=perm2resource[_perm], app_id=app_id,
to_dict=False) perm_id=perm.id,
for existed in existeds: __func_in___key_resource_id=perm2resource[_perm],
to_dict=False)
for existed in exists:
existed.deleted = True existed.deleted = True
existed.deleted_at = datetime.datetime.now() existed.deleted_at = datetime.datetime.now()
db.session.add(existed) db.session.add(existed)

View File

@@ -2,7 +2,6 @@
from flask import abort from flask import abort
from flask import current_app
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
existed_ids = [i.id for i in existed] existed_ids = [i.id for i in existed]
current_ids = [] current_ids = []
rebuild_rids = set()
for i in existed: for i in existed:
if i.name not in perms: if i.name not in perms:
i.soft_delete() i.soft_delete(commit=False)
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
rp.soft_delete(commit=False)
rebuild_rids.add((rp.app_id, rp.rid))
else: else:
current_ids.append(i.id) current_ids.append(i.id)
db.session.commit()
for _app_id, _rid in rebuild_rids:
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
for i in perms: for i in perms:
if i not in existed_names: if i not in existed_names:
@@ -309,9 +315,12 @@ class ResourceCRUD(object):
return resource return resource
@staticmethod @staticmethod
def delete(_id, rebuild=True): def delete(_id, rebuild=True, app_id=None):
resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id))) resource = Resource.get_by_id(_id) or abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
if app_id is not None and resource.app_id != app_id:
return abort(404, ErrFormat.resource_not_found.format("id={}".format(_id)))
origin = resource.to_dict() origin = resource.to_dict()
resource.soft_delete() resource.soft_delete()

View File

@@ -3,12 +3,14 @@
import time import time
import redis_lock
import six import six
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from sqlalchemy import or_ from sqlalchemy import or_
from api.extensions import db from api.extensions import db
from api.extensions import rd
from api.lib.perm.acl.app import AppCRUD from api.lib.perm.acl.app import AppCRUD
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
from api.lib.perm.acl.cache import AppCache from api.lib.perm.acl.cache import AppCache
@@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
id2parents = {} id2parents = {}
for i in res: for i in res:
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict()) parent = RoleCache.get(i.parent_id)
if parent:
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
return id2parents return id2parents
@@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
@classmethod @classmethod
def add(cls, role, parent_id, child_ids, app_id): def add(cls, role, parent_id, child_ids, app_id):
result = [] with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
for child_id in child_ids: db.session.commit()
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
if existed:
continue
RoleRelationCache.clean(parent_id, app_id) result = []
RoleRelationCache.clean(child_id, app_id) for child_id in child_ids:
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
if existed:
continue
if parent_id in cls.recursive_child_ids(child_id, app_id): if parent_id in cls.recursive_child_ids(child_id, app_id):
return abort(400, ErrFormat.inheritance_dead_loop) return abort(400, ErrFormat.inheritance_dead_loop)
if app_id is None: result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
for app in AppCRUD.get_all():
if app.name != "acl":
RoleRelationCache.clean(child_id, app.id)
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict()) RoleRelationCache.clean(parent_id, app_id)
RoleRelationCache.clean(child_id, app_id)
if app_id is None:
for app in AppCRUD.get_all():
if app.name != "acl":
RoleRelationCache.clean(child_id, app.id)
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add, AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
AuditScope.role_relation, role.id, {}, {}, AuditScope.role_relation, role.id, {}, {},
@@ -372,16 +379,16 @@ class RoleCRUD(object):
resource_type_id = resource_type and resource_type.id resource_type_id = resource_type and resource_type.id
result = dict(resources=dict(), groups=dict()) result = dict(resources=dict(), groups=dict())
s = time.time() # s = time.time()
parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id) parent_ids = RoleRelationCRUD.recursive_parent_ids(rid, app_id)
current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s)) # current_app.logger.info('parent ids {0}: {1}'.format(parent_ids, time.time() - s))
for parent_id in parent_ids: for parent_id in parent_ids:
_resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id) _resources, _groups = cls._extend_resources(parent_id, resource_type_id, app_id)
current_app.logger.info('middle1: {0}'.format(time.time() - s)) # current_app.logger.info('middle1: {0}'.format(time.time() - s))
_merge(result['resources'], _resources) _merge(result['resources'], _resources)
current_app.logger.info('middle2: {0}'.format(time.time() - s)) # current_app.logger.info('middle2: {0}'.format(time.time() - s))
current_app.logger.info(len(_groups)) # current_app.logger.info(len(_groups))
if not group_flat: if not group_flat:
_merge(result['groups'], _groups) _merge(result['groups'], _groups)
else: else:
@@ -392,7 +399,7 @@ class RoleCRUD(object):
item.setdefault('permissions', []) item.setdefault('permissions', [])
item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions'])) item['permissions'] = list(set(item['permissions'] + _groups[rg_id]['permissions']))
result['resources'][item['id']] = item result['resources'][item['id']] = item
current_app.logger.info('End: {0}'.format(time.time() - s)) # current_app.logger.info('End: {0}'.format(time.time() - s))
result['resources'] = list(result['resources'].values()) result['resources'] = list(result['resources'].values())
result['groups'] = list(result['groups'].values()) result['groups'] = list(result['groups'].values())

View File

@@ -51,12 +51,12 @@ def _auth_with_key():
user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path) user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path)
if user and authenticated: if user and authenticated:
login_user(user) login_user(user)
reset_session(user) # reset_session(user)
return True return True
role, authenticated = Role.query.authenticate_with_key(key, secret, req_args, path) role, authenticated = Role.query.authenticate_with_key(key, secret, req_args, path)
if role and authenticated: if role and authenticated:
reset_session(None, role=role.name) # reset_session(None, role=role.name)
return True return True
return False return False

View File

@@ -29,6 +29,6 @@ class CommonErrFormat(object):
role_required = _l("Role {} can only operate!") # 角色 {} 才能操作! role_required = _l("Role {} can only operate!") # 角色 {} 才能操作!
user_not_found = _l("User {} does not exist") # 用户 {} 不存在 user_not_found = _l("User {} does not exist") # 用户 {} 不存在
no_permission = _l("You do not have {} permission for resource: {}!") # 您没有资源: {} 的{}权限! no_permission = _l("For resource: {}, you do not have {} permission!") # 您没有资源: {} 的{}权限!
no_permission2 = _l("You do not have permission to operate!") # 您没有操作权限! no_permission2 = _l("You do not have permission to operate!") # 您没有操作权限!
no_permission_only_owner = _l("Only the creator or administrator has permission!") # 只有创建人或者管理员才有权限! no_permission_only_owner = _l("Only the creator or administrator has permission!") # 只有创建人或者管理员才有权限!

View File

@@ -1,19 +1,15 @@
import json
import os import os
import secrets import secrets
import sys import sys
import threading
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from Cryptodome.Protocol.SecretSharing import Shamir from Cryptodome.Protocol.SecretSharing import Shamir
from colorama import Back from colorama import Back, Fore, Style, init as colorama_init
from colorama import Fore
from colorama import Style
from colorama import init as colorama_init
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import modes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from flask import current_app from flask import current_app
@@ -27,11 +23,17 @@ backend_encrypt_key_name = "encrypt_key"
backend_root_key_salt_name = "root_key_salt" backend_root_key_salt_name = "root_key_salt"
backend_encrypt_key_salt_name = "encrypt_key_salt" backend_encrypt_key_salt_name = "encrypt_key_salt"
backend_seal_key = "seal_status" backend_seal_key = "seal_status"
success = "success" success = "success"
seal_status = True seal_status = True
secrets_encrypt_key = ""
secrets_root_key = ""
def string_to_bytes(value): def string_to_bytes(value):
if not value:
return ""
if isinstance(value, bytes): if isinstance(value, bytes):
return value return value
if sys.version_info.major == 2: if sys.version_info.major == 2:
@@ -44,6 +46,8 @@ def string_to_bytes(value):
class Backend: class Backend:
def __init__(self, backend=None): def __init__(self, backend=None):
self.backend = backend self.backend = backend
# cache is a redis object
self.cache = backend.cache
def get(self, key): def get(self, key):
return self.backend.get(key) return self.backend.get(key)
@@ -54,23 +58,33 @@ class Backend:
def update(self, key, value): def update(self, key, value):
return self.backend.update(key, value) return self.backend.update(key, value)
def get_shares(self, key):
return self.backend.get_shares(key)
def set_shares(self, key, value):
return self.backend.set_shares(key, value)
class KeyManage: class KeyManage:
def __init__(self, trigger=None, backend=None): def __init__(self, trigger=None, backend=None):
self.trigger = trigger self.trigger = trigger
self.backend = backend self.backend = backend
self.share_key = "cmdb::secret::secrets_share"
if backend: if backend:
self.backend = Backend(backend) self.backend = Backend(backend)
def init_app(self, app, backend=None): def init_app(self, app, backend=None):
if (sys.argv[0].endswith("gunicorn") or if (sys.argv[0].endswith("gunicorn") or
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))): (len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
self.backend = backend
threading.Thread(target=self.watch_root_key, args=(app,), daemon=True).start()
self.trigger = app.config.get("INNER_TRIGGER_TOKEN") self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
if not self.trigger: if not self.trigger:
return return
self.backend = backend
resp = self.auto_unseal() resp = self.auto_unseal()
self.print_response(resp) self.print_response(resp)
@@ -124,6 +138,8 @@ class KeyManage:
return new_shares return new_shares
def is_valid_root_key(self, root_key): def is_valid_root_key(self, root_key):
if not root_key:
return False
root_key_hash, ok = self.hash_root_key(root_key) root_key_hash, ok = self.hash_root_key(root_key)
if not ok: if not ok:
return root_key_hash, ok return root_key_hash, ok
@@ -135,35 +151,42 @@ class KeyManage:
else: else:
return "", True return "", True
def auth_root_secret(self, root_key): def auth_root_secret(self, root_key, app):
msg, ok = self.is_valid_root_key(root_key) with app.app_context():
if not ok: msg, ok = self.is_valid_root_key(root_key)
return { if not ok:
"message": msg, return {
"status": "failed" "message": msg,
} "status": "failed"
}
encrypt_key_aes = self.backend.get(backend_encrypt_key_name) encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
if not encrypt_key_aes: if not encrypt_key_aes:
return { return {
"message": "encrypt key is empty", "message": "encrypt key is empty",
"status": "failed" "status": "failed"
} }
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes) secret_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
if ok:
msg, ok = self.backend.update(backend_seal_key, "open")
if ok: if ok:
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key msg, ok = self.backend.update(backend_seal_key, "open")
current_app.config["secrets_root_key"] = root_key if ok:
current_app.config["secrets_shares"] = [] global secrets_encrypt_key, secrets_root_key
return {"message": success, "status": success} secrets_encrypt_key = secret_encrypt_key
return {"message": msg, "status": "failed"} secrets_root_key = root_key
else: self.backend.cache.set(self.share_key, json.dumps([]))
return { return {"message": success, "status": success}
"message": secrets_encrypt_key, return {"message": msg, "status": "failed"}
"status": "failed" else:
} return {
"message": secret_encrypt_key,
"status": "failed"
}
def parse_shares(self, shares, app):
if len(shares) >= global_key_threshold:
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
return self.auth_root_secret(b64encode(recovered_secret), app)
def unseal(self, key): def unseal(self, key):
if not self.is_seal(): if not self.is_seal():
@@ -175,14 +198,12 @@ class KeyManage:
try: try:
t = [i for i in b64decode(key)] t = [i for i in b64decode(key)]
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2])) v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
shares = current_app.config.get("secrets_shares", []) shares = self.backend.get_shares(self.share_key)
if v not in shares: if v not in shares:
shares.append(v) shares.append(v)
current_app.config["secrets_shares"] = shares self.set_shares(shares)
if len(shares) >= global_key_threshold: if len(shares) >= global_key_threshold:
recovered_secret = Shamir.combine(shares[:global_key_threshold], False) return self.parse_shares(shares, current_app)
return self.auth_root_secret(b64encode(recovered_secret))
else: else:
return { return {
"message": "waiting for inputting other unseal key {0}/{1}".format(len(shares), "message": "waiting for inputting other unseal key {0}/{1}".format(len(shares),
@@ -242,8 +263,11 @@ class KeyManage:
msg, ok = self.backend.add(backend_seal_key, "open") msg, ok = self.backend.add(backend_seal_key, "open")
if not ok: if not ok:
return {"message": msg, "status": "failed"}, False return {"message": msg, "status": "failed"}, False
current_app.config["secrets_root_key"] = root_key
current_app.config["secrets_encrypt_key"] = encrypt_key global secrets_encrypt_key, secrets_root_key
secrets_encrypt_key = encrypt_key
secrets_root_key = root_key
self.print_token(shares, root_token=root_key) self.print_token(shares, root_token=root_key)
return {"message": "OK", return {"message": "OK",
@@ -266,7 +290,7 @@ class KeyManage:
} }
# TODO # TODO
elif len(self.trigger.strip()) == 24: elif len(self.trigger.strip()) == 24:
res = self.auth_root_secret(self.trigger.encode()) res = self.auth_root_secret(self.trigger.encode(), current_app)
if res.get("status") == success: if res.get("status") == success:
return { return {
"message": success, "message": success,
@@ -298,22 +322,31 @@ class KeyManage:
"message": msg, "message": msg,
"status": "failed", "status": "failed",
} }
current_app.config["secrets_root_key"] = '' self.clear()
current_app.config["secrets_encrypt_key"] = '' self.backend.cache.publish(self.share_key, "clear")
return { return {
"message": success, "message": success,
"status": success "status": success
} }
@staticmethod
def clear():
global secrets_encrypt_key, secrets_root_key
secrets_encrypt_key = ''
secrets_root_key = ''
def is_seal(self): def is_seal(self):
""" """
If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state. If there is no initialization or the root key is inconsistent, it is considered to be in a sealed state..
:return: :return:
""" """
secrets_root_key = current_app.config.get("secrets_root_key") # secrets_root_key = current_app.config.get("secrets_root_key")
if not secrets_root_key:
return True
msg, ok = self.is_valid_root_key(secrets_root_key) msg, ok = self.is_valid_root_key(secrets_root_key)
if not ok: if not ok:
return true return True
status = self.backend.get(backend_seal_key) status = self.backend.get(backend_seal_key)
return status == "block" return status == "block"
@@ -349,22 +382,53 @@ class KeyManage:
} }
print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL) print(status_colors.get(status, Fore.GREEN), message, Style.RESET_ALL)
def set_shares(self, values):
new_value = list()
for v in values:
new_value.append((v[0], b64encode(v[1]).decode("utf-8")))
self.backend.cache.publish(self.share_key, json.dumps(new_value))
self.backend.cache.set(self.share_key, json.dumps(new_value))
def watch_root_key(self, app):
pubsub = self.backend.cache.pubsub()
pubsub.subscribe(self.share_key)
new_value = set()
for message in pubsub.listen():
if message["type"] == "message":
if message["data"] == b"clear":
self.clear()
continue
try:
value = json.loads(message["data"].decode("utf-8"))
for v in value:
new_value.add((v[0], b64decode(v[1])))
except Exception as e:
return []
if len(new_value) >= global_key_threshold:
self.parse_shares(list(new_value), app)
new_value = set()
class InnerCrypt: class InnerCrypt:
def __init__(self): def __init__(self):
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "") self.encrypt_key = b64decode(secrets_encrypt_key)
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8")) # self.encrypt_key = b64decode(secrets_encrypt_key, "".encode("utf-8"))
def encrypt(self, plaintext): def encrypt(self, plaintext):
""" """
encrypt method contain aes currently encrypt method contain aes currently
""" """
if not self.encrypt_key:
return ValueError("secret is disabled, please seal firstly"), False
return self.aes_encrypt(self.encrypt_key, plaintext) return self.aes_encrypt(self.encrypt_key, plaintext)
def decrypt(self, ciphertext): def decrypt(self, ciphertext):
""" """
decrypt method contain aes currently decrypt method contain aes currently
""" """
if not self.encrypt_key:
return ValueError("secret is disabled, please seal firstly"), False
return self.aes_decrypt(self.encrypt_key, ciphertext) return self.aes_decrypt(self.encrypt_key, ciphertext)
@classmethod @classmethod
@@ -381,6 +445,7 @@ class InnerCrypt:
return b64encode(iv + ciphertext).decode("utf-8"), True return b64encode(iv + ciphertext).decode("utf-8"), True
except Exception as e: except Exception as e:
return str(e), False return str(e), False
@classmethod @classmethod

View File

@@ -1,8 +1,13 @@
import base64
import json
from api.models.cmdb import InnerKV from api.models.cmdb import InnerKV
from api.extensions import rd
class InnerKVManger(object): class InnerKVManger(object):
def __init__(self): def __init__(self):
self.cache = rd.r
pass pass
@classmethod @classmethod
@@ -33,3 +38,26 @@ class InnerKVManger(object):
return "success", True return "success", True
return "update failed", True return "update failed", True
@classmethod
def get_shares(cls, key):
new_value = list()
v = rd.get_str(key)
if not v:
return new_value
try:
value = json.loads(v.decode("utf-8"))
for v in value:
new_value.append((v[0], base64.b64decode(v[1])))
except Exception as e:
return []
return new_value
@classmethod
def set_shares(cls, key, value):
new_value = list()
for v in value:
new_value.append((v[0], base64.b64encode(v[1]).decode("utf-8")))
rd.set_str(key, json.dumps(new_value))

View File

@@ -117,6 +117,23 @@ class RedisHandler(object):
except Exception as e: except Exception as e:
current_app.logger.error("delete redis key error, {0}".format(str(e))) current_app.logger.error("delete redis key error, {0}".format(str(e)))
def set_str(self, key, value, expired=None):
try:
if expired:
self.r.setex(key, expired, value)
else:
self.r.set(key, value)
except Exception as e:
current_app.logger.error("set redis error, {0}".format(str(e)))
def get_str(self, key):
try:
value = self.r.get(key)
except Exception as e:
current_app.logger.error("get redis error, {0}".format(str(e)))
return
return value
class ESHandler(object): class ESHandler(object):
def __init__(self, flask_app=None): def __init__(self, flask_app=None):

View File

@@ -88,11 +88,11 @@ def webhook_request(webhook, payload):
params = webhook.get('parameters') or None params = webhook.get('parameters') or None
if isinstance(params, dict): if isinstance(params, dict):
params = json.loads(Template(json.dumps(params)).render(payload)) params = json.loads(Template(json.dumps(params)).render(payload).encode('utf-8'))
headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload)) headers = json.loads(Template(json.dumps(webhook.get('headers') or {})).render(payload))
data = Template(json.dumps(webhook.get('body', ''))).render(payload) data = Template(json.dumps(webhook.get('body', ''))).render(payload).encode('utf-8')
auth = _wrap_auth(**webhook.get('authorization', {})) auth = _wrap_auth(**webhook.get('authorization', {}))
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0': if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':

View File

@@ -2,7 +2,6 @@
import datetime import datetime
from sqlalchemy.dialects.mysql import DOUBLE from sqlalchemy.dialects.mysql import DOUBLE
from api.extensions import db from api.extensions import db
@@ -11,6 +10,7 @@ from api.lib.cmdb.const import CIStatusEnum
from api.lib.cmdb.const import CITypeOperateType from api.lib.cmdb.const import CITypeOperateType
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.database import Model from api.lib.database import Model
from api.lib.database import Model2 from api.lib.database import Model2
@@ -46,13 +46,17 @@ class CIType(Model):
name = db.Column(db.String(32), nullable=False) name = db.Column(db.String(32), nullable=False)
alias = db.Column(db.String(32), nullable=False) alias = db.Column(db.String(32), nullable=False)
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False) unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
show_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
enabled = db.Column(db.Boolean, default=True, nullable=False) enabled = db.Column(db.Boolean, default=True, nullable=False)
is_attached = db.Column(db.Boolean, default=False, nullable=False) is_attached = db.Column(db.Boolean, default=False, nullable=False)
icon = db.Column(db.Text) icon = db.Column(db.Text)
order = db.Column(db.SmallInteger, default=0, nullable=False) order = db.Column(db.SmallInteger, default=0, nullable=False)
default_order_attr = db.Column(db.String(33)) default_order_attr = db.Column(db.String(33))
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id") unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id",
primaryjoin="Attribute.id==CIType.unique_id", foreign_keys=[unique_id])
show_key = db.relationship("Attribute", backref="c_ci_types.show_id",
primaryjoin="Attribute.id==CIType.show_id", foreign_keys=[show_id])
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
@@ -75,8 +79,11 @@ class CITypeRelation(Model):
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False) relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many) constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
parent_attr_ids = db.Column(db.JSON) # [parent_attr_id, ]
child_attr_ids = db.Column(db.JSON) # [child_attr_id, ]
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id") parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id") child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
@@ -97,6 +104,7 @@ class Attribute(Model):
is_link = db.Column(db.Boolean, default=False) is_link = db.Column(db.Boolean, default=False)
is_password = db.Column(db.Boolean, default=False) is_password = db.Column(db.Boolean, default=False)
is_sortable = db.Column(db.Boolean, default=False) is_sortable = db.Column(db.Boolean, default=False)
is_dynamic = db.Column(db.Boolean, default=False)
default = db.Column(db.JSON) # {"default": None} default = db.Column(db.JSON) # {"default": None}
@@ -205,6 +213,26 @@ class CITriggerHistory(Model):
webhook = db.Column(db.Text) webhook = db.Column(db.Text)
class TopologyViewGroup(Model):
__tablename__ = 'c_topology_view_groups'
name = db.Column(db.String(64), index=True)
order = db.Column(db.Integer, default=0)
class TopologyView(Model):
__tablename__ = 'c_topology_views'
name = db.Column(db.String(64), index=True)
group_id = db.Column(db.Integer, db.ForeignKey('c_topology_view_groups.id'))
category = db.Column(db.String(32))
central_node_type = db.Column(db.Integer)
central_node_instances = db.Column(db.Text)
path = db.Column(db.JSON)
order = db.Column(db.Integer, default=0)
option = db.Column(db.JSON)
class CITypeUniqueConstraint(Model): class CITypeUniqueConstraint(Model):
__tablename__ = "c_c_t_u_c" __tablename__ = "c_c_t_u_c"
@@ -232,6 +260,7 @@ class CIRelation(Model):
second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False) second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False)
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False) relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
more = db.Column(db.Integer, db.ForeignKey("c_cis.id")) more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
source = db.Column(db.Enum(*RelationSourceEnum.all()), name="source")
ancestor_ids = db.Column(db.String(128), index=True) ancestor_ids = db.Column(db.String(128), index=True)
@@ -428,6 +457,7 @@ class CITypeHistory(Model):
attr_id = db.Column(db.Integer) attr_id = db.Column(db.Integer)
trigger_id = db.Column(db.Integer) trigger_id = db.Column(db.Integer)
rc_id = db.Column(db.Integer)
unique_constraint_id = db.Column(db.Integer) unique_constraint_id = db.Column(db.Integer)
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
@@ -537,18 +567,28 @@ class AutoDiscoveryCIType(Model):
attributes = db.Column(db.JSON) # {ad_key: cmdb_key} attributes = db.Column(db.JSON) # {ad_key: cmdb_key}
relation = db.Column(db.JSON) # [{ad_key: {type_id: x, attr_id: x}}] relation = db.Column(db.JSON) # [{ad_key: {type_id: x, attr_id: x}}], CMDB > 2.4.5: deprecated
auto_accept = db.Column(db.Boolean, default=False) auto_accept = db.Column(db.Boolean, default=False)
agent_id = db.Column(db.String(8), index=True) agent_id = db.Column(db.String(8), index=True)
query_expr = db.Column(db.Text) query_expr = db.Column(db.Text)
interval = db.Column(db.Integer) # seconds interval = db.Column(db.Integer) # seconds, > 2.4.5: deprecated
cron = db.Column(db.String(128)) cron = db.Column(db.String(128))
extra_option = db.Column(db.JSON) extra_option = db.Column(db.JSON)
uid = db.Column(db.Integer, index=True) uid = db.Column(db.Integer, index=True)
enabled = db.Column(db.Boolean, default=True)
class AutoDiscoveryCITypeRelation(Model):
__tablename__ = "c_ad_ci_type_relations"
ad_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
ad_key = db.Column(db.String(128))
peer_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
peer_attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
class AutoDiscoveryCI(Model): class AutoDiscoveryCI(Model):
@@ -566,6 +606,45 @@ class AutoDiscoveryCI(Model):
accept_time = db.Column(db.DateTime) accept_time = db.Column(db.DateTime)
class AutoDiscoveryRuleSyncHistory(Model2):
__tablename__ = "c_ad_rule_sync_histories"
adt_id = db.Column(db.Integer, db.ForeignKey('c_ad_ci_types.id'))
oneagent_id = db.Column(db.String(8))
oneagent_name = db.Column(db.String(64))
sync_at = db.Column(db.DateTime, default=datetime.datetime.now())
class AutoDiscoveryExecHistory(Model2):
__tablename__ = "c_ad_exec_histories"
type_id = db.Column(db.Integer, index=True)
stdout = db.Column(db.Text)
class AutoDiscoveryCounter(Model2):
__tablename__ = "c_ad_counter"
type_id = db.Column(db.Integer, index=True)
rule_count = db.Column(db.Integer, default=0)
exec_target_count = db.Column(db.Integer, default=0)
instance_count = db.Column(db.Integer, default=0)
accept_count = db.Column(db.Integer, default=0)
this_month_count = db.Column(db.Integer, default=0)
this_week_count = db.Column(db.Integer, default=0)
last_month_count = db.Column(db.Integer, default=0)
last_week_count = db.Column(db.Integer, default=0)
class AutoDiscoveryAccount(Model):
__tablename__ = "c_ad_accounts"
uid = db.Column(db.Integer, index=True)
name = db.Column(db.String(64))
adr_id = db.Column(db.Integer, db.ForeignKey('c_ad_rules.id'))
config = db.Column(db.JSON)
class CIFilterPerms(Model): class CIFilterPerms(Model):
__tablename__ = "c_ci_filter_perms" __tablename__ = "c_ci_filter_perms"

View File

@@ -3,12 +3,13 @@
import json import json
import re import re
from celery_once import QueueOnce import redis_lock
from flask import current_app from flask import current_app
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from api.extensions import celery from api.extensions import celery
from api.extensions import rd
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@@ -25,14 +26,14 @@ from api.models.acl import Role
from api.models.acl import Trigger from api.models.acl import Trigger
@celery.task(name="acl.role_rebuild", @celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
queue=ACL_QUEUE,)
@flush_db @flush_db
@reconnect_db @reconnect_db
def role_rebuild(rids, app_id): def role_rebuild(rids, app_id):
rids = rids if isinstance(rids, list) else [rids] rids = rids if isinstance(rids, list) else [rids]
for rid in rids: for rid in rids:
RoleRelationCache.rebuild(rid, app_id) with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
RoleRelationCache.rebuild(rid, app_id)
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id)) current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))

View File

@@ -1,8 +1,8 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import datetime
import json import json
import redis_lock import redis_lock
from flask import current_app from flask import current_app
from flask_login import login_user from flask_login import login_user
@@ -12,16 +12,21 @@ from api.extensions import celery
from api.extensions import db from api.extensions import db
from api.extensions import es from api.extensions import es
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
@@ -55,10 +60,10 @@ def ci_cache(ci_id, operate_type, record_id):
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE) @celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
@reconnect_db @reconnect_db
def rebuild_relation_for_attribute_changed(ci_type_relation): def rebuild_relation_for_attribute_changed(ci_type_relation, uid):
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
CIRelationManager.rebuild_all_by_attribute(ci_type_relation) CIRelationManager.rebuild_all_by_attribute(ci_type_relation, uid)
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
@@ -87,6 +92,13 @@ def ci_delete(ci_id):
else: else:
rd.delete(ci_id, REDIS_PREFIX_CI) rd.delete(ci_id, REDIS_PREFIX_CI)
instance = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
if instance is not None:
adt = AutoDiscoveryCIType.get_by_id(instance.adt_id)
if adt:
adt.update(updated_at=datetime.datetime.now())
instance.delete()
current_app.logger.info("{0} delete..........".format(ci_id)) current_app.logger.info("{0} delete..........".format(ci_id))
@@ -113,18 +125,17 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@reconnect_db @reconnect_db
def ci_relation_cache(parent_id, child_id, ancestor_ids): def ci_relation_cache(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
if ancestor_ids is None: children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] children = json.loads(children) if children is not None else {}
children = json.loads(children) if children is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids, cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False) first=True, to_dict=False)
if str(child_id) not in children: if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id children[str(child_id)] = cr.second_ci.type_id
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else: if ancestor_ids is not None:
key = "{},{}".format(ancestor_ids, parent_id) key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0] grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {} grandson = json.loads(grandson) if grandson is not None else {}
@@ -191,16 +202,15 @@ def ci_relation_add(parent_dict, child_id, uid):
@reconnect_db @reconnect_db
def ci_relation_delete(parent_id, child_id, ancestor_ids): def ci_relation_delete(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)): with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
if ancestor_ids is None: children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0] children = json.loads(children) if children is not None else {}
children = json.loads(children) if children is not None else {}
if str(child_id) in children: if str(child_id) in children:
children.pop(str(child_id)) children.pop(str(child_id))
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else: if ancestor_ids is not None:
key = "{},{}".format(ancestor_ids, parent_id) key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0] grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {} grandson = json.loads(grandson) if grandson is not None else {}
@@ -251,3 +261,93 @@ def calc_computed_attribute(attr_id, uid):
cis = CI.get_by(type_id=i.type_id, to_dict=False) cis = CI.get_by(type_id=i.type_id, to_dict=False)
for ci in cis: for ci in cis:
cim.update(ci.id, {}) cim.update(ci.id, {})
@celery.task(name="cmdb.write_ad_rule_sync_history", queue=CMDB_QUEUE)
@reconnect_db
def write_ad_rule_sync_history(rules, oneagent_id, oneagent_name, sync_at):
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleSyncHistoryCRUD
for rule in rules:
AutoDiscoveryRuleSyncHistoryCRUD().upsert(adt_id=rule['id'],
oneagent_id=oneagent_id,
oneagent_name=oneagent_name,
sync_at=sync_at,
commit=False)
try:
db.session.commit()
except Exception as e:
current_app.logger.error("write auto discovery rule sync history failed: {}".format(e))
db.session.rollback()
@celery.task(name="cmdb.build_relations_for_ad_accept", queue=CMDB_QUEUE)
@reconnect_db
def build_relations_for_ad_accept(adc, ci_id, ad_key2attr):
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
current_app.test_request_context().push()
login_user(UserCache.get('worker'))
relation_ads = AutoDiscoveryCITypeRelation.get_by(ad_type_id=adc['type_id'], to_dict=False)
for r_adt in relation_ads:
ad_key = r_adt.ad_key
if not adc['instance'].get(ad_key):
continue
ad_key_values = [adc['instance'].get(ad_key)] if not isinstance(
adc['instance'].get(ad_key), list) else adc['instance'].get(ad_key)
for ad_key_value in ad_key_values:
query = "_type:{},{}:{}".format(r_adt.peer_type_id, r_adt.peer_attr_id, ad_key_value)
s = ci_search(query, use_ci_filter=False, count=1000000)
try:
response, _, _, _, _, _ = s.search()
except SearchError as e:
current_app.logger.error("build_relations_for_ad_accept failed: {}".format(e))
return
for relation_ci in response:
relation_ci_id = relation_ci['_id']
try:
CIRelationManager.add(ci_id, relation_ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
try:
CIRelationManager.add(relation_ci_id, ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
pass
# build relations in reverse
relation_ads = AutoDiscoveryCITypeRelation.get_by(peer_type_id=adc['type_id'], to_dict=False)
attr2ad_key = {v: k for k, v in ad_key2attr.items()}
for r_adt in relation_ads:
attr = AttributeCache.get(r_adt.peer_attr_id)
ad_key = attr2ad_key.get(attr and attr.name)
if not ad_key:
continue
ad_value = adc['instance'].get(ad_key)
peer_ad_key = r_adt.ad_key
peer_instances = AutoDiscoveryCI.get_by(type_id=r_adt.ad_type_id, to_dict=False)
for peer_instance in peer_instances:
peer_ad_values = peer_instance.instance.get(peer_ad_key)
peer_ad_values = [peer_ad_values] if not isinstance(peer_ad_values, list) else peer_ad_values
if ad_value in peer_ad_values and peer_instance.ci_id:
try:
CIRelationManager.add(peer_instance.ci_id, ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
try:
CIRelationManager.add(ci_id, peer_instance.ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
pass

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-03-29 10:42+0800\n" "POT-Creation-Date: 2024-06-20 19:12+0800\n"
"PO-Revision-Date: 2023-12-25 20:21+0800\n" "PO-Revision-Date: 2023-12-25 20:21+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh\n" "Language: zh\n"
@@ -81,7 +81,7 @@ msgid "User {} does not exist"
msgstr "用户 {} 不存在" msgstr "用户 {} 不存在"
#: api/lib/resp_format.py:32 #: api/lib/resp_format.py:32
msgid "You do not have {} permission for resource: {}!" msgid "For resource: {}, you do not have {} permission!"
msgstr "您没有资源: {} 的{}权限!" msgstr "您没有资源: {} 的{}权限!"
#: api/lib/resp_format.py:33 #: api/lib/resp_format.py:33
@@ -238,212 +238,248 @@ msgstr "因为CI已经存在不能删除模型"
msgid "The inheritance cannot be deleted because the CI already exists" msgid "The inheritance cannot be deleted because the CI already exists"
msgstr "因为CI已经存在不能删除继承关系" msgstr "因为CI已经存在不能删除继承关系"
#: api/lib/cmdb/resp_format.py:67 #: api/lib/cmdb/resp_format.py:65
msgid "The model is inherited and cannot be deleted"
msgstr "该模型被继承, 不能删除"
#: api/lib/cmdb/resp_format.py:68
msgid "" msgid ""
"The model cannot be deleted because the model is referenced by the " "The model cannot be deleted because the model is referenced by the "
"relational view {}" "relational view {}"
msgstr "因为关系视图 {} 引用了该模型,不能删除模型" msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
#: api/lib/cmdb/resp_format.py:69 #: api/lib/cmdb/resp_format.py:70
msgid "Model group {} does not exist" msgid "Model group {} does not exist"
msgstr "模型分组 {} 不存在" msgstr "模型分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:70 #: api/lib/cmdb/resp_format.py:71
msgid "Model group {} already exists" msgid "Model group {} already exists"
msgstr "模型分组 {} 已经存在" msgstr "模型分组 {} 已经存在"
#: api/lib/cmdb/resp_format.py:71 #: api/lib/cmdb/resp_format.py:72
msgid "Model relationship {} does not exist" msgid "Model relationship {} does not exist"
msgstr "模型关系 {} 不存在" msgstr "模型关系 {} 不存在"
#: api/lib/cmdb/resp_format.py:72 #: api/lib/cmdb/resp_format.py:73
msgid "Attribute group {} already exists" msgid "Attribute group {} already exists"
msgstr "属性分组 {} 已存在" msgstr "属性分组 {} 已存在"
#: api/lib/cmdb/resp_format.py:73 #: api/lib/cmdb/resp_format.py:74
msgid "Attribute group {} does not exist" msgid "Attribute group {} does not exist"
msgstr "属性分组 {} 不存在" msgstr "属性分组 {} 不存在"
#: api/lib/cmdb/resp_format.py:75 #: api/lib/cmdb/resp_format.py:76
msgid "Attribute group <{0}> - attribute <{1}> does not exist" msgid "Attribute group <{0}> - attribute <{1}> does not exist"
msgstr "属性组<{0}> - 属性<{1}> 不存在" msgstr "属性组<{0}> - 属性<{1}> 不存在"
#: api/lib/cmdb/resp_format.py:76 #: api/lib/cmdb/resp_format.py:77
msgid "The unique constraint already exists!" msgid "The unique constraint already exists!"
msgstr "唯一约束已经存在!" msgstr "唯一约束已经存在!"
#: api/lib/cmdb/resp_format.py:78 #: api/lib/cmdb/resp_format.py:79
msgid "Uniquely constrained attributes cannot be JSON and multi-valued" msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
msgstr "唯一约束的属性不能是 JSON 和 多值" msgstr "唯一约束的属性不能是 JSON 和 多值"
#: api/lib/cmdb/resp_format.py:79 #: api/lib/cmdb/resp_format.py:80
msgid "Duplicated trigger" msgid "Duplicated trigger"
msgstr "重复的触发器" msgstr "重复的触发器"
#: api/lib/cmdb/resp_format.py:80 #: api/lib/cmdb/resp_format.py:81
msgid "Trigger {} does not exist" msgid "Trigger {} does not exist"
msgstr "触发器 {} 不存在" msgstr "触发器 {} 不存在"
#: api/lib/cmdb/resp_format.py:82 #: api/lib/cmdb/resp_format.py:82
msgid "Duplicated reconciliation rule"
msgstr ""
#: api/lib/cmdb/resp_format.py:83
msgid "Reconciliation rule {} does not exist"
msgstr "关系类型 {} 不存在"
#: api/lib/cmdb/resp_format.py:85
msgid "Operation record {} does not exist" msgid "Operation record {} does not exist"
msgstr "操作记录 {} 不存在" msgstr "操作记录 {} 不存在"
#: api/lib/cmdb/resp_format.py:83 #: api/lib/cmdb/resp_format.py:86
msgid "Unique identifier cannot be deleted" msgid "Unique identifier cannot be deleted"
msgstr "不能删除唯一标识" msgstr "不能删除唯一标识"
#: api/lib/cmdb/resp_format.py:84 #: api/lib/cmdb/resp_format.py:87
msgid "Cannot delete default sorted attributes" msgid "Cannot delete default sorted attributes"
msgstr "不能删除默认排序的属性" msgstr "不能删除默认排序的属性"
#: api/lib/cmdb/resp_format.py:86 #: api/lib/cmdb/resp_format.py:89
msgid "No node selected" msgid "No node selected"
msgstr "没有选择节点" msgstr "没有选择节点"
#: api/lib/cmdb/resp_format.py:87 #: api/lib/cmdb/resp_format.py:90
msgid "This search option does not exist!" msgid "This search option does not exist!"
msgstr "该搜索选项不存在!" msgstr "该搜索选项不存在!"
#: api/lib/cmdb/resp_format.py:88 #: api/lib/cmdb/resp_format.py:91
msgid "This search option has a duplicate name!" msgid "This search option has a duplicate name!"
msgstr "该搜索选项命名重复!" msgstr "该搜索选项命名重复!"
#: api/lib/cmdb/resp_format.py:90 #: api/lib/cmdb/resp_format.py:93
msgid "Relationship type {} already exists" msgid "Relationship type {} already exists"
msgstr "关系类型 {} 已经存在" msgstr "关系类型 {} 已经存在"
#: api/lib/cmdb/resp_format.py:91 #: api/lib/cmdb/resp_format.py:94
msgid "Relationship type {} does not exist" msgid "Relationship type {} does not exist"
msgstr "关系类型 {} 不存在" msgstr "关系类型 {} 不存在"
#: api/lib/cmdb/resp_format.py:93 #: api/lib/cmdb/resp_format.py:96
msgid "Invalid attribute value: {}" msgid "Invalid attribute value: {}"
msgstr "无效的属性值: {}" msgstr "无效的属性值: {}"
#: api/lib/cmdb/resp_format.py:94 #: api/lib/cmdb/resp_format.py:97
msgid "{} Invalid value: {}" msgid "{} Invalid value: {}"
msgstr "{} 无效的值: {}" msgstr "{} 无效的值: {}"
#: api/lib/cmdb/resp_format.py:95 #: api/lib/cmdb/resp_format.py:98
msgid "{} is not in the predefined values" msgid "{} is not in the predefined values"
msgstr "{} 不在预定义值里" msgstr "{} 不在预定义值里"
#: api/lib/cmdb/resp_format.py:97 #: api/lib/cmdb/resp_format.py:100
msgid "The value of attribute {} must be unique, {} already exists" msgid "The value of attribute {} must be unique, {} already exists"
msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在" msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
#: api/lib/cmdb/resp_format.py:98 #: api/lib/cmdb/resp_format.py:101
msgid "Attribute {} value must exist" msgid "Attribute {} value must exist"
msgstr "属性 {} 值必须存在" msgstr "属性 {} 值必须存在"
#: api/lib/cmdb/resp_format.py:99 #: api/lib/cmdb/resp_format.py:102
msgid "Out of range value, the maximum value is 2147483647" msgid "Out of range value, the maximum value is 2147483647"
msgstr "超过最大值限制, 最大值是2147483647" msgstr "超过最大值限制, 最大值是2147483647"
#: api/lib/cmdb/resp_format.py:101 #: api/lib/cmdb/resp_format.py:104
msgid "Unknown error when adding or modifying attribute value: {}" msgid "Unknown error when adding or modifying attribute value: {}"
msgstr "新增或者修改属性值未知错误: {}" msgstr "新增或者修改属性值未知错误: {}"
#: api/lib/cmdb/resp_format.py:103 #: api/lib/cmdb/resp_format.py:106
msgid "Duplicate custom name" msgid "Duplicate custom name"
msgstr "订制名重复" msgstr "订制名重复"
#: api/lib/cmdb/resp_format.py:105 #: api/lib/cmdb/resp_format.py:108
msgid "Number of models exceeds limit: {}" msgid "Number of models exceeds limit: {}"
msgstr "模型数超过限制: {}" msgstr "模型数超过限制: {}"
#: api/lib/cmdb/resp_format.py:106 #: api/lib/cmdb/resp_format.py:109
msgid "The number of CIs exceeds the limit: {}" msgid "The number of CIs exceeds the limit: {}"
msgstr "CI数超过限制: {}" msgstr "CI数超过限制: {}"
#: api/lib/cmdb/resp_format.py:108 #: api/lib/cmdb/resp_format.py:111
msgid "Auto-discovery rule: {} already exists!" msgid "Auto-discovery rule: {} already exists!"
msgstr "自动发现规则: {} 已经存在!" msgstr "自动发现规则: {} 已经存在!"
#: api/lib/cmdb/resp_format.py:109 #: api/lib/cmdb/resp_format.py:112
msgid "Auto-discovery rule: {} does not exist!" msgid "Auto-discovery rule: {} does not exist!"
msgstr "自动发现规则: {} 不存在!" msgstr "自动发现规则: {} 不存在!"
#: api/lib/cmdb/resp_format.py:111 #: api/lib/cmdb/resp_format.py:114
msgid "This auto-discovery rule is referenced by the model and cannot be deleted!" msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
msgstr "该自动发现规则被模型引用, 不能删除!" msgstr "该自动发现规则被模型引用, 不能删除!"
#: api/lib/cmdb/resp_format.py:113 #: api/lib/cmdb/resp_format.py:116
msgid "The application of auto-discovery rules cannot be defined repeatedly!" msgid "The application of auto-discovery rules cannot be defined repeatedly!"
msgstr "自动发现规则的应用不能重复定义!" msgstr "自动发现规则的应用不能重复定义!"
#: api/lib/cmdb/resp_format.py:114 #: api/lib/cmdb/resp_format.py:117
msgid "The auto-discovery you want to modify: {} does not exist!" msgid "The auto-discovery you want to modify: {} does not exist!"
msgstr "您要修改的自动发现: {} 不存在!" msgstr "您要修改的自动发现: {} 不存在!"
#: api/lib/cmdb/resp_format.py:115 #: api/lib/cmdb/resp_format.py:118
msgid "Attribute does not include unique identifier: {}" msgid "Attribute does not include unique identifier: {}"
msgstr "属性字段没有包括唯一标识: {}" msgstr "属性字段没有包括唯一标识: {}"
#: api/lib/cmdb/resp_format.py:116 #: api/lib/cmdb/resp_format.py:119
msgid "The auto-discovery instance does not exist!" msgid "The auto-discovery instance does not exist!"
msgstr "自动发现的实例不存在!" msgstr "自动发现的实例不存在!"
#: api/lib/cmdb/resp_format.py:117 #: api/lib/cmdb/resp_format.py:120
msgid "The model is not associated with this auto-discovery!" msgid "The model is not associated with this auto-discovery!"
msgstr "模型并未关联该自动发现!" msgstr "模型并未关联该自动发现!"
#: api/lib/cmdb/resp_format.py:118 #: api/lib/cmdb/resp_format.py:121
msgid "Only the creator can modify the Secret!" msgid "Only the creator can modify the Secret!"
msgstr "只有创建人才能修改Secret!" msgstr "只有创建人才能修改Secret!"
#: api/lib/cmdb/resp_format.py:120 #: api/lib/cmdb/resp_format.py:123
msgid "This rule already has auto-discovery instances and cannot be deleted!" msgid "This rule already has auto-discovery instances and cannot be deleted!"
msgstr "该规则已经有自动发现的实例, 不能被删除!" msgstr "该规则已经有自动发现的实例, 不能被删除!"
#: api/lib/cmdb/resp_format.py:122 #: api/lib/cmdb/resp_format.py:125
msgid "The default auto-discovery rule is already referenced by model {}!" msgid "The default auto-discovery rule is already referenced by model {}!"
msgstr "该默认的自动发现规则 已经被模型 {} 引用!" msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
#: api/lib/cmdb/resp_format.py:124 #: api/lib/cmdb/resp_format.py:127
msgid "The unique_key method must return a non-empty string!" msgid "The unique_key method must return a non-empty string!"
msgstr "unique_key方法必须返回非空字符串!" msgstr "unique_key方法必须返回非空字符串!"
#: api/lib/cmdb/resp_format.py:125 #: api/lib/cmdb/resp_format.py:128
msgid "The attributes method must return a list" msgid "The attributes method must return a list"
msgstr "attributes方法必须返回的是list" msgstr "attributes方法必须返回的是list"
#: api/lib/cmdb/resp_format.py:127 #: api/lib/cmdb/resp_format.py:130
msgid "The list returned by the attributes method cannot be empty!" msgid "The list returned by the attributes method cannot be empty!"
msgstr "attributes方法返回的list不能为空!" msgstr "attributes方法返回的list不能为空!"
#: api/lib/cmdb/resp_format.py:129 #: api/lib/cmdb/resp_format.py:132
msgid "Only administrators can define execution targets as: all nodes!" msgid "Only administrators can define execution targets as: all nodes!"
msgstr "只有管理员才可以定义执行机器为: 所有节点!" msgstr "只有管理员才可以定义执行机器为: 所有节点!"
#: api/lib/cmdb/resp_format.py:130 #: api/lib/cmdb/resp_format.py:133
msgid "Execute targets permission check failed: {}" msgid "Execute targets permission check failed: {}"
msgstr "执行机器权限检查不通过: {}" msgstr "执行机器权限检查不通过: {}"
#: api/lib/cmdb/resp_format.py:132 #: api/lib/cmdb/resp_format.py:135
msgid "CI filter authorization must be named!" msgid "CI filter authorization must be named!"
msgstr "CI过滤授权 必须命名!" msgstr "CI过滤授权 必须命名!"
#: api/lib/cmdb/resp_format.py:133 #: api/lib/cmdb/resp_format.py:136
msgid "CI filter authorization is currently not supported or query" msgid "CI filter authorization is currently not supported or query"
msgstr "CI过滤授权 暂时不支持 或 查询" msgstr "CI过滤授权 暂时不支持 或 查询"
#: api/lib/cmdb/resp_format.py:136 #: api/lib/cmdb/resp_format.py:139
msgid "You do not have permission to operate attribute {}!" msgid "You do not have permission to operate attribute {}!"
msgstr "您没有属性 {} 的操作权限!" msgstr "您没有属性 {} 的操作权限!"
#: api/lib/cmdb/resp_format.py:137 #: api/lib/cmdb/resp_format.py:140
msgid "You do not have permission to operate this CI!" msgid "You do not have permission to operate this CI!"
msgstr "您没有该CI的操作权限!" msgstr "您没有该CI的操作权限!"
#: api/lib/cmdb/resp_format.py:139 #: api/lib/cmdb/resp_format.py:142
msgid "Failed to save password: {}" msgid "Failed to save password: {}"
msgstr "保存密码失败: {}" msgstr "保存密码失败: {}"
#: api/lib/cmdb/resp_format.py:140 #: api/lib/cmdb/resp_format.py:143
msgid "Failed to get password: {}" msgid "Failed to get password: {}"
msgstr "获取密码失败: {}" msgstr "获取密码失败: {}"
#: api/lib/cmdb/resp_format.py:145
msgid "Scheduling time format error"
msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
#: api/lib/cmdb/resp_format.py:146
msgid "CMDB data reconciliation results"
msgstr ""
#: api/lib/cmdb/resp_format.py:147
msgid "Number of {} illegal: {}"
msgstr ""
#: api/lib/cmdb/resp_format.py:149
msgid "Topology view {} already exists"
msgstr "拓扑视图 {} 已经存在"
#: api/lib/cmdb/resp_format.py:150
msgid "Topology group {} already exists"
msgstr "拓扑视图分组 {} 已经存在"
#: api/lib/cmdb/resp_format.py:152
msgid "The group cannot be deleted because the topology view already exists"
msgstr "因为该分组下定义了拓扑视图,不能删除"
#: api/lib/common_setting/resp_format.py:8 #: api/lib/common_setting/resp_format.py:8
msgid "Company info already existed" msgid "Company info already existed"
msgstr "公司信息已存在,无法创建!" msgstr "公司信息已存在,无法创建!"
@@ -700,6 +736,10 @@ msgstr "LDAP测试用户名必填"
msgid "Company wide" msgid "Company wide"
msgstr "全公司" msgstr "全公司"
#: api/lib/common_setting/resp_format.py:84
msgid "No permission to access resource {}, perm {} "
msgstr "您没有资源: {} 的 {} 权限"
#: api/lib/perm/acl/resp_format.py:9 #: api/lib/perm/acl/resp_format.py:9
msgid "login successful" msgid "login successful"
msgstr "登录成功" msgstr "登录成功"

View File

@@ -1,24 +1,32 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import copy
import json import json
from io import BytesIO import uuid
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask_login import current_user from flask_login import current_user
from io import BytesIO
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryAccountCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCICRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCICRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeRelationCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryComponentsManager
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCounterCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryExecHistoryCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryHTTPManager from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryHTTPManager
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleSyncHistoryCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoverySNMPManager from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoverySNMPManager
from api.lib.cmdb.auto_discovery.const import DEFAULT_HTTP from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search from api.lib.cmdb.search.ci import search as ci_search
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
@@ -37,14 +45,19 @@ class AutoDiscoveryRuleView(APIView):
rebuild = False rebuild = False
exists = {i['name'] for i in res} exists = {i['name'] for i in res}
for i in DEFAULT_HTTP: for i in copy.deepcopy(DEFAULT_INNER):
if i['name'] not in exists: if i['name'] not in exists:
i.pop('en', None)
AutoDiscoveryRuleCRUD().add(**i) AutoDiscoveryRuleCRUD().add(**i)
rebuild = True rebuild = True
if rebuild: if rebuild:
_, res = AutoDiscoveryRuleCRUD.search(page=1, page_size=100000, **request.values) _, res = AutoDiscoveryRuleCRUD.search(page=1, page_size=100000, **request.values)
for i in res:
if i['type'] == 'http':
i['resources'] = AutoDiscoveryHTTPManager().get_resources(i['name'])
return self.jsonify(res) return self.jsonify(res)
@args_required("name", value_required=True) @args_required("name", value_required=True)
@@ -98,24 +111,39 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
class AutoDiscoveryRuleHTTPView(APIView): class AutoDiscoveryRuleHTTPView(APIView):
url_prefix = ("/adr/http/<string:name>/categories", "/adr/http/<string:name>/attributes", url_prefix = ("/adr/http/<string:name>/categories",
"/adr/snmp/<string:name>/attributes") "/adr/http/<string:name>/attributes",
"/adr/http/<string:name>/mapping",
"/adr/snmp/<string:name>/attributes",
"/adr/components/<string:name>/attributes",)
def get(self, name): def get(self, name):
if "snmp" in request.url: if "snmp" in request.url:
return self.jsonify(AutoDiscoverySNMPManager.get_attributes()) return self.jsonify(AutoDiscoverySNMPManager.get_attributes())
if "components" in request.url:
return self.jsonify(AutoDiscoveryComponentsManager.get_attributes(name))
if "attributes" in request.url: if "attributes" in request.url:
category = request.values.get('category') resource = request.values.get('resource')
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, category)) return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, resource))
if "mapping" in request.url:
resource = request.values.get('resource')
return self.jsonify(AutoDiscoveryHTTPManager.get_mapping(name, resource))
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name)) return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
class AutoDiscoveryCITypeView(APIView): class AutoDiscoveryCITypeView(APIView):
url_prefix = ("/adt/ci_types/<int:type_id>", "/adt/<int:adt_id>") url_prefix = ("/adt/ci_types/<int:type_id>",
"/adt/ci_types/<int:type_id>/attributes",
"/adt/<int:adt_id>")
def get(self, type_id): def get(self, type_id):
if "attributes" in request.url:
return self.jsonify(AutoDiscoveryCITypeCRUD.get_ad_attributes(type_id))
_, res = AutoDiscoveryCITypeCRUD.search(page=1, page_size=100000, type_id=type_id, **request.values) _, res = AutoDiscoveryCITypeCRUD.search(page=1, page_size=100000, type_id=type_id, **request.values)
for i in res: for i in res:
if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('secret'): if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('secret'):
@@ -123,6 +151,11 @@ class AutoDiscoveryCITypeView(APIView):
i['extra_option'].pop('secret', None) i['extra_option'].pop('secret', None)
else: else:
i['extra_option']['secret'] = AESCrypto.decrypt(i['extra_option']['secret']) i['extra_option']['secret'] = AESCrypto.decrypt(i['extra_option']['secret'])
if isinstance(i.get("extra_option"), dict) and i['extra_option'].get('password'):
if not (current_user.username == "cmdb_agent" or current_user.uid == i['uid']):
i['extra_option'].pop('password', None)
else:
i['extra_option']['password'] = AESCrypto.decrypt(i['extra_option']['password'])
return self.jsonify(res) return self.jsonify(res)
@@ -146,6 +179,27 @@ class AutoDiscoveryCITypeView(APIView):
return self.jsonify(adt_id=adt_id) return self.jsonify(adt_id=adt_id)
class AutoDiscoveryCITypeRelationView(APIView):
url_prefix = ("/adt/ci_types/<int:type_id>/relations", "/adt/relations/<int:_id>")
def get(self, type_id):
_, res = AutoDiscoveryCITypeRelationCRUD.search(page=1, page_size=100000, ad_type_id=type_id, **request.values)
return self.jsonify(res)
@args_required("relations")
def post(self, type_id):
return self.jsonify(AutoDiscoveryCITypeRelationCRUD().upsert(type_id, request.values['relations']))
def put(self):
return self.post()
def delete(self, _id):
AutoDiscoveryCITypeRelationCRUD().delete(_id)
return self.jsonify(id=_id)
class AutoDiscoveryCIView(APIView): class AutoDiscoveryCIView(APIView):
url_prefix = ("/adc", "/adc/<int:adc_id>", "/adc/ci_types/<int:type_id>/attributes", "/adc/ci_types") url_prefix = ("/adc", "/adc/<int:adc_id>", "/adc/ci_types/<int:type_id>/attributes", "/adc/ci_types")
@@ -213,24 +267,127 @@ class AutoDiscoveryRuleSyncView(APIView):
url_prefix = ("/adt/sync",) url_prefix = ("/adt/sync",)
def get(self): def get(self):
if current_user.username not in ("cmdb_agent", "worker", "admin"): if current_user.username not in PRIVILEGED_USERS:
return abort(403) return abort(403)
oneagent_name = request.values.get('oneagent_name') oneagent_name = request.values.get('oneagent_name')
oneagent_id = request.values.get('oneagent_id') oneagent_id = request.values.get('oneagent_id')
last_update_at = request.values.get('last_update_at') last_update_at = request.values.get('last_update_at')
query = "{},oneagent_id:{}".format(oneagent_name, oneagent_id) response = []
current_app.logger.info(query) if AttributeCache.get('oneagent_id'):
s = search(query) query = "oneagent_id:{}".format(oneagent_id)
try: s = ci_search(query)
response, _, _, _, _, _ = s.search() try:
except SearchError as e: response, _, _, _, _, _ = s.search()
import traceback except SearchError as e:
current_app.logger.error(traceback.format_exc()) import traceback
return abort(400, str(e)) current_app.logger.error(traceback.format_exc())
return abort(400, str(e))
ci_id = response and response[0]["_id"] for res in response:
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(ci_id, oneagent_id, last_update_at) if res.get('{}_name'.format(res['ci_type'])) == oneagent_name or oneagent_name == res.get('oneagent_name'):
ci_id = res["_id"]
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(ci_id, oneagent_id, oneagent_name, last_update_at)
return self.jsonify(rules=rules, last_update_at=last_update_at)
rules, last_update_at = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
return self.jsonify(rules=rules, last_update_at=last_update_at) return self.jsonify(rules=rules, last_update_at=last_update_at)
class AutoDiscoveryRuleSyncHistoryView(APIView):
url_prefix = ("/adt/<int:adt_id>/sync/histories",)
def get(self, adt_id):
page = get_page(request.values.pop('page', 1))
page_size = get_page_size(request.values.pop('page_size', None))
numfound, res = AutoDiscoveryRuleSyncHistoryCRUD.search(page=page,
page_size=page_size,
adt_id=adt_id,
**request.values)
return self.jsonify(page=page,
page_size=page_size,
numfound=numfound,
total=len(res),
result=res)
class AutoDiscoveryTestView(APIView):
url_prefix = ("/adt/<int:adt_id>/test", "/adt/test/<string:exec_id>/result")
def get(self, exec_id):
return self.jsonify(stdout="1\n2\n3", exec_id=exec_id)
def post(self, adt_id):
return self.jsonify(exec_id=uuid.uuid4().hex)
class AutoDiscoveryExecHistoryView(APIView):
url_prefix = ("/adc/exec/histories",)
@args_required('type_id')
def get(self):
page = get_page(request.values.pop('page', 1))
page_size = get_page_size(request.values.pop('page_size', None))
last_size = request.values.pop('last_size', None)
if last_size and last_size.isdigit():
last_size = int(last_size)
numfound, res = AutoDiscoveryExecHistoryCRUD.search(page=page,
page_size=page_size,
last_size=last_size,
**request.values)
return self.jsonify(page=page,
page_size=page_size,
numfound=numfound,
total=len(res),
result=res)
@args_required('type_id')
@args_required('stdout')
def post(self):
AutoDiscoveryExecHistoryCRUD().add(type_id=request.values.get('type_id'),
stdout=request.values.get('stdout'))
return self.jsonify(code=200)
class AutoDiscoveryCounterView(APIView):
url_prefix = ("/adc/counter",)
@args_required('type_id')
def get(self):
type_id = request.values.get('type_id')
return self.jsonify(AutoDiscoveryCounterCRUD().get(type_id))
class AutoDiscoveryAccountView(APIView):
url_prefix = ("/adr/accounts", "/adr/accounts/<int:account_id>")
@args_required('adr_id')
def get(self):
adr_id = request.values.get('adr_id')
return self.jsonify(AutoDiscoveryAccountCRUD().get(adr_id))
@args_required('adr_id')
@args_required('accounts', value_required=False)
def post(self):
AutoDiscoveryAccountCRUD().upsert(**request.values)
return self.jsonify(code=200)
@args_required('config')
def put(self, account_id):
res = AutoDiscoveryAccountCRUD().update(account_id, **request.values)
return self.jsonify(res.to_dict())
def delete(self, account_id):
AutoDiscoveryAccountCRUD().delete(account_id)
return self.jsonify(account_id=account_id)

View File

@@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
from api.lib.cmdb.perms import has_perm_for_ci from api.lib.cmdb.perms import has_perm_for_ci
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search from api.lib.cmdb.search.ci import search
from api.lib.decorator import args_required
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
@@ -254,3 +255,24 @@ class CIPasswordView(APIView):
def post(self, ci_id, attr_id): def post(self, ci_id, attr_id):
return self.get(ci_id, attr_id) return self.get(ci_id, attr_id)
class CIBaselineView(APIView):
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
@args_required("before_date")
def get(self):
ci_ids = handle_arg_list(request.values.get('ci_ids'))
before_date = request.values.get('before_date')
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
@args_required("before_date")
@has_perm_for_ci("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type)
def post(self, ci_id):
if 'rollback' in request.url:
before_date = request.values.get('before_date')
return self.jsonify(**CIManager().rollback(ci_id, before_date))
return self.get(ci_id)

View File

@@ -30,6 +30,7 @@ class CIRelationSearchView(APIView):
level: default is 1 level: default is 1
facet: statistic facet: statistic
""" """
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
count = get_page_size(request.values.get("count") or request.values.get("page_size")) count = get_page_size(request.values.get("count") or request.values.get("page_size"))
@@ -86,6 +87,26 @@ class CIRelationStatisticsView(APIView):
return self.jsonify(result) return self.jsonify(result)
class CIRelationSearchFullView(APIView):
url_prefix = "/ci_relations/search/full"
def get(self):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1)
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
start = time.time()
s = Search(root_ids, level, has_m2m=has_m2m)
try:
result = s.search_full(type_ids)
except SearchError as e:
return abort(400, str(e))
current_app.logger.debug("search time is :{0}".format(time.time() - start))
return self.jsonify(result)
class GetSecondCIsView(APIView): class GetSecondCIsView(APIView):
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis" url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"

View File

@@ -7,7 +7,6 @@ from io import BytesIO
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask import session
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
@@ -19,10 +18,12 @@ from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.ci_type import CITypeTemplateManager from api.lib.cmdb.ci_type import CITypeTemplateManager
from api.lib.cmdb.ci_type import CITypeTriggerManager from api.lib.cmdb.ci_type import CITypeTriggerManager
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
@@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CITypeView(APIView): class CITypeView(APIView):
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>", url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
@@ -48,7 +51,11 @@ class CITypeView(APIView):
q = request.args.get("type_name") q = request.args.get("type_name")
if type_id is not None: if type_id is not None:
ci_type = CITypeCache.get(type_id).to_dict() ci_type = CITypeCache.get(type_id)
if ci_type is None:
return abort(404, ErrFormat.ci_type_not_found)
ci_type = ci_type.to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id) ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
ci_types = [ci_type] ci_types = [ci_type]
elif type_name is not None: elif type_name is not None:
@@ -116,7 +123,6 @@ class CITypeInheritanceView(APIView):
class CITypeGroupView(APIView): class CITypeGroupView(APIView):
url_prefix = ("/ci_types/groups", url_prefix = ("/ci_types/groups",
"/ci_types/groups/config", "/ci_types/groups/config",
"/ci_types/groups/order",
"/ci_types/groups/<int:gid>") "/ci_types/groups/<int:gid>")
def get(self): def get(self):
@@ -125,7 +131,8 @@ class CITypeGroupView(APIView):
return self.jsonify(CITypeGroupManager.get(need_other, config_required)) return self.jsonify(CITypeGroupManager.get(need_other, config_required))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.create_CIType_group, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def post(self): def post(self):
@@ -136,15 +143,6 @@ class CITypeGroupView(APIView):
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def put(self, gid=None): def put(self, gid=None):
if "/order" in request.url:
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
group_ids = request.values.get('group_ids')
CITypeGroupManager.order(group_ids)
return self.jsonify(group_ids=group_ids)
name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name")) name = request.values.get('name') or abort(400, ErrFormat.argument_value_required.format("name"))
type_ids = request.values.get('type_ids') type_ids = request.values.get('type_ids')
@@ -152,7 +150,8 @@ class CITypeGroupView(APIView):
return self.jsonify(gid=gid) return self.jsonify(gid=gid)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.delete_CIType_group, app_cli.admin_name)
def delete(self, gid): def delete(self, gid):
type_ids = request.values.get("type_ids") type_ids = request.values.get("type_ids")
CITypeGroupManager.delete(gid, type_ids) CITypeGroupManager.delete(gid, type_ids)
@@ -160,6 +159,18 @@ class CITypeGroupView(APIView):
return self.jsonify(gid=gid) return self.jsonify(gid=gid)
class CITypeGroupOrderView(APIView):
url_prefix = "/ci_types/groups/order"
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.update_CIType_group, app_cli.admin_name)
def put(self):
group_ids = request.values.get('group_ids')
CITypeGroupManager.order(group_ids)
return self.jsonify(group_ids=group_ids)
class CITypeQueryView(APIView): class CITypeQueryView(APIView):
url_prefix = "/ci_types/query" url_prefix = "/ci_types/query"
@@ -350,16 +361,16 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView): class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export") url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
def get(self, type_id=None): # export app_cli.op.download_CIType, app_cli.admin_name)
if type_id is not None: def get(self): # export
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id))) type_ids = list(map(int, handle_arg_list(request.values.get('type_ids')))) or None
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template(type_ids=type_ids)))
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template())) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
@role_required(RoleEnum.CONFIG)
def post(self): # import def post(self): # import
tpt = request.values.get('ci_type_template') or {} tpt = request.values.get('ci_type_template') or {}
@@ -379,7 +390,8 @@ class CITypeCanDefineComputed(APIView):
class CITypeTemplateFileView(APIView): class CITypeTemplateFileView(APIView):
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file") url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def get(self): # export def get(self): # export
tpt_json = CITypeTemplateManager.export_template() tpt_json = CITypeTemplateManager.export_template()
tpt_json = dict(ci_type_template=tpt_json) tpt_json = dict(ci_type_template=tpt_json)
@@ -394,7 +406,8 @@ class CITypeTemplateFileView(APIView):
mimetype='application/json', mimetype='application/json',
max_age=0) max_age=0)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def post(self): # import def post(self): # import
f = request.files.get('file') f = request.files.get('file')

View File

@@ -11,6 +11,8 @@ from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
@@ -18,6 +20,8 @@ from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import role_required from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class GetChildrenView(APIView): class GetChildrenView(APIView):
url_prefix = ("/ci_type_relations/<int:parent_id>/children", url_prefix = ("/ci_type_relations/<int:parent_id>/children",
@@ -41,7 +45,8 @@ class GetParentsView(APIView):
class CITypeRelationView(APIView): class CITypeRelationView(APIView):
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>") url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
res, type2attributes = CITypeRelationManager.get() res, type2attributes = CITypeRelationManager.get()
@@ -52,10 +57,10 @@ class CITypeRelationView(APIView):
def post(self, parent_id, child_id): def post(self, parent_id, child_id):
relation_type_id = request.values.get("relation_type_id") relation_type_id = request.values.get("relation_type_id")
constraint = request.values.get("constraint") constraint = request.values.get("constraint")
parent_attr_id = request.values.get("parent_attr_id") parent_attr_ids = request.values.get("parent_attr_ids")
child_attr_id = request.values.get("child_attr_id") child_attr_ids = request.values.get("child_attr_ids")
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint, ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
parent_attr_id, child_attr_id) parent_attr_ids, child_attr_ids)
return self.jsonify(ctr_id=ctr_id) return self.jsonify(ctr_id=ctr_id)
@@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
class CITypeRelationDelete2View(APIView): class CITypeRelationDelete2View(APIView):
url_prefix = "/ci_type_relations/<int:ctr_id>" url_prefix = "/ci_type_relations/<int:ctr_id>"
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
app_cli.op.read, app_cli.admin_name)
def delete(self, ctr_id): def delete(self, ctr_id):
CITypeRelationManager.delete(ctr_id) CITypeRelationManager.delete(ctr_id)

View File

@@ -3,14 +3,16 @@
from flask import request from flask import request
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.lib.cmdb.custom_dashboard import SystemConfigManager from api.lib.cmdb.custom_dashboard import SystemConfigManager
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CustomDashboardApiView(APIView): class CustomDashboardApiView(APIView):
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch", url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
@@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
def get(self): def get(self):
return self.jsonify(CustomDashboardManager.get()) return self.jsonify(CustomDashboardManager.get())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
@args_validate(CustomDashboardManager.cls) @args_validate(CustomDashboardManager.cls)
def post(self): def post(self):
if request.url.endswith("/preview"): if request.url.endswith("/preview"):
@@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(res) return self.jsonify(res)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
@args_validate(CustomDashboardManager.cls) @args_validate(CustomDashboardManager.cls)
def put(self, _id=None): def put(self, _id=None):
if _id is not None: if _id is not None:
@@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(id2options=request.values.get('id2options')) return self.jsonify(id2options=request.values.get('id2options'))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
def delete(self, _id): def delete(self, _id):
CustomDashboardManager.delete(_id) CustomDashboardManager.delete(_id)
@@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
class SystemConfigApiView(APIView): class SystemConfigApiView(APIView):
url_prefix = ("/system_config",) url_prefix = ("/system_config",)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name", value_required=True) @args_required("name", value_required=True)
def get(self): def get(self):
return self.jsonify(SystemConfigManager.get(request.values['name'])) return self.jsonify(SystemConfigManager.get(request.values['name']))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_validate(SystemConfigManager.cls) @args_validate(SystemConfigManager.cls)
@args_required("name", value_required=True) @args_required("name", value_required=True)
@args_required("option", value_required=True) @args_required("option", value_required=True)
@@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
def put(self, _id=None): def put(self, _id=None):
return self.post() return self.post()
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def delete(self): def delete(self):
CustomDashboardManager.delete(request.values['name']) CustomDashboardManager.delete(request.values['name'])

View File

@@ -5,28 +5,29 @@ import datetime
from flask import abort from flask import abort
from flask import request from flask import request
from flask import session
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum 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 AttributeHistoryManger
from api.lib.cmdb.history import CITriggerHistoryManager from api.lib.cmdb.history import CITriggerHistoryManager
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import role_required
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RecordView(APIView): class RecordView(APIView):
url_prefix = ("/history/records/attribute", "/history/records/relation") url_prefix = ("/history/records/attribute", "/history/records/relation")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@@ -80,18 +81,21 @@ class CIHistoryView(APIView):
class CITriggerHistoryView(APIView): class CITriggerHistoryView(APIView):
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers") url_prefix = ("/history/ci_triggers/<int:ci_id>",)
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name) @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
def get(self, ci_id=None): def get(self, ci_id):
if ci_id is not None: result = CITriggerHistoryManager.get_by_ci_id(ci_id)
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
return self.jsonify(result) return self.jsonify(result)
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
class CIsTriggerHistoryView(APIView):
url_prefix = ("/history/ci_triggers",)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self):
type_id = request.values.get("type_id") type_id = request.values.get("type_id")
trigger_id = request.values.get("trigger_id") trigger_id = request.values.get("trigger_id")
operate_type = request.values.get("operate_type") operate_type = request.values.get("operate_type")
@@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
class CITypeHistoryView(APIView): class CITypeHistoryView(APIView):
url_prefix = "/history/ci_types" url_prefix = "/history/ci_types"
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
type_id = request.values.get("type_id") type_id = request.values.get("type_id")
username = request.values.get("username") username = request.values.get("username")

View File

@@ -8,20 +8,22 @@ from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import role_required
from api.lib.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class PreferenceShowCITypesView(APIView): class PreferenceShowCITypesView(APIView):
url_prefix = ("/preference/ci_types", "/preference/ci_types2") url_prefix = ("/preference/ci_types", "/preference/ci_types2")
@@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_required("cr_ids") @args_required("cr_ids")
@args_validate(PreferenceManager.pref_rel_cls) @args_validate(PreferenceManager.pref_rel_cls)
@@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def put(self, _id): def put(self, _id):
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values) views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def delete(self): def delete(self):
name = request.values.get("name") name = request.values.get("name")

View File

@@ -4,14 +4,16 @@
from flask import abort from flask import abort
from flask import request from flask import request
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.relation_type import RelationTypeManager from api.lib.cmdb.relation_type import RelationTypeManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RelationTypeView(APIView): class RelationTypeView(APIView):
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>") url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
@@ -19,7 +21,8 @@ class RelationTypeView(APIView):
def get(self): def get(self):
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()]) return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def post(self): def post(self):
@@ -28,7 +31,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) return self.jsonify(rel.to_dict())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def put(self, rel_id): def put(self, rel_id):
@@ -37,7 +41,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) return self.jsonify(rel.to_dict())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
def delete(self, rel_id): def delete(self, rel_id):
RelationTypeManager.delete(rel_id) RelationTypeManager.delete(rel_id)

View File

@@ -0,0 +1,178 @@
# -*- coding:utf-8 -*-
from flask import abort
from flask import request
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.topology import TopologyViewManager
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required
from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin
from api.resource import APIView
app_cli = CMDBApp()
class TopologyGroupView(APIView):
url_prefix = ('/topology_views/groups', '/topology_views/groups/<int:group_id>')
@args_required('name')
@args_validate(TopologyViewManager.group_cls)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_group, app_cli.admin_name)
def post(self):
name = request.values.get('name')
order = request.values.get('order')
group = TopologyViewManager.add_group(name, order)
return self.jsonify(group.to_dict())
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.update_topology_group, app_cli.admin_name)
def put(self, group_id):
name = request.values.get('name')
view_ids = request.values.get('view_ids')
group = TopologyViewManager().update_group(group_id, name, view_ids)
return self.jsonify(**group)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.delete_topology_group, app_cli.admin_name)
def delete(self, group_id):
TopologyViewManager.delete_group(group_id)
return self.jsonify(group_id=group_id)
class TopologyGroupOrderView(APIView):
url_prefix = ('/topology_views/groups/order',)
@args_required('group_ids')
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.update_topology_group, app_cli.admin_name)
def post(self):
group_ids = request.values.get('group_ids')
TopologyViewManager.group_order(group_ids)
return self.jsonify(group_ids=group_ids)
def put(self):
return self.post()
class TopologyView(APIView):
url_prefix = ('/topology_views', '/topology_views/relations/ci_types/<int:type_id>', '/topology_views/<int:_id>')
def get(self, type_id=None, _id=None):
if type_id is not None:
return self.jsonify(TopologyViewManager.relation_from_ci_type(type_id))
if _id is not None:
return self.jsonify(TopologyViewManager().get_view_by_id(_id))
return self.jsonify(TopologyViewManager.get_all())
@args_required('name', 'central_node_type', 'central_node_instances', 'path', 'group_id')
@args_validate(TopologyViewManager.cls)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_view, app_cli.admin_name)
def post(self):
name = request.values.pop('name')
group_id = request.values.pop('group_id', None)
option = request.values.pop('option', None)
order = request.values.pop('order', None)
topo_view = TopologyViewManager.add(name, group_id, option, order, **request.values)
return self.jsonify(topo_view)
@args_validate(TopologyViewManager.cls)
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.UPDATE, TopologyViewManager.get_name_by_id)
def put(self, _id):
topo_view = TopologyViewManager.update(_id, **request.values)
return self.jsonify(topo_view)
@has_perm_from_args("_id", ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.DELETE, TopologyViewManager.get_name_by_id)
def delete(self, _id):
TopologyViewManager.delete(_id)
return self.jsonify(code=200)
class TopologyOrderView(APIView):
url_prefix = ('/topology_views/order',)
@args_required('view_ids')
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.TopologyView,
app_cli.op.create_topology_view, app_cli.admin_name)
def post(self):
view_ids = request.values.get('view_ids')
TopologyViewManager.group_inner_order(view_ids)
return self.jsonify(view_ids=view_ids)
def put(self):
return self.post()
class TopologyViewPreview(APIView):
url_prefix = ('/topology_views/preview', '/topology_views/<int:_id>/view')
def get(self, _id=None):
if _id is not None:
acl = ACLManager('cmdb')
resource_name = TopologyViewManager.get_name_by_id(_id)
if (not acl.has_permission(resource_name, ResourceTypeEnum.TOPOLOGY_VIEW, PermEnum.READ) and
not is_app_admin('cmdb')):
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.READ))
return self.jsonify(TopologyViewManager().topology_view(view_id=_id))
else:
return self.jsonify(TopologyViewManager().topology_view(preview=request.values))
def post(self, _id=None):
return self.get(_id)
class TopologyViewGrantView(APIView):
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/grant"
def post(self, view_id, rid):
perms = request.values.pop('perms', None)
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
acl = ACLManager('cmdb')
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
acl.grant_resource_to_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
return self.jsonify(code=200)
class TopologyViewRevokeView(APIView):
url_prefix = "/topology_views/<int:view_id>/roles/<int:rid>/revoke"
@args_required('perms')
def post(self, view_id, rid):
perms = request.values.pop('perms', None)
view_name = TopologyViewManager.get_name_by_id(view_id) or abort(404, ErrFormat.not_found)
acl = ACLManager('cmdb')
if not acl.has_permission(view_name, ResourceTypeEnum.TOPOLOGY_VIEW,
PermEnum.GRANT) and not is_app_admin('cmdb'):
return abort(403, ErrFormat.no_permission.format(view_name, PermEnum.GRANT))
acl.revoke_resource_from_role_by_rid(view_name, rid, ResourceTypeEnum.TOPOLOGY_VIEW, perms, rebuild=True)
return self.jsonify(code=200)

View File

@@ -1,7 +1,7 @@
-i https://mirrors.aliyun.com/pypi/simple -i https://mirrors.aliyun.com/pypi/simple
alembic==1.7.7 alembic==1.7.7
bs4==0.0.1 bs4==0.0.1
celery>=5.3.1 celery==5.3.1
celery-once==3.0.1 celery-once==3.0.1
click==8.1.3 click==8.1.3
elasticsearch==7.17.9 elasticsearch==7.17.9
@@ -31,6 +31,7 @@ marshmallow==2.20.2
more-itertools==5.0.0 more-itertools==5.0.0
msgpack-python==0.5.6 msgpack-python==0.5.6
Pillow>=10.0.1 Pillow>=10.0.1
pycryptodome==3.12.0
cryptography>=41.0.2 cryptography>=41.0.2
PyJWT==2.4.0 PyJWT==2.4.0
PyMySQL==1.1.0 PyMySQL==1.1.0
@@ -53,4 +54,5 @@ shamir~=17.12.0
pycryptodomex>=3.19.0 pycryptodomex>=3.19.0
colorama>=0.4.6 colorama>=0.4.6
lz4>=4.3.2 lz4>=4.3.2
python-magic==0.4.27 python-magic==0.4.27
jsonpath==0.82.2

View File

@@ -20,10 +20,16 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502] ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
MYSQL_USER = env.str('MYSQL_USER', default='cmdb')
MYSQL_PASSWORD = env.str('MYSQL_PASSWORD', default='123456')
MYSQL_HOST = env.str('MYSQL_HOST', default='127.0.0.1')
MYSQL_PORT = env.int('MYSQL_PORT', default=3306)
MYSQL_DATABASE = env.str('MYSQL_DATABASE', default='cmdb')
# # database # # database
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8' SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@' \
f'{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8'
SQLALCHEMY_BINDS = { SQLALCHEMY_BINDS = {
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8' 'user': SQLALCHEMY_DATABASE_URI
} }
SQLALCHEMY_ECHO = False SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False

View File

@@ -39,12 +39,14 @@
"md5": "^2.2.1", "md5": "^2.2.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"relation-graph": "^1.1.0", "relation-graph": "^2.1.42",
"snabbdom": "^3.5.1", "snabbdom": "^3.5.1",
"sortablejs": "1.9.0", "sortablejs": "1.9.0",
"style-resources-loader": "^1.5.0",
"viser-vue": "^2.4.8", "viser-vue": "^2.4.8",
"vue": "2.6.11", "vue": "2.6.11",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-cli-plugin-style-resources-loader": "^0.1.5",
"vue-codemirror": "^4.0.6", "vue-codemirror": "^4.0.6",
"vue-cropper": "^0.6.2", "vue-cropper": "^0.6.2",
"vue-grid-layout": "2.3.12", "vue-grid-layout": "2.3.12",
@@ -57,7 +59,7 @@
"vue-template-compiler": "2.6.11", "vue-template-compiler": "2.6.11",
"vuedraggable": "^2.23.0", "vuedraggable": "^2.23.0",
"vuex": "^3.1.1", "vuex": "^3.1.1",
"vxe-table": "3.6.9", "vxe-table": "3.7.10",
"vxe-table-plugin-export-xlsx": "2.0.0", "vxe-table-plugin-export-xlsx": "2.0.0",
"xe-utils": "3", "xe-utils": "3",
"xlsx": "0.15.0", "xlsx": "0.15.0",

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1711963254221') format('woff2'), src: url('iconfont.woff2?t=1721959219377') format('woff2'),
url('iconfont.woff?t=1711963254221') format('woff'), url('iconfont.woff?t=1721959219377') format('woff'),
url('iconfont.ttf?t=1711963254221') format('truetype'); url('iconfont.ttf?t=1721959219377') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,6 +13,510 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.caise-data_center:before {
content: "\e96f";
}
.caise-folder:before {
content: "\e970";
}
.caise-resource_pool:before {
content: "\e971";
}
.caise-network:before {
content: "\e972";
}
.caise-distributed_switch:before {
content: "\e973";
}
.caise-standard_switch:before {
content: "\e974";
}
.caise-host_cluster:before {
content: "\e975";
}
.caise-storage_cluster:before {
content: "\e976";
}
.caise-data_storage:before {
content: "\e977";
}
.veops-account:before {
content: "\e96e";
}
.veops-collect:before {
content: "\e96d";
}
.veops-collected:before {
content: "\e96c";
}
.veops-text:before {
content: "\e96b";
}
.veops-markdown:before {
content: "\e96a";
}
.veops-bar_horizontal:before {
content: "\e860";
}
.veops-gauge:before {
content: "\e965";
}
.veops-heatmap:before {
content: "\e966";
}
.veops-treemap:before {
content: "\e967";
}
.veops-radar:before {
content: "\e968";
}
.veops-data:before {
content: "\e969";
}
.veops-import:before {
content: "\e963";
}
.veops-batch_operation:before {
content: "\e964";
}
.cmdb-enterprise_edition:before {
content: "\e962";
}
.ops-KVM:before {
content: "\e961";
}
.cmdb-vcenter:before {
content: "\e960";
}
.cmdb-manual_warehousing:before {
content: "\e95f";
}
.cmdb-not_warehousing:before {
content: "\e95d";
}
.cmdb-warehousing:before {
content: "\e95e";
}
.cmdb-prompt:before {
content: "\e95c";
}
.cmdb-arrow:before {
content: "\e95b";
}
.cmdb-automatic_inventory:before {
content: "\e95a";
}
.cmdb-week_additions:before {
content: "\e959";
}
.cmdb-month_additions:before {
content: "\e958";
}
.cmdb-rule:before {
content: "\e955";
}
.cmdb-executing_machine:before {
content: "\e956";
}
.cmdb-resource:before {
content: "\e957";
}
.cmdb-discovery_resources:before {
content: "\e954";
}
.cmdb-association:before {
content: "\e953";
}
.ops-is_dynamic-disabled:before {
content: "\e952";
}
.itsm-pdf:before {
content: "\e951";
}
.monitor-sqlserver:before {
content: "\e950";
}
.monitor-dig2:before {
content: "\e94d";
}
.monitor-base2:before {
content: "\e94e";
}
.monitor-foreground1:before {
content: "\e94f";
}
.monitor-log2:before {
content: "\e945";
}
.monitor-backgroud1:before {
content: "\e946";
}
.monitor-port1:before {
content: "\e947";
}
.monitor-ipmi2:before {
content: "\e948";
}
.monitor-process2:before {
content: "\e949";
}
.monitor-snmp2:before {
content: "\e94a";
}
.monitor-performance1:before {
content: "\e94b";
}
.monitor-testing1:before {
content: "\e94c";
}
.monitor-ping2:before {
content: "\e941";
}
.monitor-prometheus:before {
content: "\e942";
}
.monitor-websocket2:before {
content: "\e943";
}
.monitor-traceroute2:before {
content: "\e944";
}
.monitor-port:before {
content: "\e93c";
}
.monitor-base1:before {
content: "\e93d";
}
.monitor-backgroud:before {
content: "\e93e";
}
.monitor-dig1:before {
content: "\e93f";
}
.monitor-foreground:before {
content: "\e940";
}
.monitor-log1:before {
content: "\e934";
}
.monitor-process1:before {
content: "\e935";
}
.monitor-testing:before {
content: "\e936";
}
.monitor-snmp1:before {
content: "\e937";
}
.monitor-performance:before {
content: "\e938";
}
.monitor-traceroute1:before {
content: "\e939";
}
.monitor-ping1:before {
content: "\e93a";
}
.monitor-ipmi1:before {
content: "\e93b";
}
.a-monitor-prometheus1:before {
content: "\e932";
}
.monitor-websocket1:before {
content: "\e933";
}
.monitor-group_expansion1:before {
content: "\e930";
}
.monitor-group_collapse1:before {
content: "\e931";
}
.monitor-group_expansion:before {
content: "\e92e";
}
.monitor-group_collapse:before {
content: "\e92f";
}
.monitor-list_view:before {
content: "\e92d";
}
.monitor-group_view:before {
content: "\e92c";
}
.ops-topology_view:before {
content: "\e92b";
}
.monitor-host_analysis:before {
content: "\e92a";
}
.monitor-add2:before {
content: "\e929";
}
.monitor-native:before {
content: "\e928";
}
.veops-filter2:before {
content: "\e927";
}
.ops-cmdb-data_companies-selected:before {
content: "\e601";
}
.ops-cmdb-data_companies:before {
content: "\e926";
}
.monitor-threshold_value:before {
content: "\e921";
}
.monitor-disposition:before {
content: "\e922";
}
.monitor-automatic_discovery:before {
content: "\e923";
}
.monitor-grouping_list:before {
content: "\e924";
}
.monitor-node_list:before {
content: "\e925";
}
.monitor-general_view:before {
content: "\e920";
}
.monitor-network_topology:before {
content: "\e91b";
}
.monitor-node_management:before {
content: "\e91c";
}
.monitor-alarm_policy:before {
content: "\e91d";
}
.monitor-alarm:before {
content: "\e91e";
}
.monitor-healing:before {
content: "\e91f";
}
.monitor-data_acquisition:before {
content: "\e8d4";
}
.monitor-analysis:before {
content: "\e91a";
}
.monitor-index:before {
content: "\e89b";
}
.monitor-user_defined:before {
content: "\e867";
}
.monitor-database:before {
content: "\e861";
}
.monitor-common:before {
content: "\e865";
}
.veops-edit:before {
content: "\e866";
}
.veops-empower:before {
content: "\e863";
}
.veops-share:before {
content: "\e864";
}
.veops-export:before {
content: "\e862";
}
.monitor-ip:before {
content: "\e807";
}
.monitor-director:before {
content: "\e803";
}
.monitor-host:before {
content: "\e804";
}
.a-cmdb-log1:before {
content: "\e802";
}
.monitor-add:before {
content: "\e7ff";
}
.monitor-down:before {
content: "\e7fc";
}
.monitor-up:before {
content: "\e7fd";
}
.itsm-unfold:before {
content: "\e7f9";
}
.itsm-stretch:before {
content: "\e7f8";
}
.monitor-data_comaparison2:before {
content: "\e7a1";
}
.monitor-data_comaparison1:before {
content: "\e7f7";
}
.a-monitor-online1:before {
content: "\e7a0";
}
.ops-setting-application-selected:before {
content: "\e919";
}
.ops-setting-application:before {
content: "\e918";
}
.ops-setting-basic:before {
content: "\e889";
}
.ops-setting-basic-selected:before {
content: "\e917";
}
.ops-setting-security:before {
content: "\e915";
}
.ops-setting-theme:before {
content: "\e916";
}
.veops-show:before {
content: "\e914";
}
.itsm-duration:before {
content: "\e913";
}
.itsm-workload:before {
content: "\e912";
}
.caise-VPC:before { .caise-VPC:before {
content: "\e910"; content: "\e910";
} }
@@ -257,10 +761,6 @@
content: "\e8d5"; content: "\e8d5";
} }
.ops-setting-auth-selected:before {
content: "\e8d4";
}
.itsm-knowledge2:before { .itsm-knowledge2:before {
content: "\e8d2"; content: "\e8d2";
} }
@@ -489,10 +989,6 @@
content: "\e89c"; content: "\e89c";
} }
.ops-setting-duty-selected:before {
content: "\e89b";
}
.datainsight-sequential:before { .datainsight-sequential:before {
content: "\e899"; content: "\e899";
} }
@@ -657,38 +1153,6 @@
content: "\e870"; content: "\e870";
} }
.ops-itsm-ticketsetting-selected:before {
content: "\e860";
}
.ops-itsm-reports-selected:before {
content: "\e861";
}
.ops-itsm-servicecatalog-selected:before {
content: "\e862";
}
.ops-itsm-ticketmanage-selected:before {
content: "\e863";
}
.ops-itsm-knowledge-selected:before {
content: "\e864";
}
.ops-itsm-workstation-selected:before {
content: "\e865";
}
.ops-itsm-servicedesk-selected:before {
content: "\e866";
}
.ops-itsm-planticket-selected:before {
content: "\e867";
}
.ops-itsm-servicecatalog:before { .ops-itsm-servicecatalog:before {
content: "\e868"; content: "\e868";
} }
@@ -1061,26 +1525,10 @@
content: "\e816"; content: "\e816";
} }
.ops-cmdb-batch-selected:before {
content: "\e803";
}
.ops-cmdb-batch:before { .ops-cmdb-batch:before {
content: "\e80a"; content: "\e80a";
} }
.ops-cmdb-adc-selected:before {
content: "\e7f7";
}
.ops-cmdb-resource-selected:before {
content: "\e7f8";
}
.ops-cmdb-preference-selected:before {
content: "\e7f9";
}
.ops-cmdb-preference:before { .ops-cmdb-preference:before {
content: "\e7fa"; content: "\e7fa";
} }
@@ -1089,22 +1537,10 @@
content: "\e7fb"; content: "\e7fb";
} }
.ops-cmdb-tree-selected:before {
content: "\e7fc";
}
.ops-cmdb-relation-selected:before {
content: "\e7fd";
}
.ops-cmdb-adc:before { .ops-cmdb-adc:before {
content: "\e7fe"; content: "\e7fe";
} }
.ops-cmdb-search-selected:before {
content: "\e7ff";
}
.ops-cmdb-relation:before { .ops-cmdb-relation:before {
content: "\e800"; content: "\e800";
} }
@@ -1113,14 +1549,6 @@
content: "\e801"; content: "\e801";
} }
.ops-cmdb-citype-selected:before {
content: "\e802";
}
.ops-cmdb-dashboard-selected:before {
content: "\e804";
}
.ops-cmdb-citype:before { .ops-cmdb-citype:before {
content: "\e805"; content: "\e805";
} }
@@ -1129,10 +1557,6 @@
content: "\e806"; content: "\e806";
} }
.ops-cmdb-screen-selected:before {
content: "\e807";
}
.ops-cmdb-resource:before { .ops-cmdb-resource:before {
content: "\e808"; content: "\e808";
} }
@@ -1481,14 +1905,6 @@
content: "\e7a6"; content: "\e7a6";
} }
.ops-setting-role-selected:before {
content: "\e7a0";
}
.ops-setting-group-selected:before {
content: "\e7a1";
}
.ops-setting-role:before { .ops-setting-role:before {
content: "\e7a2"; content: "\e7a2";
} }
@@ -1929,18 +2345,10 @@
content: "\e738"; content: "\e738";
} }
.ops-setting-notice-email-selected-copy:before {
content: "\e889";
}
.ops-setting-notice:before { .ops-setting-notice:before {
content: "\e72f"; content: "\e72f";
} }
.ops-setting-notice-selected:before {
content: "\e730";
}
.ops-setting-notice-email-selected:before { .ops-setting-notice-email-selected:before {
content: "\e731"; content: "\e731";
} }
@@ -1965,10 +2373,6 @@
content: "\e736"; content: "\e736";
} }
.ops-setting-companyStructure-selected:before {
content: "\e72b";
}
.ops-setting-companyStructure:before { .ops-setting-companyStructure:before {
content: "\e72c"; content: "\e72c";
} }
@@ -1977,10 +2381,6 @@
content: "\e72d"; content: "\e72d";
} }
.ops-setting-companyInfo-selected:before {
content: "\e72e";
}
.ops-email:before { .ops-email:before {
content: "\e61a"; content: "\e61a";
} }
@@ -3021,14 +3421,6 @@
content: "\e600"; content: "\e600";
} }
.ops-dag-dashboard-selected:before {
content: "\e601";
}
.ops-dag-applet-selected:before {
content: "\e602";
}
.ops-dag-applet:before { .ops-dag-applet:before {
content: "\e603"; content: "\e603";
} }
@@ -3037,62 +3429,26 @@
content: "\e604"; content: "\e604";
} }
.ops-dag-terminal-selected:before {
content: "\e605";
}
.ops-dag-cron:before { .ops-dag-cron:before {
content: "\e606"; content: "\e606";
} }
.ops-dag-cron-selected:before {
content: "\e608";
}
.ops-dag-history:before { .ops-dag-history:before {
content: "\e609"; content: "\e609";
} }
.ops-dag-history-selected:before {
content: "\e60a";
}
.ops-dag-dags-selected:before {
content: "\e60c";
}
.ops-dag-dagreview:before { .ops-dag-dagreview:before {
content: "\e60d"; content: "\e60d";
} }
.ops-dag-dagreview-selected:before {
content: "\e60e";
}
.ops-dag-panel:before { .ops-dag-panel:before {
content: "\e60f"; content: "\e60f";
} }
.ops-dag-panel-selected:before {
content: "\e615";
}
.ops-dag-variables:before { .ops-dag-variables:before {
content: "\e616"; content: "\e616";
} }
.ops-dag-variables-selected:before {
content: "\e618";
}
.ops-dag-appletadmin:before {
content: "\e65c";
}
.ops-dag-appletadmin-selected:before {
content: "\e65d";
}
.ops-dag-dags:before { .ops-dag-dags:before {
content: "\e60b"; content: "\e60b";
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,14 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
<defs>
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 916 B

View File

@@ -1,14 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
<defs>
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

View File

@@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
<defs>
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
<stop stop-color="#497DFF"/>
<stop offset="1" stop-color="#8CD5FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
<defs>
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
<defs>
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4D82FF"/>
<stop offset="1" stop-color="#88CFFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1022 B

View File

@@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
<defs>
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View File

@@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
<defs>
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -9,7 +9,7 @@
:bodyStyle="{ padding: '24px 12px' }" :bodyStyle="{ padding: '24px 12px' }"
:placement="placement" :placement="placement"
> >
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" /> <ResourceSearch ref="resourceSearch" :fromCronJob="true" :type="type" :typeId="typeId" @copySuccess="copySuccess" />
</CustomDrawer> </CustomDrawer>
</template> </template>
@@ -23,6 +23,14 @@ export default {
type: String, type: String,
default: 'right', default: 'right',
}, },
type: {
type: String,
default: 'resourceSearch'
},
typeId: {
type: Number,
default: null
}
}, },
data() { data() {
return { return {

View File

@@ -69,12 +69,11 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.custom-drawer-close { .custom-drawer-close {
position: absolute; position: absolute;
cursor: pointer; cursor: pointer;
background: #custom_colors[color_1]; background: @primary-color;
color: white; color: white;
text-align: center; text-align: center;
transition: all 0.3s; transition: all 0.3s;

View File

@@ -905,6 +905,33 @@ export const multicolorIconList = [
value: 'caise-application', value: 'caise-application',
label: '应用', label: '应用',
list: [{ list: [{
value: 'caise-data_center',
label: '数据中心'
}, {
value: 'caise-folder',
label: '文件夹'
}, {
value: 'caise-resource_pool',
label: '资源池'
}, {
value: 'caise-network',
label: '网络'
}, {
value: 'caise-distributed_switch',
label: '分布式交换机'
}, {
value: 'caise-standard_switch',
label: '标准式交换机'
}, {
value: 'caise-host_cluster',
label: '主机集群'
}, {
value: 'caise-storage_cluster',
label: '数据存储集群'
}, {
value: 'caise-data_storage',
label: '数据存储'
}, {
value: 'caise-yilianjie', value: 'caise-yilianjie',
label: '已连接' label: '已连接'
}, { }, {

View File

@@ -230,7 +230,6 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.employee-transfer { .employee-transfer {
width: 100%; width: 100%;
.vue-treeselect__multi-value-item-container { .vue-treeselect__multi-value-item-container {
@@ -263,7 +262,6 @@ export default {
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.employee-transfer { .employee-transfer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -300,14 +298,14 @@ export default {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: #custom_colors[color_1]; color: @primary-color;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
} }
} }

View File

@@ -182,8 +182,8 @@ export default {
<tag {...{ props, attrs }}> <tag {...{ props, attrs }}>
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })} {this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
<span> <span>
<span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span> <span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
{isShowDot && {isShowDot && !menu.meta.disabled &&
<a-popover <a-popover
overlayClassName="custom-menu-extra-submenu" overlayClassName="custom-menu-extra-submenu"
placement="rightTop" placement="rightTop"

View File

@@ -10,10 +10,10 @@ export const regList = () => {
{ id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' }, { id: 'landline', label: i18n.t('regexSelect.landline'), value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$', message: '请输入正确座机' },
{ id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' }, { id: 'zipCode', label: i18n.t('regexSelect.zipCode'), value: '^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$', message: '请输入正确邮政编码' },
{ id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' }, { id: 'IDCard', label: i18n.t('regexSelect.IDCard'), value: '(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)', message: '请输入正确身份证号' },
{ id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' }, { id: 'ip', label: i18n.t('regexSelect.ip'), value: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', message: '请输入正确IP地址' },
{ id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' }, { id: 'email', label: i18n.t('regexSelect.email'), value: '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$', message: '请输入正确邮箱' },
{ id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' }, { id: 'link', label: i18n.t('regexSelect.link'), value: '^(https:\/\/www\\.|http:\/\/www\\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\\.[a-zA-Z0-9]{2,})(\\.[a-zA-Z0-9]{2,})?$', message: '请输入链接' },
{ id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\.\\d{1,2})?$', message: '请输入货币金额' }, { id: 'monetaryAmount', label: i18n.t('regexSelect.monetaryAmount'), value: '^-?\\d+(,\\d{3})*(\\.\\d{1,2})?$', message: '请输入货币金额' },
{ id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' } { id: 'custom', label: i18n.t('regexSelect.custom'), value: '', message: '' }
] ]
} }

View File

@@ -146,7 +146,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.regex-select { .regex-select {
width: 100%; width: 100%;
height: 300px; height: 300px;
@@ -162,7 +161,7 @@ export default {
cursor: pointer; cursor: pointer;
&-selected, &-selected,
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }
@@ -180,7 +179,7 @@ export default {
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
color: #000000; color: #000000;
border-left: 2px solid #custom_colors[color_1]; border-left: 2px solid @primary-color;
padding-left: 6px; padding-left: 6px;
margin-left: -6px; margin-left: -6px;
} }

View File

@@ -126,7 +126,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.role-transfer { .role-transfer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -186,14 +185,14 @@ export default {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
color: #custom_colors[color_1]; color: @primary-color;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: #custom_colors[color_1]; background-color: @primary-color;
color: #fff; color: #fff;
} }
} }

View File

@@ -60,7 +60,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.sidebar-list-item { .sidebar-list-item {
.ops_popover_item(); .ops_popover_item();
margin: 2px 0; margin: 2px 0;
@@ -94,7 +93,7 @@ export default {
background-color: transparent; background-color: transparent;
} }
.sidebar-list-item.sidebar-list-item-selected::before { .sidebar-list-item.sidebar-list-item-selected::before {
background-color: #custom_colors[color_1]; background-color: @primary-color;
} }
.sidebar-list-item-dotline { .sidebar-list-item-dotline {
padding-left: 20px; padding-left: 20px;

View File

@@ -52,7 +52,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.two-column-layout { .two-column-layout {
margin-bottom: -24px; margin-bottom: -24px;

View File

@@ -4,34 +4,22 @@
@click="jumpTo" @click="jumpTo"
v-if="showTitle && !collapsed" v-if="showTitle && !collapsed"
style="width: 100%; height: 100%; cursor: pointer" style="width: 100%; height: 100%; cursor: pointer"
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')" :src="require('@/assets/logo_VECMDB.png')"
/> />
<img <img
@click="jumpTo" @click="jumpTo"
v-else v-else
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer" style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')" :src="require('@/assets/logo.png')"
/> />
<!-- <logo-svg/> -->
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
</div> </div>
</template> </template>
<script> <script>
// import LogoSvg from '@/assets/logo.svg?inline'
import { mapState } from 'vuex'
export default { export default {
name: 'Logo', name: 'Logo',
components: { components: {},
// LogoSvg, computed: {},
},
computed: {
...mapState({
file_name: (state) => state.logo.file_name,
small_file_name: (state) => state.logo.small_file_name,
}),
},
props: { props: {
title: { title: {
type: String, type: String,

View File

@@ -81,7 +81,7 @@ export default {
if (route.name === 'cmdb') { if (route.name === 'cmdb') {
const preference = await getPreference() const preference = await getPreference()
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) { if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
this.$router.push(`/cmdb/instances/types/${lastTypeId}`) this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
} else { } else {
this.$router.push('/cmdb/dashboard') this.$router.push('/cmdb/dashboard')

View File

@@ -57,7 +57,9 @@ export default {
computed: { computed: {
...mapState(['user', 'locale']), ...mapState(['user', 'locale']),
hasBackendPermission() { hasBackendPermission() {
return this.user?.detailPermissions?.backend?.length const isAdmin = this?.user?.roles?.permissions?.includes('acl_admin')
return isAdmin || this.user?.detailPermissions?.backend?.length
}, },
}, },
methods: { methods: {
@@ -96,10 +98,9 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.color { .color {
color: #custom_colors[color_1]; color: @primary-color;
background-color: #custom_colors[color_2]; background-color: @primary-color_5;
} }
.custom-user { .custom-user {
.custom-user-item { .custom-user-item {
@@ -118,7 +119,7 @@ export default {
.locale { .locale {
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
</style> </style>

View File

@@ -9,25 +9,11 @@
import gridSvg from '@/assets/icons/grid.svg?inline' import gridSvg from '@/assets/icons/grid.svg?inline'
import top_agent from '@/assets/icons/top_agent.svg?inline' import top_agent from '@/assets/icons/top_agent.svg?inline'
import top_acl from '@/assets/icons/top_acl.svg?inline' import top_acl from '@/assets/icons/top_acl.svg?inline'
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline' import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
export { export {
gridSvg, gridSvg,
top_agent, top_agent,
top_acl, top_acl,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
ops_move_icon ops_move_icon
} }

View File

@@ -0,0 +1,14 @@
import './highlight.less'
const highlight = (el, binding) => {
if (binding.value.value) {
let testValue = `${binding.value.value}`
if (['(', ')', '$'].includes(testValue)) {
testValue = `\\${testValue}`
}
const regex = new RegExp(`(${testValue})`, 'gi')
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
}
}
export default highlight

View File

@@ -0,0 +1,5 @@
@import '~@/style/static.less';
.ops-text-highlight {
background-color: @primary-color_3;
}

View File

@@ -0,0 +1,12 @@
import hightlight from './highlight'
const install = function (Vue) {
Vue.directive('hightlight', hightlight)
}
if (window.Vue) {
window.hightlight = hightlight
Vue.use(install); // eslint-disable-line
}
hightlight.install = install
export default hightlight

View File

@@ -0,0 +1,13 @@
import waves from './waves'
const install = function (Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

@@ -0,0 +1,26 @@
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}

View File

@@ -0,0 +1,72 @@
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}

View File

@@ -31,7 +31,6 @@ router.beforeEach(async (to, from, next) => {
store.dispatch("loadAllUsers") store.dispatch("loadAllUsers")
store.dispatch("loadAllEmployees") store.dispatch("loadAllEmployees")
store.dispatch("loadAllDepartments") store.dispatch("loadAllDepartments")
store.dispatch("getCompanyInfo")
store.dispatch('GenerateRoutes', { roles }).then(() => { store.dispatch('GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.appRoutes) router.addRoutes(store.getters.appRoutes)
const redirect = decodeURIComponent(from.query.redirect || to.path) const redirect = decodeURIComponent(from.query.redirect || to.path)

View File

@@ -160,7 +160,7 @@ export default {
landline: 'landline', landline: 'landline',
zipCode: 'zip code', zipCode: 'zip code',
IDCard: 'ID card', IDCard: 'ID card',
ip: 'IP', ip: 'IPv4',
email: 'email', email: 'email',
link: 'link', link: 'link',
monetaryAmount: 'monetary amount', monetaryAmount: 'monetary amount',

View File

@@ -160,7 +160,7 @@ export default {
landline: '座机', landline: '座机',
zipCode: '邮政编码', zipCode: '邮政编码',
IDCard: '身份证号', IDCard: '身份证号',
ip: 'IP地址', ip: 'IPv4地址',
email: '邮箱', email: '邮箱',
link: '链接', link: '链接',
monetaryAmount: '货币金额', monetaryAmount: '货币金额',

View File

@@ -233,7 +233,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-history { .acl-history {
border-radius: @border-radius-box; border-radius: @border-radius-box;

View File

@@ -32,7 +32,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-operation-history { .acl-operation-history {
border-radius: @border-radius-box; border-radius: @border-radius-box;

View File

@@ -189,7 +189,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-resource-types { .acl-resource-types {
border-radius: @border-radius-box; border-radius: @border-radius-box;

Some files were not shown because too many files have changed in this diff Show More