Compare commits

..

529 Commits
2.3.8 ... 2.3.6

Author SHA1 Message Date
pycook
dc1a2a7632 fix(api): secrets 2023-10-30 17:23:42 +08:00
pycook
153fef4918 feat: add inner password storage and optimize flask command about inner cmdb (#248)
Co-authored-by: fxiang21 <fxiang21@126.com>
2023-10-30 16:48:53 +08:00
wang-liang0615
e218f8e065 Dev UI 231030 (#247)
* config(ui):useEncryption default false

* fix(cmdb-ui):ident 4

* fix(cmdb-ui):relation views
2023-10-30 12:38:05 +08:00
pycook
b7137b3975 Dev api password (#244)
* fix: delete CI password data

* fix(api): update CI password to flush cache
2023-10-29 11:42:07 +08:00
pycook
c6a9478dbb fix: delete CI password data (#243) 2023-10-29 10:53:29 +08:00
pycook
af40004fe9 fix(cmdb-ui): CI update password 2023-10-28 23:07:01 +08:00
pycook
d86eb1c5eb release: v2.3.6 2023-10-28 17:51:13 +08:00
pycook
fef3bfceaf feat(cmdb-api): ci password 2023-10-28 16:55:02 +08:00
pycook
3a4f0b248f feat(cmdb-api): CI password data store (#242)
* add secrets,for test

* feat: vault SDK (#238)

* feat: vault SDK

* docs: i18n

* perf(vault): format code

* feat(secrets): support vault

* feat: add inner password storage

* feat: secrets

* feat: add inner password storage

* feat: add secrets feature

* perf(secrets): review

---------

Co-authored-by: fxiang21 <fxiang21@126.com>
Co-authored-by: Mimo <osatmnzn@gmail.com>
2023-10-28 16:19:00 +08:00
wang-liang0615
ffa3d7cd43 feat:预定义值支持脚本&&密码存储&&一些bugfix (#239) 2023-10-27 11:10:43 +08:00
ivonGwy
6aefac98cd Doc (#235)
* change assignees
2023-10-25 16:00:04 +08:00
ivonGwy
22989f8d5a Doc (#234)
* final template
2023-10-25 15:52:12 +08:00
ivonGwy
ebe9d1e29f Doc (#232)
* fix bugs
2023-10-25 14:54:23 +08:00
ivonGwy
f1dd5ca074 Update issue templates 2023-10-25 14:46:44 +08:00
ivonGwy
4dd95f0d7e Doc (#231)
* add issue template
2023-10-25 14:36:36 +08:00
ivonGwy
7e3e248c2b Update issue templates 2023-10-25 14:06:24 +08:00
ivonGwy
a5ff1139f7 Update issue templates 2023-10-25 14:04:53 +08:00
pycook
00135f4644 fix(api): add ci (#230) 2023-10-25 13:51:29 +08:00
ivonGwy
8297d4c9b4 Doc (#229)
* change reandme
2023-10-25 13:19:30 +08:00
kdyq007
5143539593 关闭前端密码加密;加强 ldap 用户验证 (#216)
* [更新] python-ldap 更新到 ldap3

* [更新] 关闭前端密码加密;加强 ldap 用户验证

* Update app.js

---------

Co-authored-by: sherlock <sherlock@gmail.com>
Co-authored-by: pycook <pycook@126.com>
2023-10-24 19:47:46 +08:00
pycook
a6eb2f0d21 feat: Predefined values support executing scripts (#227) 2023-10-24 19:32:43 +08:00
simontigers
07a63bef6e fix: add_employee_from_acl (#225) 2023-10-24 14:20:40 +08:00
wang-liang0615
d69efeea25 fix:关系视图删除关系接口传参修改 (#224)
* fix:acl新增用户展示异常问题

* fix:关系视图删除关系接口传参修改
2023-10-24 06:04:21 +08:00
wang-liang0615
0ef67360ad fix:acl新增用户展示异常问题 (#223) 2023-10-24 05:59:08 +08:00
pycook
e2f993bc11 feat: add cryptography to requirements 2023-10-23 14:37:01 +08:00
pycook
05d2795e79 fix: acl cache 2023-10-23 13:57:06 +08:00
Evan Sung
6ff77a140c fix(common): fix 'ACLManager' object has no attribute 'create_app' (#217) 2023-10-21 11:38:19 +08:00
Evan Sung
6503d32e6e fix(ci_cache): ci cache async args (#215) 2023-10-20 12:05:19 +08:00
kdyq007
887a69c2bd feat: python-ldap 更新到 ldap3 (#214)
Co-authored-by: sherlock <sherlock@gmail.com>
2023-10-20 09:36:38 +08:00
pycook
6d052eaffc Dev api 20231019 (#210)
* fix(acl): get resources

* fix(celery worker): db server has gone away
2023-10-19 11:51:34 +08:00
wang-liang0615
d0f0bf84dd fix:ci relation add type filter (#208) 2023-10-18 14:06:28 +08:00
pycook
802fda66e7 fix: ci relation statistics 2023-10-18 13:35:01 +08:00
pycook
c95747c88a docs: api doc 2023-10-17 12:06:37 +08:00
wang-liang0615
ed49b238d8 feat:webhook body 支持非json (#203) 2023-10-17 10:44:38 +08:00
Evan Sung
375f0879fb Feature db migrate 20231013 (#202)
* feat(db): support flask migrate

* minor

---------

Co-authored-by: s01249 <songbing@smyfinancial.com>
2023-10-13 16:24:49 +08:00
Evan Sung
8bc1893ca9 feat(db): support flask migrate (#201)
Co-authored-by: s01249 <songbing@smyfinancial.com>
2023-10-13 15:55:26 +08:00
simontigers
53cd2342bf fix: common perms (#200) 2023-10-12 16:02:35 +08:00
wang-liang0615
eff6d974d4 pref:批量上传&资源管理小优化 (#199) 2023-10-12 15:06:39 +08:00
ivonGwy
8478d2f858 Merge pull request #197 from veops/doc
Doc
2023-10-11 14:34:05 +08:00
ivonGwy
80e99cc335 change wechat pic size 2023-10-11 14:33:19 +08:00
ivonGwy
8f64fc4aa0 change wechat pic 2023-10-11 14:28:49 +08:00
pycook
cfc345c993 Dev api (#196)
* docs: update

* docs: README & Makefile
2023-10-11 13:40:15 +08:00
wang-liang0615
928116d0b5 Dev UI 231009 (#195)
* pref:用户密钥非必填

* fix:chartColor undefined
2023-10-11 09:12:04 +08:00
pycook
4c2e6ae69f docs: update (#194) 2023-10-10 16:53:30 +08:00
wang-liang0615
a2c75fd34e pref:用户密钥非必填 (#193) 2023-10-10 09:25:24 +08:00
pycook
8217053abf release: v2.3.5 2023-10-09 20:55:30 +08:00
pycook
8c17373e45 feat: get messenger url from common setting 2023-10-09 20:25:27 +08:00
wang-liang0615
a8e2595327 前端更新 (#192)
* fix:add package

* fix:notice_info为null的情况

* fix:2 bugs

* feat:1.common增加通知配置 2.cmdb预定义值webhook&其他模型

* fix:json 不支持预定义值

* fix:json 不支持预定义值

* fix:删除代码
2023-10-09 19:52:19 +08:00
simontigers
dfbba103cd fix: init company structure resource (#191)
* fix: init company structure resource

* fix: notice_info null
2023-10-09 19:25:49 +08:00
wang-liang0615
86b9d5a7f4 前端更新 (#189)
* fix:add package

* fix:notice_info为null的情况

* fix:2 bugs

* feat:1.common增加通知配置 2.cmdb预定义值webhook&其他模型

* fix:json 不支持预定义值

* fix:json 不支持预定义值
2023-10-09 17:43:34 +08:00
simontigers
612922a1b7 feat: notice_config access messenger (#190) 2023-10-09 17:32:20 +08:00
pycook
2758c5e468 fix: delete user role 2023-10-09 15:40:18 +08:00
pycook
d85c86a839 feat: The definition of attribute choice values supports webhook and other model attribute values. 2023-10-09 15:33:18 +08:00
wang-liang0615
8355137e43 Dev UI (#186)
* fix:add package

* fix:notice_info为null的情况

* fix:2 bugs
2023-09-28 09:45:11 +08:00
pycook
2e644233bc release 2.3.4 2023-09-27 11:37:18 +08:00
simontigers
d9b4082b46 feat: add api get_notice_by_ids (#184) 2023-09-27 09:54:30 +08:00
wang-liang0615
a07f984152 前端更新 (#183)
* fix:add package

* fix:notice_info为null的情况
2023-09-27 09:18:33 +08:00
pycook
4cab7ef6b0 feat: ci triggers 2023-09-26 21:18:34 +08:00
wang-liang0615
070c163de6 fix:add package (#182) 2023-09-26 21:12:10 +08:00
wang-liang0615
282a779fb1 Merge pull request #181 from veops/dev_ui
前端更新
2023-09-26 20:34:27 +08:00
wang-liang0615
cb6b51a84c Merge branch 'master' into dev_ui 2023-09-26 20:34:14 +08:00
wang-liang0615
34bd320e75 fix:topo图相同节点出现两次的bug 2023-09-26 20:13:12 +08:00
wang-liang0615
1eca5791f6 feat:wangeditor 注册自定义组件 2023-09-26 20:07:00 +08:00
wang-liang0615
13b1c9a30c delete:删除getwx 2023-09-26 20:04:38 +08:00
simontigers
b1a15a85d2 feat: common notice config (#180) 2023-09-26 19:44:20 +08:00
wang-liang0615
08e5a02caf feat: UI更新 触发器 (#179)
* feat:新增api&适配

* feat:触发器

* add packages & 注释代码

* feat: webhook tips
2023-09-26 18:25:04 +08:00
wang-liang0615
308827b8fc feat: webhook tips 2023-09-26 18:17:23 +08:00
wang-liang0615
dc4ccb22b9 add packages & 注释代码 2023-09-26 17:35:41 +08:00
wang-liang0615
c482e7ea43 feat:触发器 2023-09-26 17:01:31 +08:00
wang-liang0615
663c14f763 feat:新增api&适配 2023-09-26 16:26:25 +08:00
pycook
c6ee227bab fix: ci_cache 2023-09-25 15:46:07 +08:00
wang-liang0615
cb62cf2410 Merge pull request #178 from veops/dev_ui
前端更新:仪表盘优化
2023-09-25 14:52:09 +08:00
wang-liang0615
133f32a6b0 pref:仪表盘优化 2023-09-25 14:50:08 +08:00
wang-liang0615
45c48c86fe Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-25 14:43:34 +08:00
pycook
2321f17dae refactor: CI triggers 2023-09-22 17:39:54 +08:00
simontigers
ddb31a07a2 fix: icon svg support (#177) 2023-09-20 15:56:57 +08:00
pycook
b474914fbb fix date search 2023-09-18 18:15:02 +08:00
pycook
26099a3d69 fix dashboard compute 2023-09-18 13:04:50 +08:00
pycook
62829c885b release v2.3.3 2023-09-15 17:57:39 +08:00
pycook
260aed6462 dashboard ui update 2023-09-15 17:36:10 +08:00
simontigers
3841999cca feat: init resource for backend (#176) 2023-09-15 15:30:30 +08:00
pycook
14c03ce5d2 enhance dashboard 2023-09-15 15:26:20 +08:00
pycook
f463ecd6e6 cmdb-api/api/lib/resp_format.py 2023-09-12 20:01:30 +08:00
pycook
adc0cfd5c5 Detect circular dependencies when adding CIType relationships 2023-09-12 20:00:56 +08:00
wang-liang0615
086481657e 计算属性 触发计算 (#174) 2023-09-11 19:16:05 +08:00
pycook
d2f84ae3dc fix upload template and add /api/v0.1/attributes/<int:attr_id>/calc_computed_attribute 2023-09-11 19:15:31 +08:00
wang-liang0615
9f1b510cb3 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-11 17:35:44 +08:00
wang-liang0615
61acb2483d 计算属性 触发计算 2023-09-11 17:34:51 +08:00
pycook
0196c8a82c release 2.3.2 2023-09-07 13:44:51 +08:00
pycook
bed2323fc1 Merge pull request #172 from veops/dev_ui
新建ci及批量导入时,新建关系
2023-09-07 11:04:49 +08:00
wang-liang0615
be9b308f56 新建ci及批量导入时,新建关系 2023-09-07 10:25:18 +08:00
pycook
8ba658ea1b Merge branch 'master' of github.com:veops/cmdb 2023-09-07 10:12:55 +08:00
pycook
0aa668cfa0 Add CI relationship when creating CI, the text value removes the escape 2023-09-07 10:12:42 +08:00
pycook
e20fd33a53 Merge pull request #171 from ronething/fix/makefile
optimize: makefile help
2023-09-05 20:34:16 +08:00
ashing
7462de63de fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:33:07 +08:00
ashing
5f9ba069ad fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:29:29 +08:00
ashing
5dc0d95ff8 fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:21:20 +08:00
ashing
e5536b76e6 optimize: makefile help
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:06:31 +08:00
pycook
8b044efd4e Merge pull request #170 from ronething/feat/xx
feat: support docker deploy mysql and redis
2023-09-05 19:28:47 +08:00
ivonGwy
747b5bf494 Merge pull request #169 from veops/doc
add document link
2023-09-05 15:41:52 +08:00
ivonGwy
21067022f6 add document link 2023-09-05 15:40:31 +08:00
ashing
4102c44fb2 feat: support docker deploy mysql and redis
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 15:26:50 +08:00
wang-liang0615
600f95ce18 Merge pull request #168 from veops/dev_ui
UI更新
2023-09-05 15:23:43 +08:00
wang-liang0615
950fd38044 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-05 15:22:18 +08:00
wang-liang0615
01085615b5 模型关联 展示反向关系 2023-09-05 15:22:08 +08:00
pycook
734f1940f9 Merge branch 'master' of github.com:veops/cmdb 2023-09-05 14:49:53 +08:00
pycook
c25c1e4e4b move Dockerfile to docs 2023-09-05 14:49:34 +08:00
wang-liang0615
826a8306d3 Merge pull request #167 from veops/dev_ui
sub menu color
2023-09-04 16:34:26 +08:00
wang-liang0615
740aae573e sub menu color 2023-09-04 16:33:35 +08:00
wang-liang0615
17828a7631 Merge pull request #166 from veops/dev_ui
ui更新
2023-09-04 13:15:27 +08:00
wang-liang0615
02cb497bdc Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-04 13:14:35 +08:00
wang-liang0615
05a7dc41ee sidebar 2023-09-04 13:14:11 +08:00
pycook
459c70ba2d import format 2023-09-02 12:09:41 +08:00
pycook
774f42ac34 format 2023-09-01 18:07:44 +08:00
pycook
420029a5e2 fix delete choice values 2023-08-31 16:02:24 +08:00
pycook
ab8acbfd20 fix delete choice values 2023-08-31 15:18:15 +08:00
wang-liang0615
4468b6a8de Merge pull request #165 from veops/dev_ui
proxy
2023-08-31 13:31:26 +08:00
wang-liang0615
6bf145d085 proxy 2023-08-31 13:28:15 +08:00
pycook
42b1e47e76 Merge pull request #162 from simontigers/cmdb_icon_manage
feat: add cmdb custom icon manage
2023-08-31 11:15:09 +08:00
pycook
673134003a Merge pull request #163 from veops/dev_ui
支持上传自定义图标
2023-08-31 11:14:42 +08:00
hu.sima
ef67885571 feat: add cmdb custom icon manage 2023-08-31 10:49:56 +08:00
wang-liang0615
075bf7217f 支持上传自定义图标 2023-08-31 10:05:11 +08:00
pycook
3b7b8f435c fix update attribute 2023-08-30 13:34:10 +08:00
pycook
2b7f6aeef3 Merge branch 'master' of github.com:veops/cmdb 2023-08-29 14:49:21 +08:00
pycook
544fac8aca The default value of USE_ACL is set to True 2023-08-29 14:49:09 +08:00
pycook
3d0a56ec8c Merge pull request #161 from simontigers/common_setting_format
fix: company info create
2023-08-29 11:01:25 +08:00
hu.sima
d2d8482052 fix: company info create 2023-08-29 10:56:48 +08:00
pycook
a0afae8d2e Merge branch 'master' of github.com:veops/cmdb 2023-08-25 11:01:24 +08:00
pycook
9f3da68636 update ad_ci when deleting ci 2023-08-25 10:59:38 +08:00
wang-liang0615
24b955c288 Merge pull request #160 from veops/dev_ui
前端更新
2023-08-25 10:12:31 +08:00
wang-liang0615
a07b2d37ec fix 新增类型回车键发送两次请求 2023-08-25 10:11:09 +08:00
wang-liang0615
c86fcb4e7b fix 新增类型回车键发送两次请求 2023-08-25 10:08:04 +08:00
pycook
ca7964f24b Merge pull request #158 from EvanSung/perf_20230824_optimize_ad_ci_relation
perf(ad_ci_relation): optimize ad_ci relation
2023-08-24 16:26:43 +08:00
EvanSung
c42ac634fb perf(ad_ci_relation): optimize ad_ci relation 2023-08-24 14:16:12 +08:00
pycook
a6fc3341ce docker-compose add flask db-setup 2023-08-24 11:32:09 +08:00
pycook
fc3f2e25f3 vxe-table-plugin-export-xlsx==2.0.0 2023-08-24 11:06:28 +08:00
pycook
511a5f70c6 add config CACHE_REDIS_PASSWORD and fix delete ci_type 2023-08-23 18:05:28 +08:00
pycook
f8ff4d5e45 fix update ci 2023-08-22 11:34:40 +08:00
pycook
3ab72cceaf Register api and commands with absolute paths 2023-08-21 20:08:23 +08:00
pycook
4ab7e3c70c fix merge conflict 2023-08-21 11:55:49 +08:00
pycook
a7fe75f7df fix g.user 2023-08-21 11:54:33 +08:00
pycook
3474a71a75 version: 2.3.1 2023-08-20 11:24:53 +08:00
pycook
6531baff64 lint 2023-08-20 11:23:55 +08:00
pycook
ed5936250f Merge pull request #157 from EvanSung/fix_20230817_guser_issue
fix(acl): g user issue
2023-08-17 22:12:45 +08:00
EvanSung
52c32e2ab1 fix(acl): g user issue 2023-08-17 18:40:45 +08:00
pycook
d3224625b6 fix MyJSONEncoder 2023-08-16 21:28:27 +08:00
pycook
f158c7e33a Merge pull request #155 from veops/dev_ui
前端更新
2023-08-16 13:01:13 +08:00
pycook
6dc12bb6ac Merge branch 'master' of github.com:veops/cmdb 2023-08-16 13:00:44 +08:00
pycook
b33ae16c00 Delete user without soft delete 2023-08-16 13:00:30 +08:00
wang-liang0615
2caffc2670 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-16 10:09:47 +08:00
wang-liang0615
f28af51007 delete user 2023-08-16 10:09:25 +08:00
pycook
3a0369559f Merge pull request #154 from simontigers/common_setting_format
fix: init-import-user-from-acl
2023-08-15 20:47:36 +08:00
hu.sima
a74a2c5a94 fix: init-import-user-from-acl 2023-08-15 20:45:28 +08:00
pycook
9fbcb2838e Merge pull request #153 from simontigers/common_setting_format
fix: import_user_from_acl
2023-08-15 20:25:09 +08:00
hu.sima
60a445b972 fix: import_user_from_acl 2023-08-15 20:19:45 +08:00
pycook
bfdd7b6a0e Merge branch 'master' of github.com:veops/cmdb 2023-08-15 19:48:11 +08:00
pycook
ab093d2493 [update] delete roles, users, attributes 2023-08-15 19:47:59 +08:00
wang-liang0615
315a578a31 Merge pull request #152 from veops/dev_ui
前端更新
2023-08-15 19:47:03 +08:00
wang-liang0615
1e16dc5e5b 属性库 2023-08-15 19:34:17 +08:00
wang-liang0615
f67e196acf 属性库 2023-08-15 19:26:49 +08:00
wang-liang0615
439e25d5dd 属性库 2023-08-15 19:21:09 +08:00
wang-liang0615
ea59c0d71f 属性库 2023-08-15 19:10:26 +08:00
wang-liang0615
1137127aab 后台管理-模型关联 关系删除&&筛选 2023-08-15 15:02:46 +08:00
pycook
4ad1b5282e update gitattributes 2023-08-15 13:41:45 +08:00
wang-liang0615
cdd5e4d9aa Merge pull request #150 from EvanSung/optimize_20230810_acl_resource_fe
refactor(fe): reduce the width of resource mgt table
2023-08-10 19:32:49 +08:00
pycook
432de5e847 Merge pull request #148 from simontigers/common_setting_format
fix: default arg value
2023-08-10 19:31:18 +08:00
pycook
3a2339765a Merge pull request #149 from veops/dev_ui
ui更新:password
2023-08-10 19:28:24 +08:00
EvanSung
b5a2af7420 refactor(fe): reduce the width of resource mgt table 2023-08-10 19:23:41 +08:00
wang-liang0615
8b267613d6 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-10 19:21:28 +08:00
wang-liang0615
b365eb27f6 增加密码明文传输 2023-08-10 19:21:10 +08:00
hu.sima
2125f020b5 fix: default arg value 2023-08-10 19:05:56 +08:00
pycook
ea762e35a0 Merge pull request #147 from simontigers/common_setting_format
fix: remove useless
2023-08-10 19:01:25 +08:00
hu.sima
f11aadf6d4 fix: remove useless 2023-08-10 18:55:32 +08:00
pycook
9cbf133b9f Merge pull request #146 from simontigers/common_setting_format
Common setting format
2023-08-10 18:23:24 +08:00
hu.sima
95e8f9de74 fix: remove unused column 2023-08-10 16:29:52 +08:00
hu.sima
26792147ae style: format common setting 2023-08-10 15:30:01 +08:00
pycook
4f9b581c2e Merge pull request #145 from EvanSung/optimize_20230810_auth_require
optimize(auth): auth request json
2023-08-10 11:24:23 +08:00
EvanSung
e2b1cb3003 optimize(auth): auth request json 2023-08-10 10:43:59 +08:00
pycook
f75a85b48a fix celery config 2023-08-08 16:33:24 +08:00
pycook
313fc80e54 Merge branch 'master' of github.com:veops/cmdb 2023-08-08 13:16:14 +08:00
pycook
e0666689e5 upgrade celery 2023-08-08 13:16:07 +08:00
pycook
7a9fd4f9d6 Merge pull request #144 from veops/dev_ui
UI更新:fix preferenceList=>attrList
2023-08-08 09:21:10 +08:00
wang-liang0615
2fd706be85 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-08 09:11:24 +08:00
wang-liang0615
3df51bb670 fix preferenceList=>attrList 2023-08-08 09:11:03 +08:00
pycook
9bbbcbe6dc upgrade flask to 2.3.2 and replace g.user with current_user 2023-08-06 21:54:18 +08:00
pycook
16d6b40e8d Merge pull request #138 from lovvvve/fix_ldap
fix ldap login
2023-08-04 11:31:58 +08:00
pycook
ef2d3812a2 Merge pull request #142 from veops/dev_ui
ci 批量更新和删除的异步处理
2023-08-04 09:27:55 +08:00
wang-liang0615
bc653efd04 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-03 16:54:47 +08:00
wang-liang0615
d891d7365d ci 批量更新和删除的异步处理 2023-08-03 16:54:27 +08:00
pycook
9953b2fc98 Merge pull request #139 from EvanSung/fix-post-acltrigger-session-invalid
fix(trigger): session invalid issue
2023-08-02 19:33:05 +08:00
songbing01249
8de54812dc fix(trigger): session invalid issue 2023-08-02 18:22:42 +08:00
lovvvve
eb7d52cf35 fix ldap login 2023-08-01 11:27:29 +00:00
pycook
6c4a5f2f6b Merge pull request #134 from veops/dependabot/pip/cmdb-api/pillow-9.3.0
Bump pillow from 9.2.0 to 9.3.0 in /cmdb-api
2023-08-01 15:57:02 +08:00
pycook
17c5d4538b Merge pull request #135 from simontigers/remove_pandas
fix: remove pandas
2023-08-01 15:55:15 +08:00
hu.sima
6c3e3f9eed fix: remove pandas 2023-08-01 15:32:44 +08:00
dependabot[bot]
b0494adc17 Bump pillow from 9.2.0 to 9.3.0 in /cmdb-api
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.2.0 to 9.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.2.0...9.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 05:51:16 +00:00
pycook
fc133f2ae9 Merge branch 'master' of github.com:veops/cmdb 2023-08-01 13:47:34 +08:00
pycook
ac6e3a0318 fix dependabot alerts 2023-08-01 13:47:11 +08:00
pycook
404ec976cc fix dependabot alerts 2023-08-01 13:46:47 +08:00
pycook
4211bbcbc9 Merge pull request #130 from veops/dependabot/pip/cmdb-api/certifi-2023.7.22
Bump certifi from 2023.5.7 to 2023.7.22 in /cmdb-api
2023-08-01 13:14:25 +08:00
pycook
0158636671 Merge pull request #132 from veops/dev_ui
删除角色相关
2023-07-31 19:54:19 +08:00
wang-liang0615
d986bc3bbc 删除角色相关 2023-07-31 19:52:06 +08:00
pycook
044b820548 Merge branch 'master' of github.com:veops/cmdb 2023-07-31 18:39:46 +08:00
pycook
536daa6d4f fix delete ci_type 2023-07-31 18:39:33 +08:00
pycook
b0620b043b Merge pull request #131 from veops/dev_ui
前端acl
2023-07-28 18:03:36 +08:00
wang-liang0615
a88c9cf7f7 common-setting 2023-07-27 15:47:13 +08:00
wang-liang0615
be50f505d1 acl 2023-07-27 15:30:27 +08:00
wang-liang0615
0bb4f633d6 fix acl change page size 2023-07-27 15:08:25 +08:00
dependabot[bot]
78b521f3af Bump certifi from 2023.5.7 to 2023.7.22 in /cmdb-api
Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.5.7 to 2023.7.22.
- [Commits](https://github.com/certifi/python-certifi/compare/2023.05.07...2023.07.22)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-25 23:35:30 +00:00
pycook
77bc850d4a Merge pull request #129 from veops/dev_ui
前端更新
2023-07-25 18:19:47 +08:00
wang-liang0615
e52f201ba1 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-07-25 13:11:03 +08:00
wang-liang0615
64aea424dc 授权高亮提示 2023-07-25 13:10:45 +08:00
pycook
0655b0e9eb add command cmdb-index-table-upgrade 2023-07-25 10:31:30 +08:00
wang-liang0615
cce0649299 style 新建属性行错乱 2023-07-25 10:18:22 +08:00
pycook
52574c64cc 废弃3个表: c_value_datetime c_value_floats c_value_integers, time类型属性值增加写入校验 2023-07-24 21:55:00 +08:00
pycook
fb904b01a6 禁止删除唯一标识的属性 2023-07-21 15:58:41 +08:00
pycook
63af79ec45 Merge branch 'master' of github.com:veops/cmdb 2023-07-20 18:37:14 +08:00
pycook
38af86317a fix docker-compose 2023-07-20 18:36:32 +08:00
pycook
03bac86588 Merge pull request #127 from veops/dev_ui
fix currentValueType
2023-07-20 15:39:11 +08:00
wang-liang0615
130b68cadd fix currentValueType 2023-07-20 15:30:12 +08:00
pycook
65000f8141 更新架构图 2023-07-20 11:01:25 +08:00
pycook
23692ad50b update readme 2023-07-20 11:01:25 +08:00
pycook
16cd34e8b8 update readme 2023-07-20 11:01:25 +08:00
pycook
985f67ee47 lint 2023-07-20 11:01:25 +08:00
wang-liang0615
8d95f8d57d 角色授权 2023-07-20 11:01:25 +08:00
pycook
cf6230008d 清理空间 2023-07-20 11:01:25 +08:00
songbing01249
ec97fa84d8 fix(ci_type_group_manager): fix resources issues 2023-07-20 11:01:25 +08:00
pycook
76f074704b update cmdb_api.md 2023-07-20 10:56:58 +08:00
wang-liang0615
e5addab3af 删除fullscreen相关代码 2023-07-19 17:46:27 +08:00
wang-liang0615
1c6be9e281 format 2023-07-19 15:36:46 +08:00
wang-liang0615
9552892c68 ops table getVxetableRef 2023-07-19 14:39:57 +08:00
wang-liang0615
b59e1af318 编译 acl 2023-07-19 13:52:24 +08:00
wang-liang0615
d164d883ab Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-07-18 15:16:50 +08:00
wang-liang0615
1fef160d9e 删除不必要文件 2023-07-18 15:16:32 +08:00
wang-liang0615
2e537d390a acl 样式升级 2023-07-18 15:14:35 +08:00
pycook
5b9fe15afa Merge pull request #119 from veops/dev_ui
模型属性 is_index
2023-07-17 18:15:28 +08:00
wang-liang0615
89fa5f2243 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-07-17 17:21:26 +08:00
wang-liang0615
652a5c7fb8 模型属性 is_index 2023-07-17 17:19:44 +08:00
pycook
afb6adec89 update local.md 2023-07-17 13:32:34 +08:00
pycook
a9db4285ab Merge pull request #118 from veops/dev_ui
Dev UI
2023-07-14 18:06:02 +08:00
wang-liang0615
a04bdc29a5 删除usedfc 2023-07-14 15:20:58 +08:00
wang-liang0615
91e0e076a7 删除usedfc 2023-07-14 15:20:36 +08:00
wang-liang0615
339a7b857e acl 前端 2023-07-14 14:34:35 +08:00
pycook
e86e5ad1fd PyJWT==2.4.0 2023-07-13 17:07:33 +08:00
pycook
c50a69de77 Merge pull request #117 from lovvvve/patch-4
Update click_cmdb.py
2023-07-13 15:51:36 +08:00
lovvvve
4d16e9e6d9 Update click_cmdb.py
add-user remove --is_admin
2023-07-13 15:49:35 +08:00
pycook
fcea4dcb9f Merge pull request #116 from lovvvve/patch-3
Update click_cmdb.py
2023-07-13 15:23:24 +08:00
lovvvve
f98fd24c62 Update click_cmdb.py
add-user  remove --is_admin
2023-07-13 15:18:47 +08:00
pycook
f10eeb8439 update README 2023-07-13 09:34:17 +08:00
pycook
f070948122 Merge pull request #115 from veops/doc
change screenshot image
2023-07-12 17:22:58 +08:00
ivonGwy
4112bcf547 change screenshot image 2023-07-12 17:21:10 +08:00
pycook
2292756bf7 Merge pull request #114 from veops/doc
change image size
2023-07-12 17:11:20 +08:00
ivonGwy
93e2483974 change image size 2023-07-12 17:07:17 +08:00
pycook
fbb4fcc255 Merge pull request #113 from veops/doc
add qrcode for gzh
2023-07-12 16:49:12 +08:00
ivonGwy
fc77241006 add qrcode for gzh 2023-07-12 16:12:17 +08:00
pycook
0d04ad7d90 update requirements 2023-07-12 15:32:46 +08:00
pycook
e6290e49ea update docker-compose 2023-07-12 11:59:51 +08:00
pycook
97aa2e0ebe remove .gitattributes 2023-07-12 11:50:05 +08:00
pycook
939d9dc3cd md format 2023-07-12 10:14:47 +08:00
pycook
576d2e3bc4 Update README.md 2023-07-12 10:09:11 +08:00
pycook
9a40246d29 Update README.md 2023-07-12 10:01:15 +08:00
pycook
044f95c3be docker-compose 构建后的默认账号密码 2023-07-11 19:40:40 +08:00
pycook
a386de355e docker-compose is ok 2023-07-11 18:22:17 +08:00
pycook
b93afc1790 docker-compose is ok 2023-07-11 18:12:22 +08:00
pycook
77d89677ef 前后端全面升级 2023-07-10 20:13:39 +08:00
pycook
7ec6775f03 友链Spug 2023-07-10 20:07:31 +08:00
pycook
98cc853dbc 前后端全面升级 2023-07-10 20:07:20 +08:00
pycook
f57ff80099 Merge pull request #90 from lovvvve/patch-2
fix: 🐛 db search
2021-11-10 20:18:18 +08:00
lovvvve
51e4b5dd8f fix: 🐛 db search
Escape ":" character in SQLAlchemy
2021-11-10 18:56:42 +08:00
pycook
dbf44a020b Merge pull request #77 from x-7/x-7-patch-1
cmdb-api:add attr check in ci_manager update method
2021-04-22 09:14:33 +08:00
x-7
8e578797ef Update ci.py
cmdb-api:add attr check in ci_manager update method
2021-04-21 19:17:05 +08:00
pycook
158de4b946 Merge pull request #69 from lovvvve/patch-1
CiManager.add and AttributeValueManager.create_or_update_attr_value update
2021-01-28 17:09:01 +08:00
lovvvve
3cf234d49e Update value.py
value type 是 int 或 float 时 value 值等于 0 是会删除 的 BUG
2021-01-28 16:57:20 +08:00
lovvvve
a7debc1b3b Update ci.py
兼容 py2
2021-01-28 16:56:04 +08:00
lovvvve
9268da2ffa Update value.py 2021-01-28 16:46:19 +08:00
lovvvve
cfcb092478 Update value.py
feat(AttributeValueManager.create_or_update_attr_value()): AttributeValue update skip The same value
2021-01-28 16:32:53 +08:00
lovvvve
0d8b41b64a Update value.py
feat(AttributeValueManager.create_or_update_attr_value()): AttributeValue update skip The same value
2021-01-28 16:30:17 +08:00
lovvvve
d85715793f Update ci.py
feat(CiManager.add()): Check the attribute is in the ci_type attributes list
2021-01-28 16:27:56 +08:00
pycook
afbdbe4682 Merge pull request #65 from shaohaojiecoder/stable
Stable
2020-12-14 09:20:15 +08:00
shaohaojiecoder
e629abebb7 remove weeds 2020-12-13 16:58:06 +08:00
shaohaojiecoder
029c12365a delay render 2020-12-13 16:42:17 +08:00
pycook
4d000d9805 yarn.lock update 2020-11-22 11:45:33 +08:00
pycook
f1fc66bd2c Fix github security 2020-11-22 11:42:17 +08:00
pycook
d6af4af1d1 upgrade ui packages 2020-11-22 11:13:46 +08:00
pycook
7fe2bdca5f Create codeql-analysis.yml 2020-11-22 10:45:45 +08:00
pycook
1432131d2b Merge pull request #59 from pycook/dependabot/npm_and_yarn/cmdb-ui/dot-prop-4.2.1
Bump dot-prop from 4.2.0 to 4.2.1 in /cmdb-ui
2020-11-22 10:28:34 +08:00
pycook
bc94d039f5 Merge pull request #52 from pycook/dependabot/npm_and_yarn/cmdb-ui/elliptic-6.5.3
Bump elliptic from 6.4.1 to 6.5.3 in /cmdb-ui
2020-11-22 10:27:49 +08:00
pycook
5abafed9c8 Merge pull request #54 from pycook/dependabot/npm_and_yarn/cmdb-ui/quill-1.3.7
Bump quill from 1.3.6 to 1.3.7 in /cmdb-ui
2020-11-22 10:27:15 +08:00
dependabot[bot]
04e249feac Bump dot-prop from 4.2.0 to 4.2.1 in /cmdb-ui
Bumps [dot-prop](https://github.com/sindresorhus/dot-prop) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/sindresorhus/dot-prop/releases)
- [Commits](https://github.com/sindresorhus/dot-prop/compare/v4.2.0...v4.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-22 02:26:41 +00:00
pycook
ef3e6bc6b0 Merge pull request #55 from pycook/dependabot/npm_and_yarn/cmdb-ui/handlebars-4.7.6
Bump handlebars from 4.4.5 to 4.7.6 in /cmdb-ui
2020-11-22 10:26:38 +08:00
pycook
d9d5f8f818 Merge pull request #56 from pycook/dependabot/npm_and_yarn/cmdb-ui/http-proxy-1.18.1
Bump http-proxy from 1.17.0 to 1.18.1 in /cmdb-ui
2020-11-22 10:25:58 +08:00
dependabot[bot]
578da0807c Bump http-proxy from 1.17.0 to 1.18.1 in /cmdb-ui
Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.17.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.17.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-11 00:19:27 +00:00
dependabot[bot]
3eb35f5497 Bump handlebars from 4.4.5 to 4.7.6 in /cmdb-ui
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.4.5 to 4.7.6.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...v4.7.6)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-10 03:19:57 +00:00
dependabot[bot]
9669ad04cd Bump quill from 1.3.6 to 1.3.7 in /cmdb-ui
Bumps [quill](https://github.com/quilljs/quill) from 1.3.6 to 1.3.7.
- [Release notes](https://github.com/quilljs/quill/releases)
- [Changelog](https://github.com/quilljs/quill/blob/v1.3.7/CHANGELOG.md)
- [Commits](https://github.com/quilljs/quill/compare/v1.3.6...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-04 00:38:10 +00:00
dependabot[bot]
70214807ca Bump elliptic from 6.4.1 to 6.5.3 in /cmdb-ui
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.4.1 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.4.1...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-01 08:41:43 +00:00
pycook
7c1c309f7a Merge pull request #51 from pycook/dependabot/npm_and_yarn/cmdb-ui/lodash-4.17.19
Bump lodash from 4.17.14 to 4.17.19 in /cmdb-ui
2020-07-21 18:17:46 +08:00
dependabot[bot]
9b9799ff5e Bump lodash from 4.17.14 to 4.17.19 in /cmdb-ui
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.14 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.14...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 03:35:06 +00:00
pycook
b2578b61fa add command: add-user | del-user 2020-06-11 21:37:41 +08:00
pycook
619f47ae13 Merge pull request #49 from pycook/dependabot/npm_and_yarn/cmdb-ui/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4 in /cmdb-ui
2020-06-08 18:37:31 +08:00
dependabot[bot]
37c5e31799 Bump websocket-extensions from 0.1.3 to 0.1.4 in /cmdb-ui
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-07 15:54:30 +00:00
pycook
ab70b2a655 Merge pull request #48 from lovvvve/master
fix(sso login): sso login redirect
2020-06-01 12:05:05 +08:00
Lovvvve
c285606f4a fix(sso login): sso login redirect 2020-06-01 12:01:55 +08:00
Lovvvve
6d3611bd73 fix(sso login): sso login redirect 2020-06-01 11:48:21 +08:00
pycook
764f6a07e0 fix merge conflict 2020-05-28 20:39:47 +08:00
pycook
ae8d487af4 Readme is in Chinese by default
Committer: pycook <pycook@126.com>

Author:    pycook <pycook@126.com>
2020-05-28 20:35:11 +08:00
pycook
87c6554555 update readme 2020-05-28 20:28:49 +08:00
pycook
f5671c2a2a Fix: spelling mistakes 2020-05-28 20:28:49 +08:00
pycook
43ad3dfa7b release version 2.1 2020-05-28 20:28:49 +08:00
pycook
29fa17a0b8 update readme 2020-04-10 17:22:33 +08:00
pycook
5191d6ed73 Merge branch 'master' of https://github.com/pycook/cmdb 2020-04-07 18:03:03 +08:00
pycook
8348f8e7b1 Fix the judgment of app admin 2020-04-07 18:02:26 +08:00
pycook
75c48a0807 Fix: spelling mistakes 2020-04-01 21:40:51 +08:00
pycook
5b38385f7e release version 2.1 2020-04-01 21:20:47 +08:00
pycook
036e1d236b auth with ldap 2020-04-01 20:30:44 +08:00
pycook
c31be0f753 UI: batch update relation 2020-04-01 11:09:41 +08:00
pycook
764d2fac3f add .eslintrc.js 2020-03-26 17:35:26 +08:00
pycook
f4079e9c3e Merge pull request #42 from pycook/dependabot/npm_and_yarn/cmdb-ui/yarn-1.22.0
Bump yarn from 1.21.1 to 1.22.0 in /cmdb-ui
2020-03-26 17:29:53 +08:00
pycook
2a0ed72235 fix: delete attribute 2020-03-23 15:49:33 +08:00
dependabot[bot]
9e803ae4c7 Bump yarn from 1.21.1 to 1.22.0 in /cmdb-ui
Bumps [yarn](https://github.com/yarnpkg/yarn) from 1.21.1 to 1.22.0.
- [Release notes](https://github.com/yarnpkg/yarn/releases)
- [Changelog](https://github.com/yarnpkg/yarn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yarnpkg/yarn/compare/v1.21.1...v1.22.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-22 03:14:30 +00:00
pycook
bebdb61adf update deps 2020-03-22 11:14:09 +08:00
pycook
f49cad771b Merge pull request #41 from pycook/dependabot/npm_and_yarn/cmdb-ui/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4 in /cmdb-ui
2020-03-15 13:06:51 +08:00
dependabot[bot]
a5b4fbda40 Bump acorn from 5.7.3 to 5.7.4 in /cmdb-ui
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-14 18:55:18 +00:00
pycook
2cce2d5cf2 Fix: permission management 2020-03-13 10:30:21 +08:00
pycook
e720b7af66 Merge branch 'develop' of https://github.com/pycook/cmdb into develop 2020-03-10 17:03:23 +08:00
pycook
09e4a5111b Merge pull request #40 from OhBonsai/develop
chore: use wait script to hang api before cache/db/es started
2020-03-10 17:01:58 +08:00
penzai
3539b12503 chore: use wait script to hang api before cache/db/es started 2020-03-10 16:49:40 +08:00
pycook
21d8673b5d Merge pull request #38 from AngrygrayWolf/dev
Change the requirements to support python3.8
2020-03-07 22:06:25 +08:00
what
7154426dc7 Change Pipfile 2020-03-07 21:51:37 +08:00
what
ca75c7dcd0 Change the requirements to support python3.8 2020-03-07 21:20:46 +08:00
pycook
194a2254a6 fix case sensitive of ES search 2020-02-28 14:32:51 +08:00
pycook
26abad14d0 Merge branch 'develop' of https://github.com/pycook/cmdb into develop 2020-02-23 23:13:59 +08:00
pycook
1521a71f9c alter table c_preference_relation_views column name varchar(64) 2020-02-23 23:13:22 +08:00
pycook
d425b455f1 Merge pull request #36 from OhBonsai/develop
test: add ci and ci relation crud test cases
2020-02-23 23:12:36 +08:00
pycook
230307474b version 2.1 and update readme 2020-02-23 20:21:13 +08:00
pycook
69d6b40e39 / redirect to /relation_views 2020-02-23 18:53:28 +08:00
pycook
5dc2f89e7f fix i18n 2020-02-23 18:41:23 +08:00
pycook
9eaca4d6a0 add library future 2020-02-21 23:29:20 +08:00
pycook
3680a462f5 Remove Chinese comments 2020-02-21 23:14:26 +08:00
pycook
3ac50e7cd8 lint 2020-02-21 22:46:12 +08:00
pycook
21b2cc1d5d The resource view is made into a two-level menu 2020-02-21 22:44:10 +08:00
penzai
cd5448cc7d test: add ci and ci relation crud test cases 2020-02-18 22:05:13 +08:00
pycook
10610bdb4b logo left justify 2020-02-16 19:18:51 +08:00
pycook
b5c2156387 Merge pull request #35 from shaohaojiecoder/i18n
I18n
2020-02-16 19:06:58 +08:00
pycook
b05ae0d1a7 Merge pull request #34 from OhBonsai/develop
add test cases
2020-02-16 19:06:24 +08:00
shaohaojiecoder
bbf6138d43 Merge branch 'develop' into i18n 2020-02-16 18:19:09 +08:00
shaohaojiecoder
1ba3e6a680 add a log pic 2020-02-16 18:18:15 +08:00
penzai
64045c1f93 test: add some test cases 2020-02-16 18:03:33 +08:00
penzai
5a3e55813c model: allow origin and ticket_id nullable in OperationRecord 2020-02-16 18:03:08 +08:00
penzai
bc72e58886 auth: add user in flask.g when auth by jwt 2020-02-16 18:02:24 +08:00
pycook
9e78955ba1 ACL i18n 2020-02-16 17:36:03 +08:00
pycook
136853d9a4 Merge pull request #33 from shaohaojiecoder/i18n
fix local storage for defalut lang
2020-02-16 14:51:59 +08:00
pycook
036e3ad00d modeling i18n 2020-02-16 14:50:17 +08:00
shaohaojiecoder
5ce6c93237 fix local storage for defalut lang 2020-02-16 13:47:30 +08:00
pycook
43dba7f7ed Merge pull request #32 from OhBonsai/develop
fix: recycle import by celery task
2020-02-16 10:38:27 +08:00
penzai
f4879d20d6 fix: recycle import by celery task 2020-02-16 09:39:33 +08:00
pycook
740e4c6034 i18n 2020-02-15 20:57:47 +08:00
pycook
0f2baa1d94 Merge pull request #31 from shaohaojiecoder/i18n
I18n
2020-02-11 09:50:50 +08:00
pycook
405b0af72c Merge branch 'develop' into i18n 2020-02-11 09:50:11 +08:00
shaohaojiecoder
a4e5178979 fix meta title 2020-02-09 19:50:23 +08:00
shaohaojiecoder
c14fe23283 add i18n basic structure 2020-02-09 17:54:57 +08:00
shaohaojiecoder
b3a058f908 add something 2020-02-09 17:22:17 +08:00
shaohaojiecoder
bd82a0e27c add some 2020-02-08 22:27:56 +08:00
pycook
f22a5c3543 Define display fields 2020-02-08 17:39:42 +08:00
pycook
ed81c3f091 Define display fields 2020-02-08 17:36:54 +08:00
shaohaojiecoder
07814b85f9 add basic 2020-02-07 22:05:52 +08:00
pycook
db52b28d6b fix jwt decode 2020-02-06 09:59:24 +08:00
pycook
fc85ba21c8 Merge pull request #29 from OhBonsai/master
fix ci_type_attr_group update bug and add ci_type test case
2020-02-04 12:47:15 +08:00
Bonsai
6c5ee3fcd9 Merge pull request #2 from pycook/master
merge from main repo
2020-02-04 10:56:03 +08:00
penzai
40f1ef88a9 test: add ci_type test cases 2020-02-04 10:54:16 +08:00
penzai
bce422ffc8 fix: update attribute group without name params will fail. #tests/test_cmdb_ci_type.py::test_update_attribute_group_ci_type 2020-02-04 10:53:07 +08:00
pycook
7c79066532 Merge branch 'master' of https://github.com/pycook/cmdb 2020-01-19 18:18:43 +08:00
pycook
1129ac93fb Merge pull request #28 from shaohaojiecoder/master
fix drag group and attrs
2020-01-19 18:18:23 +08:00
haojie.shao
5ab0e7e737 fix drag group and attrs 2020-01-19 18:14:53 +08:00
pycook
23319c7417 /ci_types/<int:type_id>/attributes/transfer and /ci_types/<int:type_id>/attribute_groups/transfer 2020-01-19 17:59:32 +08:00
pycook
c74f85cabb Merge pull request #26 from OhBonsai/master
test: add basic test code and attribute create api test case
2020-01-17 15:34:02 +08:00
penzai
fce2b689fb Merge remote-tracking branch 'origin/master' 2020-01-17 15:10:16 +08:00
penzai
105327bb0c test: add basic test code and attribute create api test case 2020-01-17 15:08:46 +08:00
Bonsai
745c43d0a4 Merge pull request #1 from pycook/master
merge master
2020-01-17 10:05:05 +08:00
pycook
3130d94568 [fix] cycle import 2020-01-15 11:52:33 +08:00
pycook
04a66eb239 flush cache when delete attribute 2020-01-15 09:06:31 +08:00
pycook
68390ec6f1 [fix] delete CIType's attribute 2020-01-14 20:52:36 +08:00
pycook
17392be138 api docs update 2020-01-06 21:58:06 +08:00
pycook
f2fdb29221 api docs update 2020-01-06 21:54:33 +08:00
pycook
4a18698423 update README 2019-12-31 22:53:21 +08:00
pycook
95ccee04f9 update README 2019-12-31 22:50:01 +08:00
pycook
b60628247b Update README.md
update
2019-12-31 22:37:36 +08:00
pycook
a6d7699ab4 Merge pull request #24 from fxiang21/master
add Readme of English
2019-12-31 22:32:48 +08:00
fxiang21
4b21bcc438 add Readme of English 2019-12-31 22:25:42 +08:00
pycook
33dce2f0f3 Update README.md
[fix] flask db-setup
2019-12-31 11:06:59 +08:00
pycook
d43b827fe5 [fix] fuzzy search 2019-12-25 13:36:43 +08:00
pycook
aec8bade41 [fix] security alerts 2019-12-25 10:19:03 +08:00
pycook
89ae89a449 [fix] validate attribute is required 2019-12-24 20:37:32 +08:00
pycook
945f90e386 disable eslint warning 2019-12-24 15:15:03 +08:00
pycook
2ba6a16613 support JSON type 2019-12-23 18:51:33 +08:00
pycook
6089039366 fix sidebar menu in mobile 2019-12-23 11:58:41 +08:00
pycook
e1e5307084 add yarn.lock 2019-12-23 11:27:47 +08:00
pycook
2ff7fce9dd flask init-acl 2019-12-20 12:57:39 +09:00
pycook
fc4d3e0c1a update makefile 2019-12-18 23:36:58 +09:00
pycook
f66a94712e Modify code organization 2019-12-18 23:33:22 +09:00
pycook
24664c7686 catch abort exception when getting relation views 2019-12-13 09:59:38 +08:00
pycook
1d668bab6e update 2019-12-12 21:45:19 +08:00
pycook
3d4b84909e fix delete relation view 2019-12-12 21:36:33 +08:00
pycook
8341e742eb [fix] update attribute which is list 2019-12-11 18:12:10 +08:00
pycook
a71ba83de0 release 2.0 2019-12-11 12:43:55 +08:00
pycook
9668131c18 V2.0 2019-12-11 12:14:23 +08:00
pycook
4a744dcad9 fix relation tree 2019-12-10 15:35:59 +08:00
pycook
2a420225e2 Merge pull request #22 from lovvvve/FixDelCi_type
fix(ci_type api): fix the judgment condition of deleting ci_type
2019-12-10 14:41:27 +08:00
Lovvvve
ff67785618 fix(ci_type api): fix the judgment condition of deleting ci_type 2019-12-10 14:31:27 +08:00
pycook
dfe1ba55d5 sidebar scroll 2019-12-09 17:16:38 +08:00
pycook
90b1b6b7af fix relation view 2019-12-09 12:03:58 +08:00
pycook
d5fbe42ed7 relation view bugfix 2019-12-08 00:20:55 +08:00
pycook
f424ad6864 acl done and bugfix 2019-12-06 22:33:31 +08:00
pycook
16b724bd40 ACL: permission management [doing] 2019-12-04 18:14:09 +08:00
pycook
f70ed54cad update readme 2019-12-04 09:26:01 +08:00
pycook
dd64564160 remove print 2019-12-03 22:13:14 +08:00
pycook
cc2cdbcc9f fix delete ci relation 2019-12-03 21:57:44 +08:00
pycook
81fe850627 fix get second cis api 2019-12-03 20:10:27 +08:00
pycook
487d9f76f6 关系视图定义支持两只方式 2019-12-03 19:54:01 +08:00
pycook
92dd4c5dfe relation view has been optimised 2019-12-03 19:10:54 +08:00
pycook
8ee7c6daf8 version 1.5: update docker file 2019-11-30 23:07:12 +08:00
pycook
882b158d18 cmdb.sql update 2019-11-29 22:21:41 +08:00
pycook
85222443c0 relation view [done] 2019-11-29 18:11:18 +08:00
pycook
1696ecf49d relation view [doing] 2019-11-28 21:17:06 +08:00
pycook
73b92ff533 relation view define [done] 2019-11-27 18:25:53 +08:00
pycook
e977bb15a5 GPLv2 2019-11-25 20:35:05 +08:00
pycook
7c46d6cdbf change to GPLv2 2019-11-25 20:33:56 +08:00
pycook
4d11c1f7db License change to GPLv3 2019-11-25 19:42:37 +08:00
pycook
0a563deb11 UI: relation type define [done] 2019-11-25 19:23:51 +08:00
pycook
ba80ec4403 /acl/resources add param resource_type_id 2019-11-24 22:33:57 +08:00
pycook
3b7cc4595b fix grant 2019-11-24 22:29:51 +08:00
pycook
9fe47657a6 Merge pull request #20 from kdyq007/master
[更新] 新增角色、资源、权限页面
2019-11-24 17:29:58 +08:00
kdyq007
5a4a6caa07 Merge branch 'master' of https://github.com/kdyq007/cmdb 2019-11-24 17:21:27 +08:00
kdyq007
9dadbe1599 Merge pull request #6 from pycook/master
fix acl api
2019-11-24 16:43:53 +08:00
pycook
40d016f513 fix acl api 2019-11-24 16:35:28 +08:00
kdyq007
655edaa7c8 [更新] 完成权限管理 2019-11-24 15:40:38 +08:00
kdyq007
7fa5cff919 [更新] 完成权限管理页面 2019-11-24 15:22:18 +08:00
kdyq007
d19834ed5d Merge pull request #5 from pycook/master
同步
2019-11-23 21:53:46 +08:00
pycook
b6be430aa3 fix 2019-11-23 21:50:45 +08:00
kdyq007
63792c242f [更新] 完成资源类型页面 2019-11-23 20:16:31 +08:00
kdyq007
10f7029722 [保存] 完成资源类型权限显示 2019-11-23 18:08:52 +08:00
pycook
ba176542dc fix acl resource 2019-11-23 17:42:33 +08:00
kdyq007
aae3b6e2ff Merge pull request #4 from pycook/master
fix acl resource_type
2019-11-23 17:36:42 +08:00
pycook
b370c7d46e fix acl resource_type 2019-11-23 17:24:43 +08:00
kdyq007
efa5a8ea5d Merge pull request #3 from pycook/master
同步
2019-11-23 14:52:41 +08:00
pycook
fd532626ac relative view api [done] 2019-11-22 18:18:22 +08:00
pycook
617337c614 Realize /api/v0.1/ci_relations/s [done] 2019-11-21 18:21:03 +08:00
kdyq007
9a3d24ac81 [更新] 保存一下 2019-11-20 19:02:36 +08:00
kdyq007
454dd4c56b Merge branch 'master' of https://github.com/kdyq007/cmdb 2019-11-19 21:52:33 +08:00
kdyq007
88ad72d4dc Merge pull request #2 from pycook/master
update
2019-11-19 21:52:02 +08:00
kdyq007
8d1517d550 [更新] 完成基础role和user管理 2019-11-19 21:49:51 +08:00
pycook
d3a8ef5966 fix get user by uid 2019-11-19 21:46:53 +08:00
pycook
e5baa5012d acl: resource type api 2019-11-19 21:41:46 +08:00
pycook
a1f63b00dd fix search 2019-11-19 18:32:35 +08:00
pycook
47ded84231 elastic search [done] 2019-11-19 18:16:31 +08:00
kdyq007
224a48a5f3 [更新] 去除app_id 2019-11-18 22:22:38 +08:00
pycook
0e7c52df71 es search update 2019-11-18 22:05:59 +08:00
pycook
ff701cc770 search by elasticsearch [doing] 2019-11-18 20:02:25 +08:00
kdyq007
6a7bb725cc Merge pull request #1 from pycook/master
怎么玩的?反向pull request
2019-11-18 18:31:14 +08:00
pycook
0a13186c13 fix acl api 2019-11-18 12:02:02 +08:00
kdyq007
a0ffeb9950 [更新] 完成角色管理页面 2019-11-17 21:08:04 +08:00
kdyq007
6c70ec6d53 [更新] 完成roles基本接口 2019-11-17 17:09:24 +08:00
qiqi
4b5f82699a [更新] 完成用户管理页面 2019-11-17 09:32:39 +08:00
pycook
f78c3b928b pep8 2019-11-15 18:03:06 +08:00
pycook
332659c1d5 update acl 2019-11-15 16:54:56 +08:00
pycook
3beb2706dc Merge pull request #18 from kdyq007/master
[更新] 修改图片路径、压缩图片
2019-11-14 21:59:38 +08:00
qiqi
a14111e1ce [更新] 优化格式 2019-11-14 21:51:58 +08:00
qiqi
c4320c14f9 [更新] 更换图片位置、压缩图片 2019-11-14 21:48:36 +08:00
qiqi
4c5442748f [更新] 优化说明文件格式 2019-11-14 21:00:24 +08:00
qiqi
a81750acba [更新] 新增Q群 README.md 2019-11-14 20:55:48 +08:00
pycook
0439e2462b update acl 2019-11-14 18:35:31 +08:00
pycook
3b62bd7ac9 update readme 2019-11-13 14:02:02 +08:00
pycook
f6add52721 python3.7 timezone fix 2019-11-13 13:56:44 +08:00
pycook
c85e535288 update acl 2019-11-13 13:25:42 +08:00
pycook
c0c6d116b5 docker images use aliyun 2019-11-13 11:56:17 +08:00
pycook
39153e92d1 update Makefile and support for install by make 2019-11-12 11:55:04 +08:00
pycook
42bcc2e510 fix py3 2019-11-12 11:15:25 +08:00
pycook
398fbb25dc merge Dockerfile 2019-11-12 10:40:37 +08:00
pycook
4b312d4f99 delete docs/Dockerfile 2019-11-11 23:12:50 +08:00
pycook
10414155a5 fix timezone 2019-11-11 23:11:12 +08:00
pycook
feda0c37e7 update README 2019-11-11 16:10:02 +08:00
pycook
173c120b64 flask init-cache 2019-11-11 15:46:57 +08:00
pycook
5f2a0d1a7b Remove package-lock.json and remove some compile warnings 2019-11-11 13:16:07 +08:00
pycook
50f894a01d add command init-cache 2019-11-11 11:27:43 +08:00
pycook
66e93e73af Merge branch 'master' of https://github.com/pycook/cmdb 2019-11-11 09:20:07 +08:00
pycook
58ad9d3f05 vue lint 2019-11-11 00:25:22 +08:00
pycook
08c96039e9 gunicorn==19.5.0 2019-11-10 19:10:23 +08:00
pycook
ca0dd97626 Docker to production 2019-11-10 19:06:38 +08:00
pycook
7810ee3974 Partially completed backend development of permissions management 2019-11-08 17:42:13 +08:00
pycook
2cfea7ef08 Update README.md
docker 一键安装说明补充
2019-11-08 15:26:22 +08:00
pycook
0cee6cea25 fix py2.7 unicode encoding error 2019-11-08 15:15:31 +08:00
pycook
5d13ba2f26 users drop is_admin 2019-11-08 14:58:21 +08:00
pycook
a583433530 fix unicode encode error 2019-11-08 14:37:53 +08:00
fxiang21
733ac3b2b4 移除多余的docker-start目录 2019-11-08 09:20:34 +08:00
fxiang21
ef6300255a 修复nginx转发问题 2019-11-08 09:20:27 +08:00
fxiang21
aad37dcf0b 添加容器化部署方式 2019-11-08 09:20:09 +08:00
pycook
cce10d39ea code format 2019-11-07 19:18:31 +08:00
pycook
c521dd447e Update README.md
pipenv run flask run -h 0.0.0.0
2019-11-05 17:52:45 +08:00
pycook
4d0cd4ba56 Update README.md
如果是非本机访问, 要修改ui/.env里VUE_APP_API_BASE_URL里的IP地址
2019-11-05 17:44:48 +08:00
pycook
7291274cb1 Update README.md
如果是非本机访问, 要修改ui/.env里VUE_APP_API_BASE_URL里的IP地址
2019-11-05 17:43:40 +08:00
pycook
44f2e383c3 update overview jpeg url 2019-11-01 11:37:16 +08:00
pycook
1f8219b418 fix add integer list 2019-11-01 11:27:24 +08:00
pycook
cb2f170ded mkdir logs, ignore *.log 2019-11-01 10:45:35 +08:00
pycook
1241a23ba8 Update README.md
add cmdb.sql
2019-10-28 21:48:46 +08:00
pycook
7d7744b7dc add docs/cmdb.sql 2019-10-28 21:46:41 +08:00
pycook
9c7d51127a fix date picker 2019-10-24 20:43:59 +08:00
pycook
b5a987f6b4 choice value tip fix 2019-10-24 20:43:58 +08:00
pycook
7bbc68bfd5 fix delete ci type 2019-10-24 20:43:58 +08:00
pycook
99d11e11ce fix ci types show 2019-10-24 20:43:58 +08:00
pycook
7b96ac4638 attribute alias must be unique 2019-10-24 20:43:58 +08:00
pycook
0a36330852 fix attributes paginate 2019-10-24 20:43:54 +08:00
pycook
9105f92c82 update README 2019-10-24 20:43:51 +08:00
pycook
57541ab486 Update README.md
create tables fix
2019-10-24 20:43:51 +08:00
pycook
a0fcbd220e attributes paginate and fix update value 2019-10-24 20:43:51 +08:00
pycook
d54b404eb6 add docs 2019-10-24 20:43:51 +08:00
pycook
620c5bb5eb ci search return unique key 2019-10-24 20:43:51 +08:00
pycook
0fde1d699d invalid username or password -> 403 2019-10-24 20:43:51 +08:00
shaohaojiecoder
61f77cf311 add batch module 2019-10-24 20:43:51 +08:00
lilixiang
13476128d5 add 添加属性库和模型模块 2019-10-24 20:43:51 +08:00
pycook
5cdb4ecd2a Revert "add 添加属性库和模型模块" 2019-10-24 20:43:51 +08:00
lilixiang
64c3b9da3b add 添加属性库和模型模块 2019-10-24 20:43:51 +08:00
pycook
55dad7a58c cache强制unicode编码 2019-08-30 09:46:24 +08:00
pycook
38dabc35e5 add .gitattributes 2019-08-28 21:08:28 +08:00
pycook
5b4f95a50e add ui 2019-08-28 20:51:51 +08:00
pycook
f3046d3c91 remove ui 2019-08-28 20:48:23 +08:00
pycook
5faae9af67 remove ui 2019-08-28 20:48:04 +08:00
pycook
c0b50642e0 update README 2019-08-28 20:45:59 +08:00
pycook
12ca296879 升级后端并开源UI 2019-08-28 20:34:10 +08:00
pycook
420c6cea2b delete。。。 2016-08-26 13:46:03 +08:00
pycook
ccc4bb48fa pep8 2016-06-27 10:50:32 +08:00
124 changed files with 14831 additions and 5684 deletions

1
.gitignore vendored
View File

@@ -70,7 +70,6 @@ settings.py
# UI
cmdb-ui/node_modules
cmdb-ui/dist
cmdb-ui/yarn.lock
# Log files
cmdb-ui/npm-debug.log*

View File

@@ -73,33 +73,19 @@
## 安装
### Docker 一键快速构建
> 方法一
- 第一步: 先安装 docker 环境, 以及docker-compose
- 第二步: 拷贝项目
```shell
git clone https://github.com/veops/cmdb.git
```
- 第三步:进入主目录,执行:
- 进入主目录(先安装 docker 环境, 注意要clone整个项目
```
docker-compose up -d
```
> 方法二, 该方法适用于linux系统
- 第一步: 先安装 docker 环境, 以及docker-compose
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
```shell
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh
sh install.sh install
```
### [本地开发环境搭建](docs/local.md)
### [Makefile 安装](docs/makefile.md)
## 验证
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
- username: demo 或者 admin
- password: 123456
### [本地开发环境搭建](docs/local.md)
### [Makefile 安装](docs/makefile.md)
---

View File

@@ -6,7 +6,7 @@ name = "pypi"
[packages]
# Flask
Flask = "==2.3.2"
Werkzeug = ">=2.3.6"
Werkzeug = "==2.3.6"
click = ">=5.0"
# Api
Flask-RESTful = "==0.3.10"
@@ -21,7 +21,7 @@ Flask-Migrate = "==2.5.2"
gunicorn = "==21.0.1"
supervisor = "==4.0.3"
# Auth
Flask-Login = ">=0.6.2"
Flask-Login = "==0.6.2"
Flask-Bcrypt = "==1.0.1"
Flask-Cors = ">=3.0.8"
ldap3 = "==2.9.1"
@@ -43,7 +43,7 @@ WTForms = "==3.0.0"
email-validator = "==1.3.1"
treelib = "==1.6.1"
flasgger = "==0.9.5"
Pillow = ">=10.0.1"
Pillow = "==9.3.0"
# other
six = "==1.16.0"
bs4 = ">=0.0.1"
@@ -62,7 +62,6 @@ alembic = "==1.7.7"
hvac = "==2.0.0"
colorama = ">=0.4.6"
pycryptodomex = ">=3.19.0"
lz4 = ">=4.3.2"
[dev-packages]
# Testing

View File

@@ -19,8 +19,7 @@ from flask.json.provider import DefaultJSONProvider
import api.views.entry
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
from api.extensions import inner_secrets
from api.lib.perm.authentication.cas import CAS
from api.lib.perm.authentication.oauth2 import OAuth2
from api.flask_cas import CAS
from api.lib.secrets.secrets import InnerKVManger
from api.models.acl import User
@@ -97,7 +96,6 @@ def create_app(config_object="settings"):
register_shell_context(app)
register_commands(app)
CAS(app)
OAuth2(app)
app.wsgi_app = ReverseProxy(app.wsgi_app)
configure_upload_dir(app)
@@ -194,11 +192,10 @@ def configure_logger(app):
app.logger.addHandler(handler)
log_file = app.config['LOG_PATH']
if log_file and log_file != "/dev/stdout":
file_handler = RotatingFileHandler(log_file,
maxBytes=2 ** 30,
backupCount=7)
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
file_handler = RotatingFileHandler(log_file,
maxBytes=2 ** 30,
backupCount=7)
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))

View File

@@ -1,8 +1,6 @@
import click
from flask.cli import with_appcontext
from api.lib.perm.acl.user import UserCRUD
@click.command()
@with_appcontext
@@ -25,18 +23,50 @@ def init_acl():
role_rebuild.apply_async(args=(role.id, app.id), queue=ACL_QUEUE)
@click.command()
@with_appcontext
def add_user():
"""
create a user
is_admin: default is False
"""
username = click.prompt('Enter username', confirmation_prompt=False)
password = click.prompt('Enter password', hide_input=True, confirmation_prompt=True)
email = click.prompt('Enter email ', confirmation_prompt=False)
UserCRUD.add(username=username, password=password, email=email)
# @click.command()
# @with_appcontext
# def acl_clean():
# from api.models.acl import Resource
# from api.models.acl import Permission
# from api.models.acl import RolePermission
#
# perms = RolePermission.get_by(to_dict=False)
#
# for r in perms:
# perm = Permission.get_by_id(r.perm_id)
# if perm and perm.app_id != r.app_id:
# resource_id = r.resource_id
# resource = Resource.get_by_id(resource_id)
# perm_name = perm.name
# existed = Permission.get_by(resource_type_id=resource.resource_type_id, name=perm_name, first=True,
# to_dict=False)
# if existed is not None:
# other = RolePermission.get_by(rid=r.rid, perm_id=existed.id, resource_id=resource_id)
# if not other:
# r.update(perm_id=existed.id)
# else:
# r.soft_delete()
# else:
# r.soft_delete()
#
#
# @click.command()
# @with_appcontext
# def acl_has_resource_role():
# from api.models.acl import Role
# from api.models.acl import App
# from api.lib.perm.acl.cache import HasResourceRoleCache
# from api.lib.perm.acl.role import RoleCRUD
#
# roles = Role.get_by(to_dict=False)
# apps = App.get_by(to_dict=False)
# for role in roles:
# if role.app_id:
# res = RoleCRUD.recursive_resources(role.id, role.app_id)
# if res.get('resources') or res.get('groups'):
# HasResourceRoleCache.add(role.id, role.app_id)
# else:
# for app in apps:
# res = RoleCRUD.recursive_resources(role.id, app.id)
# if res.get('resources') or res.get('groups'):
# HasResourceRoleCache.add(role.id, app.id)

View File

@@ -19,7 +19,6 @@ from api.lib.cmdb.cache import AttributeCache
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_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum
@@ -30,6 +29,7 @@ from api.lib.perm.acl.cache import AppCache
from api.lib.perm.acl.resource import ResourceCRUD
from api.lib.perm.acl.resource import ResourceTypeCRUD
from api.lib.perm.acl.role import RoleCRUD
from api.lib.perm.acl.user import UserCRUD
from api.lib.secrets.inner import KeyManage
from api.lib.secrets.inner import global_key_threshold
from api.lib.secrets.secrets import InnerKVManger
@@ -50,17 +50,12 @@ def cmdb_init_cache():
ci_relations = CIRelation.get_by(to_dict=False)
relations = dict()
relations2 = dict()
for cr in ci_relations:
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
if cr.ancestor_ids:
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
for i in relations:
relations[i] = json.dumps(relations[i])
if relations:
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
if relations2:
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
es = None
if current_app.config.get("USE_ES"):
@@ -133,10 +128,10 @@ def cmdb_init_acl():
# 3. add resource and grant
ci_types = CIType.get_by(to_dict=False)
resource_type_id = ResourceType.get_by(name=ResourceTypeEnum.CI, first=True, to_dict=False).id
type_id = ResourceType.get_by(name=ResourceTypeEnum.CI, first=True, to_dict=False).id
for ci_type in ci_types:
try:
ResourceCRUD.add(ci_type.name, resource_type_id, app_id)
ResourceCRUD.add(ci_type.name, type_id, app_id)
except AbortException:
pass
@@ -146,10 +141,10 @@ def cmdb_init_acl():
[PermEnum.READ])
relation_views = PreferenceRelationView.get_by(to_dict=False)
resource_type_id = ResourceType.get_by(name=ResourceTypeEnum.RELATION_VIEW, first=True, to_dict=False).id
type_id = ResourceType.get_by(name=ResourceTypeEnum.RELATION_VIEW, first=True, to_dict=False).id
for view in relation_views:
try:
ResourceCRUD.add(view.name, resource_type_id, app_id)
ResourceCRUD.add(view.name, type_id, app_id)
except AbortException:
pass
@@ -159,6 +154,57 @@ def cmdb_init_acl():
[PermEnum.READ])
@click.command()
@click.option(
'-u',
'--user',
help='username'
)
@click.option(
'-p',
'--password',
help='password'
)
@click.option(
'-m',
'--mail',
help='mail'
)
@with_appcontext
def add_user(user, password, mail):
"""
create a user
is_admin: default is False
Example: flask add-user -u <username> -p <password> -m <mail>
"""
assert user is not None
assert password is not None
assert mail is not None
UserCRUD.add(username=user, password=password, email=mail)
@click.command()
@click.option(
'-u',
'--user',
help='username'
)
@with_appcontext
def del_user(user):
"""
delete a user
Example: flask del-user -u <username>
"""
assert user is not None
from api.models.acl import User
u = User.get_by(username=user, first=True, to_dict=False)
u and UserCRUD.delete(u.uid)
@click.command()
@with_appcontext
def cmdb_counter():
@@ -283,6 +329,7 @@ def valid_address(address):
}
KeyManage.print_response(response)
return False
return True
@@ -397,7 +444,6 @@ def cmdb_password_data_migrate():
value_table = CIIndexValueText if attr.is_index else CIValueText
failed = False
for i in value_table.get_by(attr_id=attr.id, to_dict=False):
if current_app.config.get("SECRETS_ENGINE", 'inner') == 'inner':
_, status = InnerCrypt().decrypt(i.value)
@@ -408,7 +454,6 @@ def cmdb_password_data_migrate():
if status:
CIValueText.create(ci_id=i.ci_id, attr_id=attr.id, value=encrypt_value)
else:
failed = True
continue
elif current_app.config.get("SECRETS_ENGINE") == 'vault':
if i.value == '******':
@@ -419,48 +464,8 @@ def cmdb_password_data_migrate():
vault.update("/{}/{}".format(i.ci_id, i.attr_id), dict(v=i.value))
except Exception as e:
print('save password to vault failed: {}'.format(e))
failed = True
continue
else:
continue
i.delete()
if not failed and attr.is_index:
attr.update(is_index=False)
@click.command()
@with_appcontext
def cmdb_agent_init():
"""
Initialize the agent's permissions and obtain the key and secret
"""
from api.models.acl import User
user = User.get_by(username="cmdb_agent", first=True, to_dict=False)
if user is None:
click.echo(
click.style('user cmdb_agent does not exist, please use flask add-user to create it first', fg='red'))
return
# grant
_app = AppCache.get('cmdb') or App.create(name='cmdb')
app_id = _app.id
ci_types = CIType.get_by(to_dict=False)
resource_type_id = ResourceType.get_by(name=ResourceTypeEnum.CI, first=True, to_dict=False).id
for ci_type in ci_types:
try:
ResourceCRUD.add(ci_type.name, resource_type_id, app_id)
except AbortException:
pass
ACLManager().grant_resource_to_role(ci_type.name,
"cmdb_agent",
ResourceTypeEnum.CI,
[PermEnum.READ, PermEnum.UPDATE, PermEnum.ADD, PermEnum.DELETE])
click.echo("Key : {}".format(click.style(user.key, bg='red')))
click.echo("Secret: {}".format(click.style(user.secret, bg='red')))

View File

@@ -10,6 +10,9 @@ from api.models.common_setting import Employee, Department
class InitEmployee(object):
"""
初始化员工
"""
def __init__(self):
self.log = current_app.logger
@@ -55,8 +58,7 @@ class InitEmployee(object):
self.log.error(ErrFormat.acl_import_user_failed.format(user['username'], str(e)))
self.log.error(e)
@staticmethod
def get_rid_by_uid(uid):
def get_rid_by_uid(self, uid):
from api.models.acl import Role
role = Role.get_by(first=True, uid=uid)
return role['id'] if role is not None else 0
@@ -69,8 +71,7 @@ class InitDepartment(object):
def init(self):
self.init_wide_company()
@staticmethod
def hard_delete(department_id, department_name):
def hard_delete(self, department_id, department_name):
existed_deleted_list = Department.query.filter(
Department.department_name == department_name,
Department.department_id == department_id,
@@ -79,12 +80,11 @@ class InitDepartment(object):
for existed in existed_deleted_list:
existed.delete()
@staticmethod
def get_department(department_name):
def get_department(self, department_name):
return Department.query.filter(
Department.department_name == department_name,
Department.deleted == 0,
).first()
).order_by(Department.created_at.asc()).first()
def run(self, department_id, department_name, department_parent_id):
self.hard_delete(department_id, department_name)
@@ -94,7 +94,7 @@ class InitDepartment(object):
if res.department_id == department_id:
return
else:
res.update(
new_d = res.update(
department_id=department_id,
department_parent_id=department_parent_id,
)
@@ -108,11 +108,11 @@ class InitDepartment(object):
new_d = self.get_department(department_name)
if new_d.department_id != department_id:
new_d.update(
new_d = new_d.update(
department_id=department_id,
department_parent_id=department_parent_id,
)
self.log.info(f"init {department_name} success.")
self.log.info(f"初始化 {department_name} 部门成功.")
def run_common(self, department_id, department_name, department_parent_id):
try:
@@ -123,14 +123,19 @@ class InitDepartment(object):
raise Exception(e)
def init_wide_company(self):
"""
创建 id 0, name 全公司 的部门
"""
department_id = 0
department_name = '全公司'
department_parent_id = -1
self.run_common(department_id, department_name, department_parent_id)
@staticmethod
def create_acl_role_with_department():
def create_acl_role_with_department(self):
"""
当前所有部门在ACL创建 role
"""
acl = ACLManager('acl')
role_name_map = {role['name']: role for role in acl.get_all_roles()}
@@ -141,7 +146,7 @@ class InitDepartment(object):
continue
role = role_name_map.get(department.department_name)
if not role:
if role is None:
payload = {
'app_id': 'acl',
'name': department.department_name,
@@ -203,20 +208,25 @@ class InitDepartment(object):
if acl_rid > 0:
acl.grant_resource(acl_rid, resource['id'], perms)
@staticmethod
def check_app(app_name):
def check_app(self, app_name):
acl = ACLManager(app_name)
payload = dict(
name=app_name,
description=app_name
)
app = acl.validate_app()
if not app:
acl.create_app(payload)
return acl
try:
app = acl.validate_app()
if not app:
acl.create_app(payload)
return acl
except Exception as e:
current_app.logger.error(e)
if '不存在' in str(e):
acl.create_app(payload)
return acl
raise Exception(e)
@staticmethod
def get_admin_user_rid():
def get_admin_user_rid(self):
admin = Employee.get_by(first=True, username='admin', to_dict=False)
return admin.acl_rid if admin else 0
@@ -251,19 +261,17 @@ def common_check_new_columns():
from api.extensions import db
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
def get_model_by_table_name(table_name):
for model in db.Model.registry._class_registry.values():
if hasattr(model, '__tablename__') and model.__tablename__ == table_name:
return model
return None
def add_new_column(target_table_name, new_column):
def add_new_column(table_name, new_column):
column_type = new_column.type.compile(engine.dialect)
default_value = new_column.default.arg if new_column.default else None
sql = "ALTER TABLE " + target_table_name + " ADD COLUMN " + new_column.name + " " + column_type
sql = f"ALTER TABLE {table_name} ADD COLUMN {new_column.name} {column_type} "
if new_column.comment:
sql += f" comment '{new_column.comment}'"
@@ -289,8 +297,7 @@ def common_check_new_columns():
model = get_model_by_table_name(table_name)
if model is None:
continue
model_columns = getattr(getattr(getattr(model, '__table__'), 'columns'), '_all_columns')
model_columns = model.__table__.columns._all_columns
for column in model_columns:
if column.name not in existed_column_name_list:
try:
@@ -299,20 +306,3 @@ def common_check_new_columns():
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()
@with_appcontext
def common_sync_file_to_db():
from api.lib.common_setting.upload_file import CommonFileCRUD
CommonFileCRUD.sync_file_to_db()
@click.command()
@with_appcontext
@click.option('--value', type=click.INT, default=-1)
def set_auth_auto_redirect_enable(value):
if value < 0:
return
from api.lib.common_setting.common_data import CommonDataCRUD
CommonDataCRUD.set_auth_auto_redirect_enable(value)

View File

@@ -84,6 +84,66 @@ def clean():
os.remove(full_pathname)
@click.command()
@click.option("--url", default=None, help="Url to test (ex. /static/image.png)")
@click.option(
"--order", default="rule", help="Property on Rule to order by (default: rule)"
)
@with_appcontext
def urls(url, order):
"""Display all of the url matching routes for the project.
Borrowed from Flask-Script, converted to use Click.
"""
rows = []
column_headers = ("Rule", "Endpoint", "Arguments")
if url:
try:
rule, arguments = current_app.url_map.bind("localhost").match(
url, return_rule=True
)
rows.append((rule.rule, rule.endpoint, arguments))
column_length = 3
except (NotFound, MethodNotAllowed) as e:
rows.append(("<{}>".format(e), None, None))
column_length = 1
else:
rules = sorted(
current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order)
)
for rule in rules:
rows.append((rule.rule, rule.endpoint, None))
column_length = 2
str_template = ""
table_width = 0
if column_length >= 1:
max_rule_length = max(len(r[0]) for r in rows)
max_rule_length = max_rule_length if max_rule_length > 4 else 4
str_template += "{:" + str(max_rule_length) + "}"
table_width += max_rule_length
if column_length >= 2:
max_endpoint_length = max(len(str(r[1])) for r in rows)
max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8
str_template += " {:" + str(max_endpoint_length) + "}"
table_width += 2 + max_endpoint_length
if column_length >= 3:
max_arguments_length = max(len(str(r[2])) for r in rows)
max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9
str_template += " {:" + str(max_arguments_length) + "}"
table_width += 2 + max_arguments_length
click.echo(str_template.format(*column_headers[:column_length]))
click.echo("-" * table_width)
for row in rows:
click.echo(str_template.format(*row[:column_length]))
@click.command()
@with_appcontext
def db_setup():

View File

@@ -15,7 +15,7 @@ try:
except ImportError:
from flask import _request_ctx_stack as stack
from . import routing
from api.flask_cas import routing
class CAS(object):

View File

@@ -1,24 +1,14 @@
# -*- coding:utf-8 -*-
import datetime
import uuid
import json
import bs4
from flask import Blueprint
from flask import current_app
from flask import redirect
from flask import request
from flask import session
from flask import url_for
from flask_login import login_user
from flask_login import logout_user
from six.moves.urllib.parse import urlparse
from flask import current_app, session, request, url_for, redirect
from flask_login import login_user, logout_user
from six.moves.urllib_request import urlopen
from api.lib.common_setting.common_data import AuthenticateDataCRUD
from api.lib.common_setting.const import AuthenticateType
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.resp_format import ErrFormat
from .cas_urls import create_cas_login_url
from .cas_urls import create_cas_logout_url
from .cas_urls import create_cas_validate_url
@@ -26,7 +16,6 @@ from .cas_urls import create_cas_validate_url
blueprint = Blueprint('cas', __name__)
@blueprint.route('/api/cas/login')
@blueprint.route('/api/sso/login')
def login():
"""
@@ -40,20 +29,16 @@ def login():
If validation was successful the logged in username is saved in
the user's session under the key `CAS_USERNAME_SESSION_KEY`.
"""
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
if request.values.get("next"):
session["next"] = request.values.get("next")
# _service = url_for('cas.login', _external=True)
_service = "{}://{}{}".format(urlparse(request.referrer).scheme,
urlparse(request.referrer).netloc,
url_for('cas.login'))
_service = url_for('cas.login', _external=True, next=session["next"]) \
if session.get("next") else url_for('cas.login', _external=True)
redirect_url = create_cas_login_url(
config['cas_server'],
config['cas_login_route'],
current_app.config['CAS_SERVER'],
current_app.config['CAS_LOGIN_ROUTE'],
_service)
if 'ticket' in request.args:
@@ -62,38 +47,30 @@ def login():
if request.args.get('ticket'):
if validate(request.args['ticket']):
redirect_url = session.get("next") or config.get("cas_after_login") or "/"
redirect_url = session.get("next") or \
current_app.config.get("CAS_AFTER_LOGIN")
username = session.get("CAS_USERNAME")
user = UserCache.get(username)
login_user(user)
session.permanent = True
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
session['LOGIN_ID'] = _id
else:
del session[cas_token_session_key]
redirect_url = create_cas_login_url(
config['cas_server'],
config['cas_login_route'],
current_app.config['CAS_SERVER'],
current_app.config['CAS_LOGIN_ROUTE'],
url_for('cas.login', _external=True),
renew=True)
AuditCRUD.add_login_log(session.get("CAS_USERNAME"), False, ErrFormat.invalid_password)
current_app.logger.info("redirect to: {0}".format(redirect_url))
return redirect(redirect_url)
@blueprint.route('/api/cas/logout')
@blueprint.route('/api/sso/logout')
def logout():
"""
When the user accesses this route they are logged out.
"""
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
current_app.logger.info(config)
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
@@ -105,14 +82,12 @@ def logout():
"next" in session and session.pop("next")
redirect_url = create_cas_logout_url(
config['cas_server'],
config['cas_logout_route'],
current_app.config['CAS_SERVER'],
current_app.config['CAS_LOGOUT_ROUTE'],
url_for('cas.login', _external=True, next=request.referrer))
logout_user()
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
return redirect(redirect_url)
@@ -125,15 +100,14 @@ def validate(ticket):
and the validated username is saved in the session under the
key `CAS_USERNAME_SESSION_KEY`.
"""
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
current_app.logger.debug("validating token {0}".format(ticket))
cas_validate_url = create_cas_validate_url(
config['cas_validate_server'],
config['cas_validate_route'],
current_app.config['CAS_VALIDATE_SERVER'],
current_app.config['CAS_VALIDATE_ROUTE'],
url_for('cas.login', _external=True),
ticket)
@@ -141,35 +115,23 @@ def validate(ticket):
try:
response = urlopen(cas_validate_url).read()
ticket_id = _parse_tag(response, "cas:user")
strs = [s.strip() for s in ticket_id.split('|') if s.strip()]
ticketid = _parse_tag(response, "cas:user")
strs = [s.strip() for s in ticketid.split('|') if s.strip()]
username, is_valid = None, False
if len(strs) == 1:
username = strs[0]
is_valid = True
user_info = json.loads(_parse_tag(response, "cas:other"))
current_app.logger.info(user_info)
except ValueError:
current_app.logger.error("CAS returned unexpected result")
is_valid = False
return is_valid
if is_valid:
current_app.logger.debug("{}: {}".format(cas_username_session_key, username))
current_app.logger.debug("valid")
session[cas_username_session_key] = username
user = UserCache.get(username)
if user is None:
current_app.logger.info("create user: {}".format(username))
from api.lib.perm.acl.user import UserCRUD
soup = bs4.BeautifulSoup(response)
cas_user_map = config.get('cas_user_map')
user_dict = dict()
for k in cas_user_map:
v = soup.find(cas_user_map[k]['tag'], cas_user_map[k].get('attrs', {}))
user_dict[k] = v and v.text or None
user_dict['password'] = uuid.uuid4().hex
if "email" not in user_dict:
user_dict['email'] = username
UserCRUD.add(**user_dict)
from api.lib.perm.acl.acl import ACLManager
user_info = ACLManager.get_user_info(username)
@@ -202,5 +164,4 @@ def _parse_tag(string, tag):
if soup.find(tag) is None:
return ''
return soup.find(tag).string.strip()

View File

@@ -81,9 +81,8 @@ class AttributeManager(object):
elif choice_other.get('script'):
try:
x = compile(choice_other['script'], '', "exec")
local_ns = {}
exec(x, {}, local_ns)
res = local_ns['ChoiceValue']().values() or []
exec(x)
res = locals()['ChoiceValue']().values() or []
return [[i, {}] for i in res]
except Exception as e:
current_app.logger.error("get choice values from script: {}".format(e))
@@ -337,6 +336,9 @@ class AttributeManager(object):
def update(self, _id, **kwargs):
attr = Attribute.get_by_id(_id) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_id)))
if not self._can_edit_attribute(attr):
return abort(403, ErrFormat.cannot_edit_attribute)
if kwargs.get("name"):
other = Attribute.get_by(name=kwargs['name'], first=True, to_dict=False)
if other and other.id != attr.id:
@@ -377,14 +379,6 @@ class AttributeManager(object):
kwargs.get('is_computed') and self.can_create_computed_attribute()
is_changed = False
for k in kwargs:
if kwargs[k] != getattr(attr, k, None):
is_changed = True
if is_changed and not self._can_edit_attribute(attr):
return abort(403, ErrFormat.cannot_edit_attribute)
attr.update(flush=True, filter_none=False, **kwargs)
if is_choice and choice_value:

View File

@@ -3,6 +3,11 @@ import datetime
import json
import os
from flask import abort
from flask import current_app
from flask_login import current_user
from sqlalchemy import func
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
from api.lib.cmdb.cache import CITypeAttributeCache
@@ -23,10 +28,6 @@ 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 current_app
from flask_login import current_user
from sqlalchemy import func
PWD = os.path.abspath(os.path.dirname(__file__))
@@ -35,10 +36,9 @@ def parse_plugin_script(script):
attributes = []
try:
x = compile(script, '', "exec")
local_ns = {}
exec(x, {}, local_ns)
unique_key = local_ns['AutoDiscovery']().unique_key
attrs = local_ns['AutoDiscovery']().attributes() or []
exec(x)
unique_key = locals()['AutoDiscovery']().unique_key
attrs = locals()['AutoDiscovery']().attributes() or []
except Exception as e:
return abort(400, str(e))
@@ -250,17 +250,20 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
current_app.logger.warning(e)
return abort(400, str(e))
@staticmethod
def _can_add(**kwargs):
def _can_add(self, **kwargs):
self.cls.get_by(type_id=kwargs['type_id'], adr_id=kwargs.get('adr_id') or None) and abort(
400, ErrFormat.ad_duplicate)
# self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
if kwargs.get('adr_id'):
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
# if not adr.is_plugin:
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
# if other:
# ci_type = CITypeCache.get(other.type_id)
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
if not adr.is_plugin:
other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
if other:
ci_type = CITypeCache.get(other.type_id)
return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs)

View File

@@ -45,8 +45,8 @@ from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import validate_permission
from api.lib.secrets.inner import InnerCrypt
from api.lib.secrets.vault import VaultClient
from api.lib.utils import Lock
from api.lib.utils import handle_arg_list
from api.lib.utils import Lock
from api.lib.webhook import webhook_request
from api.models.cmdb import AttributeHistory
from api.models.cmdb import AutoDiscoveryCI
@@ -182,9 +182,6 @@ class CIManager(object):
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
ci_type = CITypeCache.get(ci.type_id)
if not ci_type:
return res
res["ci_type"] = ci_type.name
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
@@ -514,20 +511,18 @@ class CIManager(object):
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs]
for attr in attrs:
value_table = TableMap(attr=attr).table
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
for attr_name in attr_names:
value_table = TableMap(attr_name=attr_name).table
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
item.delete(commit=False)
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
ci_relation_delete.apply_async(
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
item.delete(commit=False)
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
ci_relation_delete.apply_async(
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
item.delete(commit=False)
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
@@ -644,9 +639,6 @@ class CIManager(object):
_fields.append(str(attr.id))
filter_fields_sql = "WHERE A.attr_id in ({0})".format(",".join(_fields))
ci2pos = {int(_id): _pos for _pos, _id in enumerate(ci_ids)}
res = [None] * len(ci_ids)
ci_ids = ",".join(map(str, ci_ids))
if value_tables is None:
value_tables = ValueTypeMap.table_name.values()
@@ -657,6 +649,7 @@ class CIManager(object):
# current_app.logger.debug(query_sql)
cis = db.session.execute(query_sql).fetchall()
ci_set = set()
res = list()
ci_dict = dict()
unique_id2obj = dict()
excludes = excludes and set(excludes)
@@ -676,7 +669,7 @@ class CIManager(object):
ci_dict["unique"] = unique_id2obj[ci_type.unique_id] and unique_id2obj[ci_type.unique_id].name
ci_dict["unique_alias"] = unique_id2obj[ci_type.unique_id] and unique_id2obj[ci_type.unique_id].alias
ci_set.add(ci_id)
res[ci2pos[ci_id]] = ci_dict
res.append(ci_dict)
if ret_key == RetKey.NAME:
attr_key = attr_name
@@ -891,14 +884,12 @@ class CIRelationManager(object):
@classmethod
def get_ancestor_ids(cls, ci_ids, level=1):
level2ids = dict()
for _level in range(1, level + 1):
cis = db.session.query(CIRelation.first_ci_id, CIRelation.ancestor_ids).filter(
for _ in range(level):
cis = db.session.query(CIRelation.first_ci_id).filter(
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
ci_ids = [i.first_ci_id for i in cis]
level2ids[_level + 1] = {int(i.ancestor_ids.split(',')[-1]) for i in cis if i.ancestor_ids}
return ci_ids, level2ids
return ci_ids
@staticmethod
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
@@ -925,14 +916,13 @@ class CIRelationManager(object):
return abort(400, ErrFormat.relation_constraint.format("1-N"))
@classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id)
existed = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
ancestor_ids=ancestor_ids,
to_dict=False,
first=True)
if existed is not None:
@@ -968,12 +958,11 @@ class CIRelationManager(object):
existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
relation_type_id=relation_type_id,
ancestor_ids=ancestor_ids)
relation_type_id=relation_type_id)
CIRelationHistoryManager().add(existed, OperateType.ADD)
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), queue=CMDB_QUEUE)
if more is not None:
existed.upadte(more=more)
@@ -997,56 +986,53 @@ class CIRelationManager(object):
his_manager = CIRelationHistoryManager()
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)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id), queue=CMDB_QUEUE)
return cr_id
@classmethod
def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
def delete_2(cls, first_ci_id, second_ci_id):
cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
ancestor_ids=ancestor_ids,
to_dict=False,
first=True)
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), queue=CMDB_QUEUE)
return cr and cls.delete(cr.id)
return cls.delete(cr.id)
@classmethod
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
def batch_update(cls, ci_ids, parents, children):
"""
only for many to one
:param ci_ids:
:param parents:
:param children:
:param ancestor_ids:
:return:
"""
if isinstance(parents, list):
for parent_id in parents:
for ci_id in ci_ids:
cls.add(parent_id, ci_id, ancestor_ids=ancestor_ids)
cls.add(parent_id, ci_id)
if isinstance(children, list):
for child_id in children:
for ci_id in ci_ids:
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
cls.add(ci_id, child_id)
@classmethod
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
def batch_delete(cls, ci_ids, parents):
"""
only for many to one
:param ci_ids:
:param parents:
:param ancestor_ids:
:return:
"""
if isinstance(parents, list):
for parent_id in parents:
for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
cls.delete_2(parent_id, ci_id)
class CITriggerManager(object):

View File

@@ -637,16 +637,6 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
if constraint == ConstraintEnum.Many2Many:
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
if other_c and other_c.child_id != c.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
if other_p and other_p.parent_id != p.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
existed = cls._get(p.id, c.id)
if existed is not None:
existed.update(relation_type_id=relation_type_id,
@@ -696,24 +686,6 @@ class CITypeRelationManager(object):
cls.delete(ctr.id)
@staticmethod
def get_level2constraint(root_id, level):
level = level + 1 if level == 1 else level
ci = CI.get_by_id(root_id)
if ci is None:
return dict()
root_id = ci.type_id
level2constraint = dict()
for lv in range(1, int(level) + 1):
for i in CITypeRelation.get_by(parent_id=root_id, to_dict=False):
if i.constraint == ConstraintEnum.Many2Many:
root_id = i.child_id
level2constraint[lv] = ConstraintEnum.Many2Many
break
return level2constraint
class CITypeAttributeGroupManager(object):
cls = CITypeAttributeGroup

View File

@@ -100,7 +100,6 @@ class AttributeDefaultValueEnum(BaseEnum):
CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}

View File

@@ -135,7 +135,7 @@ class AttributeHistoryManger(object):
from api.lib.cmdb.ci import CIManager
cis = CIManager().get_cis_by_ids(list(ci_ids),
unique_required=True)
cis = {i['_id']: i for i in cis if i}
cis = {i['_id']: i for i in cis}
return total, res, cis

View File

@@ -14,10 +14,7 @@ from api.lib.cmdb.attribute import AttributeManager
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.exception import AbortException
@@ -232,28 +229,14 @@ class PreferenceManager(object):
if not parents:
return
for _l in leaf:
_find_parent(_l)
for l in leaf:
_find_parent(l)
for node_id in node2show_types:
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
topo_flatten = list(toposort.toposort_flatten(topo))
level2constraint = {}
for i, _ in enumerate(topo_flatten[1:]):
ctr = CITypeRelation.get_by(
parent_id=topo_flatten[i], child_id=topo_flatten[i + 1], first=True, to_dict=False)
level2constraint[i + 1] = ctr and ctr.constraint
if leaf2show_types.get(topo_flatten[-1]):
ctr = CITypeRelation.get_by(
parent_id=topo_flatten[-1],
child_id=leaf2show_types[topo_flatten[-1]][0], first=True, to_dict=False)
level2constraint[len(topo_flatten)] = ctr and ctr.constraint
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
topo_flatten=topo_flatten,
level2constraint=level2constraint,
topo_flatten=list(toposort.toposort_flatten(topo)),
leaf=leaf,
leaf2show_types=leaf2show_types,
node2show_types=node2show_types,
@@ -355,29 +338,3 @@ class PreferenceManager(object):
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete()
@staticmethod
def can_edit_relation(parent_id, child_id):
views = PreferenceRelationView.get_by(to_dict=False)
for view in views:
has_m2m = False
last_node_id = None
for cr in view.cr_ids:
_rel = CITypeRelation.get_by(parent_id=cr['parent_id'], child_id=cr['child_id'],
first=True, to_dict=False)
if _rel and _rel.constraint == ConstraintEnum.Many2Many:
has_m2m = True
if parent_id == _rel.parent_id and child_id == _rel.child_id:
return False
if _rel:
last_node_id = _rel.child_id
if parent_id == last_node_id:
rels = CITypeRelation.get_by(parent_id=last_node_id, to_dict=False)
for rel in rels:
if rel.child_id == child_id and has_m2m:
return False
return True

View File

@@ -31,7 +31,6 @@ class ErrFormat(CommonErrFormat):
unique_key_required = "主键字段 {} 缺失"
ci_is_already_existed = "CI 已经存在!"
relation_constraint = "关系约束: {}, 校验失败 "
m2m_relation_constraint = "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
relation_not_found = "CI关系: {} 不存在"
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"

View File

@@ -9,7 +9,6 @@ import time
from flask import current_app
from flask_login import current_user
from jinja2 import Template
from sqlalchemy import text
from api.extensions import db
from api.lib.cmdb.cache import AttributeCache
@@ -313,7 +312,7 @@ class Search(object):
start = time.time()
execute = db.session.execute
# current_app.logger.debug(v_query_sql)
res = execute(text(v_query_sql)).fetchall()
res = execute(v_query_sql).fetchall()
end_time = time.time()
current_app.logger.debug("query ci ids time is: {0}".format(end_time - start))
@@ -526,7 +525,7 @@ class Search(object):
if k:
table_name = TableMap(attr=attr).table_name
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
result = db.session.execute(text(query_sql)).fetchall()
result = db.session.execute(query_sql).fetchall()
facet[k] = result
facet_result = dict()

View File

@@ -1,4 +1,6 @@
# -*- coding:utf-8 -*-
import json
from collections import Counter
@@ -8,14 +10,11 @@ from flask import current_app
from api.extensions import rd
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
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.es.search import Search as SearchFromES
from api.models.cmdb import CI
from api.models.cmdb import CIRelation
class Search(object):
@@ -27,8 +26,7 @@ class Search(object):
page=1,
count=None,
sort=None,
reverse=False,
ancestor_ids=None):
reverse=False):
self.orig_query = query
self.fl = fl
self.facet_field = facet_field
@@ -40,81 +38,25 @@ class Search(object):
self.level = level or 0
self.reverse = reverse
self.level2constraint = CITypeRelationManager.get_level2constraint(
root_id[0] if root_id and isinstance(root_id, list) else root_id,
level[0] if isinstance(level, list) and level else level)
self.ancestor_ids = ancestor_ids
self.has_m2m = False
if self.ancestor_ids:
self.has_m2m = True
else:
level = level[0] if isinstance(level, list) and level else level
for _l, c in self.level2constraint.items():
if _l < int(level) and c == ConstraintEnum.Many2Many:
self.has_m2m = True
def _get_ids(self, ids):
if self.level[-1] == 1 and len(ids) == 1:
if self.ancestor_ids is None:
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
else:
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
ancestor_ids=self.ancestor_ids,
to_dict=False)}
return list(seconds)
def _get_ids(self):
merge_ids = []
key = []
_tmp = []
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
for level in range(1, sorted(self.level)[-1] + 1):
if not self.has_m2m:
_tmp = map(lambda x: json.loads(x).keys(),
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
ids = [j for i in _tmp for j in i]
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
if not self.ancestor_ids:
if level == 1:
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
else:
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
else:
if level == 1:
key, prefix = ["{},{}".format(self.ancestor_ids, i) for i in ids], REDIS_PREFIX_CI_RELATION2
else:
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
if not key:
return []
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
_tmp = list(map(lambda x: list(json.loads(x).keys()),
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
ids = [j for i in _tmp for j in i]
if level in self.level:
merge_ids.extend(ids)
return merge_ids
def _get_reverse_ids(self, ids):
def _get_reverse_ids(self):
merge_ids = []
level2ids = {}
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
for level in range(1, sorted(self.level)[-1] + 1):
ids, _level2ids = CIRelationManager.get_ancestor_ids(ids, 1)
if _level2ids.get(2):
level2ids[level + 1] = _level2ids[2]
ids = CIRelationManager.get_ancestor_ids(ids, 1)
if level in self.level:
if level in level2ids and level2ids[level]:
merge_ids.extend(set(ids) & set(level2ids[level]))
else:
merge_ids.extend(ids)
merge_ids.extend(ids)
return merge_ids
@@ -122,7 +64,7 @@ class Search(object):
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]
merge_ids = self._get_ids(ids) if not self.reverse else self._get_reverse_ids(ids)
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
if not self.orig_query or ("_type:" not in self.orig_query
and "type_id:" not in self.orig_query
@@ -134,11 +76,11 @@ class Search(object):
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
else:
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
type_ids = set(type_ids)
type_ids = list(set(type_ids))
if self.orig_query:
self.orig_query = "_type:({0}),{1}".format(";".join(map(str, type_ids)), self.orig_query)
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
else:
self.orig_query = "_type:({0})".format(";".join(map(str, type_ids)))
self.orig_query = "_type:({0})".format(";".join(list(map(str, type_ids))))
if not merge_ids:
# cis, counter, total, self.page, numfound, facet_
@@ -163,65 +105,35 @@ class Search(object):
def statistics(self, type_ids):
self.level = int(self.level)
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
_tmp = []
level2ids = {}
for lv in range(1, self.level + 1):
level2ids[lv] = []
if lv == 1:
if not self.has_m2m:
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
if not self.ancestor_ids:
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
prefix = REDIS_PREFIX_CI_RELATION2
level2ids[lv] = [[i] for i in key]
if not key:
_tmp = []
continue
if type_ids and lv == self.level:
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
for lv in range(0, self.level):
if not lv:
if type_ids and lv == self.level - 1:
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
(map(lambda x: list(json.loads(x).items()),
[i or '{}' for i in rd.get(key, prefix) or []]))))
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))))
else:
_tmp = list(map(lambda x: list(json.loads(x).items()),
[i or '{}' for i in rd.get(key, prefix) or []]))
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
else:
for idx, item in enumerate(_tmp):
if item:
if not self.has_m2m:
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
if type_ids and lv == self.level - 1:
__tmp = list(
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
if type_id in type_ids],
filter(lambda x: x is not None,
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
else:
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
level2ids[lv].append(key)
if key:
if type_ids and lv == self.level:
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
if type_id in type_ids],
filter(lambda x: x is not None,
rd.get(key, prefix) or []))
else:
__tmp = map(lambda x: list(json.loads(x).items()),
filter(lambda x: x is not None,
rd.get(key, prefix) or []))
else:
__tmp = []
__tmp = list(map(lambda x: list(json.loads(x).items()),
filter(lambda x: x is not None,
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
_tmp[idx] = [j for i in __tmp for j in i]
else:
_tmp[idx] = []
level2ids[lv].append([])
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}

View File

@@ -12,7 +12,7 @@ import api.models.cmdb as model
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import ValueTypeEnum
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d')
TIME_RE = re.compile(r"^20|21|22|23|[0-1]\d:[0-5]\d:[0-5]\d$")
def string2int(x):

View File

@@ -80,22 +80,20 @@ class ACLManager(object):
return role.to_dict()
@staticmethod
def delete_role(_id):
def delete_role(_id, payload):
RoleCRUD.delete_role(_id)
return dict(rid=_id)
def get_user_info(self, username):
from api.lib.perm.acl.acl import ACLManager as ACL
user_info = ACL().get_user_info(username, self.app_name)
result = dict(
name=user_info.get('nickname') or username,
username=user_info.get('username') or username,
email=user_info.get('email'),
uid=user_info.get('uid'),
rid=user_info.get('rid'),
role=dict(permissions=user_info.get('parents')),
avatar=user_info.get('avatar')
)
result = dict(name=user_info.get('nickname') or username,
username=user_info.get('username') or username,
email=user_info.get('email'),
uid=user_info.get('uid'),
rid=user_info.get('rid'),
role=dict(permissions=user_info.get('parents')),
avatar=user_info.get('avatar'))
return result

View File

@@ -1,24 +1,14 @@
import copy
import json
from flask import abort, current_app
from ldap3 import Connection
from ldap3 import Server
from ldap3.core.exceptions import LDAPBindError, LDAPSocketOpenError
from ldap3 import AUTO_BIND_NO_TLS
from flask import abort
from api.extensions import db
from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import CommonData
from api.lib.utils import AESCrypto
from api.lib.common_setting.const import AuthCommonConfig, AuthenticateType, AuthCommonConfigAutoRedirect, TestType
class CommonDataCRUD(object):
@staticmethod
def get_data_by_type(data_type):
CommonDataCRUD.check_auth_type(data_type)
return CommonData.get_by(data_type=data_type)
@staticmethod
@@ -28,8 +18,6 @@ class CommonDataCRUD(object):
@staticmethod
def create_new_data(data_type, **kwargs):
try:
CommonDataCRUD.check_auth_type(data_type)
return CommonData.create(data_type=data_type, **kwargs)
except Exception as e:
db.session.rollback()
@@ -41,7 +29,6 @@ class CommonDataCRUD(object):
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
try:
CommonDataCRUD.check_auth_type(existed.data_type)
return existed.update(**kwargs)
except Exception as e:
db.session.rollback()
@@ -53,230 +40,7 @@ class CommonDataCRUD(object):
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
try:
CommonDataCRUD.check_auth_type(existed.data_type)
existed.soft_delete()
except Exception as e:
db.session.rollback()
abort(400, str(e))
@staticmethod
def check_auth_type(data_type):
if data_type in list(AuthenticateType.all()) + [AuthCommonConfig]:
abort(400, ErrFormat.common_data_not_support_auth_type.format(data_type))
@staticmethod
def set_auth_auto_redirect_enable(_value: int):
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig, to_dict=False)
if not existed:
CommonDataCRUD.create_new_data(AuthCommonConfig, data={AuthCommonConfigAutoRedirect: _value})
else:
data = existed.data
data = copy.deepcopy(existed.data) if data else {}
data[AuthCommonConfigAutoRedirect] = _value
CommonDataCRUD.update_data(existed.id, data=data)
return True
@staticmethod
def get_auth_auto_redirect_enable():
existed = CommonData.get_by(first=True, data_type=AuthCommonConfig)
if not existed:
return 0
data = existed.get('data', {})
if not data:
return 0
return data.get(AuthCommonConfigAutoRedirect, 0)
class AuthenticateDataCRUD(object):
common_type_list = [AuthCommonConfig]
def __init__(self, _type):
self._type = _type
self.record = None
self.decrypt_data = {}
def get_support_type_list(self):
return list(AuthenticateType.all()) + self.common_type_list
def get(self):
if not self.decrypt_data:
self.decrypt_data = self.get_decrypt_data()
return self.decrypt_data
def get_by_key(self, _key):
if not self.decrypt_data:
self.decrypt_data = self.get_decrypt_data()
return self.decrypt_data.get(_key, None)
def get_record(self, to_dict=False) -> CommonData:
return CommonData.get_by(first=True, data_type=self._type, to_dict=to_dict)
def get_record_with_decrypt(self) -> dict:
record = CommonData.get_by(first=True, data_type=self._type, to_dict=True)
if not record:
return {}
data = self.get_decrypt_dict(record.get('data', ''))
record['data'] = data
return record
def get_decrypt_dict(self, data):
decrypt_str = self.decrypt(data)
try:
return json.loads(decrypt_str)
except Exception as e:
abort(400, str(e))
def get_decrypt_data(self) -> dict:
self.record = self.get_record()
if not self.record:
return self.get_from_config()
return self.get_decrypt_dict(self.record.data)
def get_from_config(self):
return current_app.config.get(self._type, {})
def check_by_type(self) -> None:
existed = self.get_record()
if existed:
abort(400, ErrFormat.common_data_already_existed.format(self._type))
def create(self, data) -> CommonData:
self.check_by_type()
encrypt = data.pop('encrypt', None)
if encrypt is False:
return CommonData.create(data_type=self._type, data=data)
encrypted_data = self.encrypt(data)
try:
return CommonData.create(data_type=self._type, data=encrypted_data)
except Exception as e:
db.session.rollback()
abort(400, str(e))
def update_by_record(self, record, data) -> CommonData:
encrypt = data.pop('encrypt', None)
if encrypt is False:
return record.update(data=data)
encrypted_data = self.encrypt(data)
try:
return record.update(data=encrypted_data)
except Exception as e:
db.session.rollback()
abort(400, str(e))
def update(self, _id, data) -> CommonData:
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
return self.update_by_record(existed, data)
@staticmethod
def delete(_id) -> None:
existed = CommonData.get_by(first=True, to_dict=False, id=_id)
if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id))
try:
existed.soft_delete()
except Exception as e:
db.session.rollback()
abort(400, str(e))
@staticmethod
def encrypt(data) -> str:
if type(data) is dict:
try:
data = json.dumps(data)
except Exception as e:
abort(400, str(e))
return AESCrypto().encrypt(data)
@staticmethod
def decrypt(data) -> str:
return AESCrypto().decrypt(data)
@staticmethod
def get_enable_list():
all_records = CommonData.query.filter(
CommonData.data_type.in_(AuthenticateType.all()),
CommonData.deleted == 0
).all()
enable_list = []
for auth_type in AuthenticateType.all():
record = list(filter(lambda x: x.data_type == auth_type, all_records))
if not record:
config = current_app.config.get(auth_type, None)
if not config:
continue
if config.get('enable', False):
enable_list.append(dict(
auth_type=auth_type,
))
continue
try:
decrypt_data = json.loads(AuthenticateDataCRUD.decrypt(record[0].data))
except Exception as e:
current_app.logger.error(e)
continue
if decrypt_data.get('enable', 0) == 1:
enable_list.append(dict(
auth_type=auth_type,
))
auth_auto_redirect = CommonDataCRUD.get_auth_auto_redirect_enable()
return dict(
enable_list=enable_list,
auth_auto_redirect=auth_auto_redirect,
)
def test(self, test_type, data):
type_lower = self._type.lower()
func_name = f'test_{type_lower}'
if hasattr(self, func_name):
try:
return getattr(self, f'test_{type_lower}')(test_type, data)
except Exception as e:
abort(400, str(e))
abort(400, ErrFormat.not_support_test.format(self._type))
@staticmethod
def test_ldap(test_type, data):
ldap_server = data.get('ldap_server')
ldap_user_dn = data.get('ldap_user_dn', '{}')
server = Server(ldap_server, connect_timeout=2)
if not server.check_availability():
raise Exception(ErrFormat.ldap_server_connect_not_available)
else:
if test_type == TestType.Connect:
return True
username = data.get('username', None)
if not username:
raise Exception(ErrFormat.ldap_test_username_required)
user = ldap_user_dn.format(username)
password = data.get('password', None)
try:
Connection(server, user=user, password=password, auto_bind=AUTO_BIND_NO_TLS)
except LDAPBindError:
ldap_domain = data.get('ldap_domain')
user_with_domain = f"{username}@{ldap_domain}"
try:
Connection(server, user=user_with_domain, password=password, auto_bind=AUTO_BIND_NO_TLS)
except Exception as e:
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
except LDAPSocketOpenError:
raise Exception(ErrFormat.ldap_server_connect_timeout)
except Exception as e:
raise Exception(ErrFormat.ldap_test_unknown_error.format(str(e)))
return True

View File

@@ -1,6 +1,4 @@
# -*- coding:utf-8 -*-
from urllib.parse import urlparse
from api.extensions import cache
from api.models.common_setting import CompanyInfo
@@ -13,7 +11,6 @@ class CompanyInfoCRUD(object):
@staticmethod
def create(**kwargs):
CompanyInfoCRUD.check_data(**kwargs)
res = CompanyInfo.create(**kwargs)
CompanyInfoCache.refresh(res.info)
return res
@@ -25,26 +22,10 @@ class CompanyInfoCRUD(object):
if not existed:
existed = CompanyInfoCRUD.create(**kwargs)
else:
CompanyInfoCRUD.check_data(**kwargs)
existed = existed.update(**kwargs)
CompanyInfoCache.refresh(existed.info)
return existed
@staticmethod
def check_data(**kwargs):
info = kwargs.get('info', {})
info['messenger'] = CompanyInfoCRUD.check_messenger(info.get('messenger', None))
kwargs['info'] = info
@staticmethod
def check_messenger(messenger):
if not messenger:
return messenger
parsed_url = urlparse(messenger)
return f"{parsed_url.scheme}://{parsed_url.netloc}"
class CompanyInfoCache(object):
key = 'CompanyInfoCache::'

View File

@@ -19,19 +19,3 @@ BotNameMap = {
'feishuApp': 'feishuBot',
'dingdingApp': 'dingdingBot',
}
class AuthenticateType(BaseEnum):
CAS = 'CAS'
OAUTH2 = 'OAUTH2'
OIDC = 'OIDC'
LDAP = 'LDAP'
AuthCommonConfig = 'AuthCommonConfig'
AuthCommonConfigAutoRedirect = 'auto_redirect'
class TestType(BaseEnum):
Connect = 'connect'
Login = 'login'

View File

@@ -1,6 +1,6 @@
# -*- coding:utf-8 -*-
from flask import abort, current_app
from flask import abort
from treelib import Tree
from wtforms import Form
from wtforms import IntegerField
@@ -9,7 +9,6 @@ from wtforms import validators
from api.extensions import db
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.acl import ACLManager
from api.lib.perm.acl.role import RoleCRUD
from api.models.common_setting import Department, Employee
@@ -153,10 +152,6 @@ class DepartmentForm(Form):
class DepartmentCRUD(object):
@staticmethod
def get_department_by_id(d_id, to_dict=True):
return Department.get_by(first=True, department_id=d_id, to_dict=to_dict)
@staticmethod
def add(**kwargs):
DepartmentCRUD.check_department_name_unique(kwargs['department_name'])
@@ -191,11 +186,10 @@ class DepartmentCRUD(object):
filter(lambda d: d['department_id'] == department_parent_id, allow_p_d_id_list))
if len(target) == 0:
try:
dep = Department.get_by(
d = Department.get_by(
first=True, to_dict=False, department_id=department_parent_id)
name = dep.department_name if dep else ErrFormat.department_id_not_found.format(department_parent_id)
name = d.department_name if d else ErrFormat.department_id_not_found.format(department_parent_id)
except Exception as e:
current_app.logger.error(str(e))
name = ErrFormat.department_id_not_found.format(department_parent_id)
abort(400, ErrFormat.cannot_to_be_parent_department.format(name))
@@ -259,7 +253,7 @@ class DepartmentCRUD(object):
try:
RoleCRUD.delete_role(existed.acl_rid)
except Exception as e:
current_app.logger.error(str(e))
pass
return existed.soft_delete()
@@ -274,7 +268,7 @@ class DepartmentCRUD(object):
try:
tree.remove_subtree(department_id)
except Exception as e:
current_app.logger.error(str(e))
pass
[allow_d_id_list.append({'department_id': int(n.identifier), 'department_name': n.tag}) for n in
tree.all_nodes()]
@@ -396,125 +390,6 @@ class DepartmentCRUD(object):
[id_list.append(int(n.identifier))
for n in tmp_tree.all_nodes()]
except Exception as e:
current_app.logger.error(str(e))
pass
return id_list
class EditDepartmentInACL(object):
@staticmethod
def add_department_to_acl(department_id, op_uid):
db_department = DepartmentCRUD.get_department_by_id(department_id, to_dict=False)
if not db_department:
return
from api.models.acl import Role
role = Role.get_by(first=True, name=db_department.department_name, app_id=None)
acl = ACLManager('acl', str(op_uid))
if role is None:
payload = {
'app_id': 'acl',
'name': db_department.department_name,
}
role = acl.create_role(payload)
acl_rid = role.get('id') if role else 0
db_department.update(
acl_rid=acl_rid
)
info = f"add_department_to_acl, acl_rid: {acl_rid}"
current_app.logger.info(info)
return info
@staticmethod
def delete_department_from_acl(department_rids, op_uid):
acl = ACLManager('acl', str(op_uid))
result = []
for rid in department_rids:
try:
acl.delete_role(rid)
except Exception as e:
result.append(f"delete_department_in_acl, rid: {rid}, error: {e}")
continue
return result
@staticmethod
def edit_department_name_in_acl(d_rid: int, d_name: str, op_uid: int):
acl = ACLManager('acl', str(op_uid))
payload = {
'name': d_name
}
try:
acl.edit_role(d_rid, payload)
except Exception as e:
return f"edit_department_name_in_acl, rid: {d_rid}, error: {e}"
return f"edit_department_name_in_acl, rid: {d_rid}, success"
@staticmethod
def edit_employee_department_in_acl(e_list: list, new_d_id: int, op_uid: int):
result = []
new_department = DepartmentCRUD.get_department_by_id(new_d_id, False)
if not new_department:
result.append(f"{new_d_id} new_department is None")
return result
from api.models.acl import Role
new_role = Role.get_by(first=True, name=new_department.department_name, app_id=None)
new_d_rid_in_acl = new_role.get('id') if new_role else 0
if new_d_rid_in_acl == 0:
return
if new_d_rid_in_acl != new_department.acl_rid:
new_department.update(
acl_rid=new_d_rid_in_acl
)
new_department_acl_rid = new_department.acl_rid if new_d_rid_in_acl == new_department.acl_rid else \
new_d_rid_in_acl
acl = ACLManager('acl', str(op_uid))
for employee in e_list:
old_department = DepartmentCRUD.get_department_by_id(employee.get('department_id'), False)
if not old_department:
continue
employee_acl_rid = employee.get('e_acl_rid')
if employee_acl_rid == 0:
result.append(f"employee_acl_rid == 0")
continue
old_role = Role.get_by(first=True, name=old_department.department_name, app_id=None)
old_d_rid_in_acl = old_role.get('id') if old_role else 0
if old_d_rid_in_acl == 0:
return
if old_d_rid_in_acl != old_department.acl_rid:
old_department.update(
acl_rid=old_d_rid_in_acl
)
d_acl_rid = old_department.acl_rid if old_d_rid_in_acl == old_department.acl_rid else old_d_rid_in_acl
payload = {
'app_id': 'acl',
'parent_id': d_acl_rid,
}
try:
acl.remove_user_from_role(employee_acl_rid, payload)
except Exception as e:
result.append(
f"remove_user_from_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
payload = {
'app_id': 'acl',
'child_ids': [employee_acl_rid],
}
try:
acl.add_user_to_role(new_department_acl_rid, payload)
except Exception as e:
result.append(
f"add_user_to_role employee_acl_rid: {employee_acl_rid}, parent_id: {d_acl_rid}, err: {e}")
return result

View File

@@ -178,7 +178,7 @@ class EmployeeCRUD(object):
def edit_employee_by_uid(_uid, **kwargs):
existed = EmployeeCRUD.get_employee_by_uid(_uid)
try:
edit_acl_user(_uid, **kwargs)
user = edit_acl_user(_uid, **kwargs)
for column in employee_pop_columns:
if kwargs.get(column):
@@ -190,9 +190,9 @@ class EmployeeCRUD(object):
@staticmethod
def change_password_by_uid(_uid, password):
EmployeeCRUD.get_employee_by_uid(_uid)
existed = EmployeeCRUD.get_employee_by_uid(_uid)
try:
edit_acl_user(_uid, password=password)
user = edit_acl_user(_uid, password=password)
except Exception as e:
return abort(400, str(e))
@@ -359,11 +359,9 @@ class EmployeeCRUD(object):
if value and column == "last_login":
try:
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except Exception as e:
err = f"{ErrFormat.datetime_format_error.format(column)}: {str(e)}"
abort(400, err)
return value
abort(400, ErrFormat.datetime_format_error.format(column))
@staticmethod
def get_attr_by_column(column):
@@ -384,7 +382,7 @@ class EmployeeCRUD(object):
relation = condition.get("relation", None)
value = condition.get("value", None)
value = EmployeeCRUD.check_condition(column, operator, value, relation)
EmployeeCRUD.check_condition(column, operator, value, relation)
a, o = EmployeeCRUD.get_expr_by_condition(
column, operator, value, relation)
and_list += a
@@ -563,130 +561,10 @@ class EmployeeCRUD(object):
for column in direct_columns:
tmp[column] = d.get(column, '')
notice_info = d.get('notice_info', {})
notice_info = copy.deepcopy(notice_info) if notice_info else {}
tmp.update(**notice_info)
results.append(tmp)
return results
@staticmethod
def import_employee(employee_list):
res = CreateEmployee().batch_create(employee_list)
return res
@staticmethod
def batch_edit_employee_department(employee_id_list, column_value):
err_list = []
employee_list = []
for _id in employee_id_list:
try:
existed = EmployeeCRUD.get_employee_by_id(_id)
employee = dict(
e_acl_rid=existed.acl_rid,
department_id=existed.department_id
)
employee_list.append(employee)
existed.update(department_id=column_value)
except Exception as e:
err_list.append({
'employee_id': _id,
'err': str(e),
})
from api.lib.common_setting.department import EditDepartmentInACL
EditDepartmentInACL.edit_employee_department_in_acl(
employee_list, column_value, current_user.uid
)
return err_list
@staticmethod
def batch_edit_password_or_block_column(column_name, employee_id_list, column_value, is_acl=False):
if column_name == 'block':
err_list = []
success_list = []
for _id in employee_id_list:
try:
employee = EmployeeCRUD.edit_employee_block_column(
_id, is_acl, **{column_name: column_value})
success_list.append(employee)
except Exception as e:
err_list.append({
'employee_id': _id,
'err': str(e),
})
return err_list
else:
return EmployeeCRUD.batch_edit_column(column_name, employee_id_list, column_value, is_acl)
@staticmethod
def batch_edit_column(column_name, employee_id_list, column_value, is_acl=False):
err_list = []
for _id in employee_id_list:
try:
EmployeeCRUD.edit_employee_single_column(
_id, is_acl, **{column_name: column_value})
except Exception as e:
err_list.append({
'employee_id': _id,
'err': str(e),
})
return err_list
@staticmethod
def edit_employee_single_column(_id, is_acl=False, **kwargs):
existed = EmployeeCRUD.get_employee_by_id(_id)
if 'direct_supervisor_id' in kwargs.keys():
if kwargs['direct_supervisor_id'] == existed.direct_supervisor_id:
raise Exception(ErrFormat.direct_supervisor_is_not_self)
if is_acl:
return edit_acl_user(existed.acl_uid, **kwargs)
try:
for column in employee_pop_columns:
if kwargs.get(column):
kwargs.pop(column)
return existed.update(**kwargs)
except Exception as e:
return abort(400, str(e))
@staticmethod
def edit_employee_block_column(_id, is_acl=False, **kwargs):
existed = EmployeeCRUD.get_employee_by_id(_id)
value = get_block_value(kwargs.get('block'))
if value is True:
check_department_director_id_or_direct_supervisor_id(_id)
value = 1
else:
value = 0
if is_acl:
kwargs['block'] = value
edit_acl_user(existed.acl_uid, **kwargs)
existed.update(block=value)
data = existed.to_dict()
return data
@staticmethod
def batch_employee(column_name, column_value, employee_id_list):
if column_value is None:
abort(400, ErrFormat.value_is_required)
if column_name in ['password', 'block']:
return EmployeeCRUD.batch_edit_password_or_block_column(column_name, employee_id_list, column_value, True)
elif column_name in ['department_id']:
return EmployeeCRUD.batch_edit_employee_department(employee_id_list, column_value)
elif column_name in [
'direct_supervisor_id', 'position_name'
]:
return EmployeeCRUD.batch_edit_column(column_name, employee_id_list, column_value, False)
else:
abort(400, ErrFormat.column_name_not_support)
def get_user_map(key='uid', acl=None):
"""
@@ -727,7 +605,6 @@ class CreateEmployee(object):
try:
existed = self.check_acl_user(user_data)
if not existed:
user_data['add_from'] = 'common'
return self.acl.create_user(user_data)
return existed
except Exception as e:
@@ -764,8 +641,7 @@ class CreateEmployee(object):
**kwargs
)
@staticmethod
def get_department_by_name(d_name):
def get_department_by_name(self, d_name):
return Department.get_by(first=True, department_name=d_name)
def get_end_department_id(self, department_name_list, department_name_map):

View File

@@ -8,9 +8,6 @@ class ErrFormat(CommonErrFormat):
no_file_part = "没有文件部分"
file_is_required = "文件是必须的"
file_not_found = "文件不存在"
file_type_not_allowed = "文件类型不允许"
upload_failed = "上传失败: {}"
direct_supervisor_is_not_self = "直属上级不能是自己"
parent_department_is_not_self = "上级部门不能是自己"
@@ -59,7 +56,6 @@ class ErrFormat(CommonErrFormat):
email_send_timeout = "邮件发送超时"
common_data_not_found = "ID {} 找不到记录"
common_data_already_existed = "{} 已存在"
notice_platform_existed = "{} 已存在"
notice_not_existed = "{} 配置项不存在"
notice_please_config_messenger_first = "请先配置 messenger"
@@ -67,11 +63,3 @@ class ErrFormat(CommonErrFormat):
notice_bind_failed = "绑定失败: {}"
notice_bind_success = "绑定成功"
notice_remove_bind_success = "解绑成功"
not_support_test = "不支持的测试类型: {}"
not_support_auth_type = "不支持的认证类型: {}"
ldap_server_connect_timeout = "LDAP服务器连接超时"
ldap_server_connect_not_available = "LDAP服务器连接不可用"
ldap_test_unknown_error = "LDAP测试未知错误: {}"
common_data_not_support_auth_type = "通用数据不支持auth类型: {}"
ldap_test_username_required = "LDAP测试用户名必填"

View File

@@ -1,13 +1,6 @@
import uuid
import os
from io import BytesIO
from flask import abort, current_app
import lz4.frame
from api.lib.common_setting.utils import get_cur_time_str
from api.models.common_setting import CommonFile
from api.lib.common_setting.resp_format import ErrFormat
def allowed_file(filename, allowed_extensions):
@@ -21,48 +14,3 @@ def generate_new_file_name(name):
cur_str = get_cur_time_str('_')
return f"{prev_name}_{cur_str}_{uid}.{ext}"
class CommonFileCRUD:
@staticmethod
def add_file(**kwargs):
return CommonFile.create(**kwargs)
@staticmethod
def get_file(file_name):
existed = CommonFile.get_by(file_name=file_name, first=True, to_dict=False)
if not existed:
abort(400, ErrFormat.file_not_found)
uncompressed_data = lz4.frame.decompress(existed.binary)
return BytesIO(uncompressed_data)
@staticmethod
def sync_file_to_db():
for p in ['UPLOAD_DIRECTORY_FULL']:
upload_path = current_app.config.get(p, None)
if not upload_path:
continue
for root, dirs, files in os.walk(upload_path):
for file in files:
file_path = os.path.join(root, file)
if not os.path.isfile(file_path):
continue
existed = CommonFile.get_by(file_name=file, first=True, to_dict=False)
if existed:
continue
with open(file_path, 'rb') as f:
data = f.read()
compressed_data = lz4.frame.compress(data)
try:
CommonFileCRUD.add_file(
origin_name=file,
file_name=file,
binary=compressed_data
)
current_app.logger.info(f'sync file {file} to db')
except Exception as e:
current_app.logger.error(f'sync file {file} to db error: {e}')

View File

@@ -10,18 +10,14 @@ from api.lib.exception import CommitException
class FormatMixin(object):
def to_dict(self):
res = dict()
for k in getattr(self, "__mapper__").c.keys():
if k in {'password', '_password', 'secret', '_secret'}:
continue
res = dict([(k, getattr(self, k) if not isinstance(
getattr(self, k), (datetime.datetime, datetime.date, datetime.time)) else str(
getattr(self, k))) for k in getattr(self, "__mapper__").c.keys()])
# FIXME: getattr(cls, "__table__").columns k.name
if k.startswith('_'):
k = k[1:]
if not isinstance(getattr(self, k), (datetime.datetime, datetime.date, datetime.time)):
res[k] = getattr(self, k)
else:
res[k] = str(getattr(self, k))
res.pop('password', None)
res.pop('_password', None)
res.pop('secret', None)
return res
@@ -94,7 +90,7 @@ class CRUDMixin(FormatMixin):
if any((isinstance(_id, six.string_types) and _id.isdigit(),
isinstance(_id, (six.integer_types, float))), ):
obj = getattr(cls, "query").get(int(_id))
if obj and not getattr(obj, 'deleted', False):
if obj and not obj.deleted:
return obj
@classmethod

View File

@@ -1,19 +1,14 @@
# -*- coding:utf-8 -*-
import datetime
import itertools
import json
from enum import Enum
from typing import List
from flask import has_request_context
from flask import request
from flask import has_request_context, request
from flask_login import current_user
from sqlalchemy import func
from api.extensions import db
from api.lib.perm.acl import AppCache
from api.models.acl import AuditLoginLog
from api.models.acl import AuditPermissionLog
from api.models.acl import AuditResourceLog
from api.models.acl import AuditRoleLog
@@ -288,27 +283,6 @@ class AuditCRUD(object):
return data
@staticmethod
def search_login(_, q=None, page=1, page_size=10, start=None, end=None):
query = db.session.query(AuditLoginLog)
if start:
query = query.filter(AuditLoginLog.login_at >= start)
if end:
query = query.filter(AuditLoginLog.login_at <= end)
if q:
query = query.filter(AuditLoginLog.username == q)
records = query.order_by(
AuditLoginLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
}
return data
@classmethod
def add_role_log(cls, app_id, operate_type: AuditOperateType,
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
@@ -374,24 +348,3 @@ class AuditCRUD(object):
AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
operate_type=operate_type.value,
origin=origin, current=current, extra=extra, source=source.value)
@classmethod
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
if _id is not None:
existed = AuditLoginLog.get_by_id(_id)
if existed is not None:
existed.update(logout_at=logout_at)
return
payload = dict(username=username,
is_ok=is_ok,
description=description,
logout_at=logout_at,
ip=request.headers.get('X-Real-IP') or request.remote_addr,
browser=request.headers.get('User-Agent'),
)
if logout_at is None:
payload['login_at'] = datetime.datetime.now()
return AuditLoginLog.create(**payload).id

View File

@@ -276,6 +276,7 @@ class ResourceCRUD(object):
from api.tasks.acl import apply_trigger
triggers = TriggerCRUD.match_triggers(app_id, r.name, r.resource_type_id, uid)
current_app.logger.info(triggers)
for trigger in triggers:
# auto trigger should be no uid
apply_trigger.apply_async(args=(trigger.id,),

View File

@@ -4,9 +4,6 @@ from api.lib.resp_format import CommonErrFormat
class ErrFormat(CommonErrFormat):
login_succeed = "登录成功"
ldap_connection_failed = "连接LDAP服务失败"
invalid_password = "密码验证失败"
auth_only_with_app_token_failed = "应用 Token验证失败"
session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
@@ -20,11 +17,11 @@ class ErrFormat(CommonErrFormat):
role_exists = "角色 {} 已经存在!"
global_role_not_found = "全局角色 {} 不存在!"
global_role_exists = "全局角色 {} 已经存在!"
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
resource_no_permission = "您没有资源: {}{} 权限"
admin_required = "需要管理员权限"
role_required = "需要角色: {}"
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
app_is_ready_existed = "应用 {} 已经存在"
app_not_found = "应用 {} 不存在!"

View File

@@ -41,7 +41,6 @@ class UserCRUD(object):
@classmethod
def add(cls, **kwargs):
add_from = kwargs.pop('add_from', None)
existed = User.get_by(username=kwargs['username'])
existed and abort(400, ErrFormat.user_exists.format(kwargs['username']))
@@ -63,11 +62,10 @@ class UserCRUD(object):
AuditCRUD.add_role_log(None, AuditOperateType.create,
AuditScope.user, user.uid, {}, user.to_dict(), {}, {}
)
if add_from != 'common':
from api.lib.common_setting.employee import EmployeeCRUD
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
payload['rid'] = role.id
EmployeeCRUD.add_employee_from_acl_created(**payload)
from api.lib.common_setting.employee import EmployeeCRUD
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
payload['rid'] = role.id
EmployeeCRUD.add_employee_from_acl_created(**payload)
return user

View File

@@ -93,9 +93,6 @@ def _auth_with_token():
def _auth_with_ip_white_list():
if request.url.endswith("acl/users/info"):
return False
ip = request.headers.get('X-Real-IP') or request.remote_addr
key = request.values.get('_key')
secret = request.values.get('_secret')

View File

@@ -1 +0,0 @@
# -*- coding:utf-8 -*-

View File

@@ -1,67 +0,0 @@
# -*- coding:utf-8 -*-
import uuid
from flask import abort
from flask import current_app
from flask import session
from ldap3 import ALL
from ldap3 import AUTO_BIND_NO_TLS
from ldap3 import Connection
from ldap3 import Server
from ldap3.core.exceptions import LDAPBindError
from ldap3.core.exceptions import LDAPCertificateError
from ldap3.core.exceptions import LDAPSocketOpenError
from api.lib.common_setting.common_data import AuthenticateDataCRUD
from api.lib.common_setting.const import AuthenticateType
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.resp_format import ErrFormat
from api.models.acl import User
def authenticate_with_ldap(username, password):
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
server = Server(config.get('LDAP').get('ldap_server'), get_info=ALL, connect_timeout=3)
if '@' in username:
email = username
who = config['LDAP'].get('ldap_user_dn').format(username.split('@')[0])
else:
who = config['LDAP'].get('ldap_user_dn').format(username)
email = "{}@{}".format(who, config['LDAP'].get('ldap_domain'))
username = username.split('@')[0]
user = User.query.get_by_username(username)
try:
if not password:
raise LDAPCertificateError
try:
conn = Connection(server, user=who, password=password, auto_bind=AUTO_BIND_NO_TLS)
except LDAPBindError:
conn = Connection(server,
user=f"{username}@{config['LDAP'].get('ldap_domain')}",
password=password,
auto_bind=AUTO_BIND_NO_TLS)
if conn.result['result'] != 0:
AuditCRUD.add_login_log(username, False, ErrFormat.invalid_password)
raise LDAPBindError
else:
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
session['LOGIN_ID'] = _id
if not user:
from api.lib.perm.acl.user import UserCRUD
user = UserCRUD.add(username=username, email=email, password=uuid.uuid4().hex)
return user, True
except LDAPBindError as e:
current_app.logger.info(e)
return user, False
except LDAPSocketOpenError as e:
current_app.logger.info(e)
return abort(403, ErrFormat.ldap_connection_failed)

View File

@@ -1,30 +0,0 @@
# -*- coding:utf-8 -*-
from flask import current_app
from . import routing
class OAuth2(object):
def __init__(self, app=None, url_prefix=None):
self._app = app
if app is not None:
self.init_app(app, url_prefix)
@staticmethod
def init_app(app, url_prefix=None):
# Configuration defaults
app.config.setdefault('OAUTH2_GRANT_TYPE', 'authorization_code')
app.config.setdefault('OAUTH2_RESPONSE_TYPE', 'code')
app.config.setdefault('OAUTH2_AFTER_LOGIN', '/')
app.config.setdefault('OIDC_GRANT_TYPE', 'authorization_code')
app.config.setdefault('OIDC_RESPONSE_TYPE', 'code')
app.config.setdefault('OIDC_AFTER_LOGIN', '/')
# Register Blueprint
app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
@property
def app(self):
return self._app or current_app

View File

@@ -1,139 +0,0 @@
# -*- coding:utf-8 -*-
import datetime
import secrets
import uuid
import requests
from flask import Blueprint
from flask import abort
from flask import current_app
from flask import redirect
from flask import request
from flask import session
from flask import url_for
from flask_login import login_user
from flask_login import logout_user
from six.moves.urllib.parse import urlencode
from six.moves.urllib.parse import urlparse
from api.lib.common_setting.common_data import AuthenticateDataCRUD
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.resp_format import ErrFormat
blueprint = Blueprint('oauth2', __name__)
@blueprint.route('/api/<string:auth_type>/login')
def login(auth_type):
config = AuthenticateDataCRUD(auth_type.upper()).get()
if request.values.get("next"):
session["next"] = request.values.get("next")
session[f'{auth_type}_state'] = secrets.token_urlsafe(16)
auth_type = auth_type.upper()
redirect_uri = "{}://{}{}".format(urlparse(request.referrer).scheme,
urlparse(request.referrer).netloc,
url_for('oauth2.callback', auth_type=auth_type.lower()))
qs = urlencode({
'client_id': config['client_id'],
'redirect_uri': redirect_uri,
'response_type': current_app.config[f'{auth_type}_RESPONSE_TYPE'],
'scope': ' '.join(config['scopes'] or []),
'state': session[f'{auth_type.lower()}_state'],
})
return redirect("{}?{}".format(config['authorize_url'].split('?')[0], qs))
@blueprint.route('/api/<string:auth_type>/callback')
def callback(auth_type):
auth_type = auth_type.upper()
config = AuthenticateDataCRUD(auth_type).get()
redirect_url = session.get("next") or config.get('after_login') or '/'
if request.values['state'] != session.get(f'{auth_type.lower()}_state'):
return abort(401, "state is invalid")
if 'code' not in request.values:
return abort(401, 'code is invalid')
response = requests.post(config['token_url'], data={
'client_id': config['client_id'],
'client_secret': config['client_secret'],
'code': request.values['code'],
'grant_type': current_app.config[f'{auth_type}_GRANT_TYPE'],
'redirect_uri': url_for('oauth2.callback', auth_type=auth_type.lower(), _external=True),
}, headers={'Accept': 'application/json'})
if response.status_code != 200:
current_app.logger.error(response.text)
return abort(401)
access_token = response.json().get('access_token')
if not access_token:
return abort(401)
response = requests.get(config['user_info']['url'], headers={
'Authorization': 'Bearer {}'.format(access_token),
'Accept': 'application/json',
})
if response.status_code != 200:
return abort(401)
res = response.json()
email = res.get(config['user_info']['email'])
username = res.get(config['user_info']['username'])
avatar = res.get(config['user_info'].get('avatar'))
user = UserCache.get(username)
if user is None:
current_app.logger.info("create user: {}".format(username))
from api.lib.perm.acl.user import UserCRUD
user_dict = dict(username=username, email=email, avatar=avatar)
user_dict['password'] = uuid.uuid4().hex
user = UserCRUD.add(**user_dict)
# log the user in
login_user(user)
from api.lib.perm.acl.acl import ACLManager
user_info = ACLManager.get_user_info(username)
session["acl"] = dict(uid=user_info.get("uid"),
avatar=user.avatar if user else user_info.get("avatar"),
userId=user_info.get("uid"),
rid=user_info.get("rid"),
userName=user_info.get("username"),
nickName=user_info.get("nickname") or user_info.get("username"),
parentRoles=user_info.get("parents"),
childRoles=user_info.get("children"),
roleName=user_info.get("role"))
session["uid"] = user_info.get("uid")
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
session['LOGIN_ID'] = _id
return redirect(redirect_url)
@blueprint.route('/api/<string:auth_type>/logout')
def logout(auth_type):
"acl" in session and session.pop("acl")
"uid" in session and session.pop("uid")
f'{auth_type}_state' in session and session.pop(f'{auth_type}_state')
"next" in session and session.pop("next")
redirect_url = url_for('oauth2.login', auth_type=auth_type, _external=True, next=request.referrer)
logout_user()
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
return redirect(redirect_url)

View File

@@ -38,6 +38,7 @@ def string_to_bytes(value):
byte_string = value
else:
byte_string = value.encode("utf-8")
return byte_string
@@ -64,8 +65,7 @@ class KeyManage:
self.backend = Backend(backend)
def init_app(self, app, backend=None):
if (sys.argv[0].endswith("gunicorn") or
(len(sys.argv) > 1 and sys.argv[1] in ("run", "cmdb-password-data-migrate"))):
if sys.argv[0].endswith("gunicorn") or (len(sys.argv) > 1 and sys.argv[1] == "run"):
self.trigger = app.config.get("INNER_TRIGGER_TOKEN")
if not self.trigger:
return
@@ -313,7 +313,7 @@ class KeyManage:
secrets_root_key = current_app.config.get("secrets_root_key")
msg, ok = self.is_valid_root_key(secrets_root_key)
if not ok:
return true
return {"message": msg, "status": "failed"}
status = self.backend.get(backend_seal_key)
return status == "block"

View File

@@ -12,9 +12,6 @@ from Crypto.Cipher import AES
from elasticsearch import Elasticsearch
from flask import current_app
from api.lib.secrets.inner import InnerCrypt
from api.lib.secrets.inner import KeyManage
class BaseEnum(object):
_ALL_ = set() # type: Set[str]
@@ -289,33 +286,3 @@ class AESCrypto(object):
text_decrypted = cipher.decrypt(encode_bytes)
return cls.unpad(text_decrypted).decode('utf8')
class Crypto(AESCrypto):
@classmethod
def encrypt(cls, data):
from api.lib.secrets.secrets import InnerKVManger
if not KeyManage(backend=InnerKVManger()).is_seal():
res, status = InnerCrypt().encrypt(data)
if status:
return res
return AESCrypto().encrypt(data)
@classmethod
def decrypt(cls, data):
from api.lib.secrets.secrets import InnerKVManger
if not KeyManage(backend=InnerKVManger()).is_seal():
try:
res, status = InnerCrypt().decrypt(data)
if status:
return res
except:
pass
try:
return AESCrypto().decrypt(data)
except:
return data

View File

@@ -5,18 +5,17 @@ import copy
import hashlib
from datetime import datetime
from ldap3 import Server, Connection, ALL
from ldap3.core.exceptions import LDAPBindError, LDAPCertificateError
from flask import current_app
from flask import session
from flask_sqlalchemy import BaseQuery
from api.extensions import db
from api.lib.database import CRUDModel
from api.lib.database import Model
from api.lib.database import Model2
from api.lib.database import SoftDeleteMixin
from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.const import OperateType
from api.lib.perm.acl.resp_format import ErrFormat
class App(Model):
@@ -29,26 +28,21 @@ class App(Model):
class UserQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(UserQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password):
from api.lib.perm.acl.audit import AuditCRUD
user = self.filter(db.or_(User.username == login,
User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
if user:
current_app.logger.info(user)
authenticated = user.check_password(password)
if authenticated:
_id = AuditCRUD.add_login_log(login, True, ErrFormat.login_succeed)
session['LOGIN_ID'] = _id
else:
AuditCRUD.add_login_log(login, False, ErrFormat.invalid_password)
from api.tasks.acl import op_record
op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
else:
authenticated = False
AuditCRUD.add_login_log(login, False, ErrFormat.user_not_found.format(login))
current_app.logger.info(("login", login, user, authenticated))
return user, authenticated
def authenticate_with_key(self, key, secret, args, path):
@@ -63,6 +57,38 @@ class UserQuery(BaseQuery):
return user, authenticated
def authenticate_with_ldap(self, username, password):
server = Server(current_app.config.get('LDAP_SERVER'), get_info=ALL)
if '@' in username:
email = username
who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
else:
who = current_app.config.get('LDAP_USER_DN').format(username)
email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))
username = username.split('@')[0]
user = self.get_by_username(username)
try:
if not password:
raise LDAPCertificateError
conn = Connection(server, user=who, password=password)
conn.bind()
if conn.result['result'] != 0:
raise LDAPBindError
conn.unbind()
if not user:
from api.lib.perm.acl.user import UserCRUD
user = UserCRUD.add(username=username, email=email)
from api.tasks.acl import op_record
op_record.apply_async(args=(None, username, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
return user, True
except LDAPBindError:
return user, False
def search(self, key):
query = self.filter(db.or_(User.email == key,
User.nickname.ilike('%' + key + '%'),
@@ -112,7 +138,6 @@ class User(CRUDModel, SoftDeleteMixin):
wx_id = db.Column(db.String(32))
employee_id = db.Column(db.String(16), index=True)
avatar = db.Column(db.String(128))
# apps = db.Column(db.JSON)
def __str__(self):
@@ -143,6 +168,8 @@ class User(CRUDModel, SoftDeleteMixin):
class RoleQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(RoleQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password):
role = self.filter(Role.name == login).first()
@@ -350,16 +377,3 @@ class AuditTriggerLog(Model):
current = db.Column(db.JSON, default=dict(), comment='当前数据')
extra = db.Column(db.JSON, default=dict(), comment='权限名')
source = db.Column(db.String(16), default='', comment='来源')
class AuditLoginLog(Model2):
__tablename__ = "acl_audit_login_logs"
username = db.Column(db.String(64), index=True)
channel = db.Column(db.Enum('web', 'api'), default="web")
ip = db.Column(db.String(15))
browser = db.Column(db.String(256))
description = db.Column(db.String(128))
is_ok = db.Column(db.Boolean)
login_at = db.Column(db.DateTime)
logout_at = db.Column(db.DateTime)

View File

@@ -12,9 +12,7 @@ from api.lib.cmdb.const import CITypeOperateType
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import ValueTypeEnum
from api.lib.database import Model
from api.lib.database import Model2
from api.lib.utils import Crypto
from api.lib.database import Model, Model2
# template
@@ -91,37 +89,13 @@ class Attribute(Model):
compute_expr = db.Column(db.Text)
compute_script = db.Column(db.Text)
_choice_web_hook = db.Column('choice_web_hook', db.JSON)
choice_web_hook = db.Column(db.JSON)
choice_other = db.Column(db.JSON)
uid = db.Column(db.Integer, index=True)
option = db.Column(db.JSON)
def _get_webhook(self):
if self._choice_web_hook:
if self._choice_web_hook.get('headers') and "Cookie" in self._choice_web_hook['headers']:
self._choice_web_hook['headers']['Cookie'] = Crypto.decrypt(self._choice_web_hook['headers']['Cookie'])
if self._choice_web_hook.get('authorization'):
for k, v in self._choice_web_hook['authorization'].items():
self._choice_web_hook['authorization'][k] = Crypto.decrypt(v)
return self._choice_web_hook
def _set_webhook(self, data):
if data:
if data.get('headers') and "Cookie" in data['headers']:
data['headers']['Cookie'] = Crypto.encrypt(data['headers']['Cookie'])
if data.get('authorization'):
for k, v in data['authorization'].items():
data['authorization'][k] = Crypto.encrypt(v)
self._choice_web_hook = data
choice_web_hook = db.synonym("_choice_web_hook", descriptor=property(_get_webhook, _set_webhook))
class CITypeAttribute(Model):
__tablename__ = "c_ci_type_attributes"
@@ -156,25 +130,7 @@ class CITypeTrigger(Model):
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
_option = db.Column('notify', db.JSON)
def _get_option(self):
if self._option and self._option.get('webhooks'):
if self._option['webhooks'].get('authorization'):
for k, v in self._option['webhooks']['authorization'].items():
self._option['webhooks']['authorization'][k] = Crypto.decrypt(v)
return self._option
def _set_option(self, data):
if data and data.get('webhooks'):
if data['webhooks'].get('authorization'):
for k, v in data['webhooks']['authorization'].items():
data['webhooks']['authorization'][k] = Crypto.encrypt(v)
self._option = data
option = db.synonym("_option", descriptor=property(_get_option, _set_option))
option = db.Column('notify', db.JSON)
class CITriggerHistory(Model):
@@ -218,8 +174,6 @@ class CIRelation(Model):
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"))
ancestor_ids = db.Column(db.String(128), index=True)
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")

View File

@@ -96,11 +96,3 @@ class NoticeConfig(Model):
platform = db.Column(db.VARCHAR(255), nullable=False)
info = db.Column(db.JSON)
class CommonFile(Model):
__tablename__ = 'common_file'
file_name = db.Column(db.VARCHAR(512), nullable=False, index=True)
origin_name = db.Column(db.VARCHAR(512), nullable=False)
binary = db.Column(db.LargeBinary(16777216), nullable=False)

View File

@@ -16,7 +16,6 @@ from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache
@@ -98,30 +97,16 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
@flush_db
@reconnect_db
def ci_relation_cache(parent_id, child_id, ancestor_ids):
def ci_relation_cache(parent_id, child_id):
with Lock("CIRelation_{}".format(parent_id)):
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if cr and str(cr.second_ci_id) not in grandson:
grandson[str(cr.second_ci_id)] = cr.second_ci.type_id
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
@@ -171,31 +156,20 @@ def ci_relation_add(parent_dict, child_id, uid):
try:
db.session.commit()
except:
db.session.rollback()
pass
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
@reconnect_db
def ci_relation_delete(parent_id, child_id, ancestor_ids):
def ci_relation_delete(parent_id, child_id):
with Lock("CIRelation_{}".format(parent_id)):
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if str(child_id) in children:
children.pop(str(child_id))
if str(child_id) in children:
children.pop(str(child_id))
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
if str(child_id) in grandson:
grandson.pop(str(child_id))
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))

View File

@@ -24,7 +24,6 @@ class AuditLogView(APIView):
'role': AuditCRUD.search_role,
'trigger': AuditCRUD.search_trigger,
'resource': AuditCRUD.search_resource,
'login': AuditCRUD.search_login,
}
if name not in func_map:
abort(400, f'wrong {name}, please use {func_map.keys()}')

View File

@@ -8,15 +8,11 @@ from flask import abort
from flask import current_app
from flask import request
from flask import session
from flask_login import login_user
from flask_login import logout_user
from flask_login import login_user, logout_user
from api.lib.common_setting.common_data import AuthenticateDataCRUD
from api.lib.common_setting.const import AuthenticateType
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.audit import AuditCRUD
from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.cache import User
from api.lib.perm.acl.cache import UserCache
@@ -38,10 +34,8 @@ class LoginView(APIView):
username = request.values.get("username") or request.values.get("email")
password = request.values.get("password")
_role = None
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get()
if config.get('LDAP', {}).get('enabled') or config.get('LDAP', {}).get('enable'):
from api.lib.perm.authentication.ldap import authenticate_with_ldap
user, authenticated = authenticate_with_ldap(username, password)
if current_app.config.get('AUTH_WITH_LDAP'):
user, authenticated = User.query.authenticate_with_ldap(username, password)
else:
user, authenticated = User.query.authenticate(username, password)
if not user:
@@ -182,7 +176,4 @@ class LogoutView(APIView):
@auth_abandoned
def post(self):
logout_user()
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
self.jsonify(code=200)

View File

@@ -35,7 +35,6 @@ class CIRelationSearchView(APIView):
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
root_id = request.values.get('root_id')
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
query = request.values.get('q', "")
@@ -45,7 +44,7 @@ class CIRelationSearchView(APIView):
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
start = time.time()
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse, ancestor_ids=ancestor_ids)
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse)
try:
response, counter, total, page, numfound, facet = s.search()
except SearchError as e:
@@ -68,10 +67,9 @@ class CIRelationStatisticsView(APIView):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1)
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
start = time.time()
s = Search(root_ids, level, ancestor_ids=ancestor_ids)
s = Search(root_ids, level)
try:
result = s.statistics(type_ids)
except SearchError as e:
@@ -123,18 +121,14 @@ class CIRelationView(APIView):
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
def post(self, first_ci_id, second_ci_id):
ancestor_ids = request.values.get('ancestor_ids') or None
manager = CIRelationManager()
res = manager.add(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
res = manager.add(first_ci_id, second_ci_id)
return self.jsonify(cr_id=res)
def delete(self, first_ci_id, second_ci_id):
ancestor_ids = request.values.get('ancestor_ids') or None
manager = CIRelationManager()
manager.delete_2(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
manager.delete_2(first_ci_id, second_ci_id)
return self.jsonify(message="CIType Relation is deleted")
@@ -157,9 +151,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
ci_ids = list(map(int, request.values.get('ci_ids')))
parents = list(map(int, request.values.get('parents', [])))
children = list(map(int, request.values.get('children', [])))
ancestor_ids = request.values.get('ancestor_ids') or None
CIRelationManager.batch_update(ci_ids, parents, children, ancestor_ids=ancestor_ids)
CIRelationManager.batch_update(ci_ids, parents, children)
return self.jsonify(code=200)
@@ -173,8 +166,7 @@ class BatchCreateOrUpdateCIRelationView(APIView):
def delete(self):
ci_ids = list(map(int, request.values.get('ci_ids')))
parents = list(map(int, request.values.get('parents', [])))
ancestor_ids = request.values.get('ancestor_ids') or None
CIRelationManager.batch_delete(ci_ids, parents, ancestor_ids=ancestor_ids)
CIRelationManager.batch_delete(ci_ids, parents)
return self.jsonify(code=200)

View File

@@ -105,7 +105,6 @@ class CITypeGroupView(APIView):
return self.jsonify(group.to_dict())
@role_required(RoleEnum.CONFIG)
@args_validate(CITypeGroupManager.cls)
def put(self, gid=None):
if "/order" in request.url:
@@ -507,4 +506,3 @@ class CITypeFilterPermissionView(APIView):
@auth_with_app_token
def get(self, type_id):
return self.jsonify(CIFilterPermsCRUD().get(type_id))

View File

@@ -9,7 +9,6 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager
@@ -110,10 +109,3 @@ class CITypeRelationRevokeView(APIView):
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
return self.jsonify(code=200)
class CITypeRelationCanEditView(APIView):
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>/can_edit"
def get(self, parent_id, child_id):
return self.jsonify(result=PreferenceManager.can_edit_relation(parent_id, child_id))

View File

@@ -1,88 +0,0 @@
from flask import abort, request
from api.lib.common_setting.common_data import AuthenticateDataCRUD
from api.lib.common_setting.const import TestType
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.perm.acl.acl import role_required
from api.resource import APIView
prefix = '/auth_config'
class AuthConfigView(APIView):
url_prefix = (f'{prefix}/<string:auth_type>',)
@role_required("acl_admin")
def get(self, auth_type):
cli = AuthenticateDataCRUD(auth_type)
if auth_type not in cli.get_support_type_list():
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
if auth_type in cli.common_type_list:
data = cli.get_record(True)
else:
data = cli.get_record_with_decrypt()
return self.jsonify(data)
@role_required("acl_admin")
def post(self, auth_type):
cli = AuthenticateDataCRUD(auth_type)
if auth_type not in cli.get_support_type_list():
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
params = request.json
data = params.get('data', {})
if auth_type in cli.common_type_list:
data['encrypt'] = False
cli.create(data)
return self.jsonify(params)
class AuthConfigViewWithId(APIView):
url_prefix = (f'{prefix}/<string:auth_type>/<int:_id>',)
@role_required("acl_admin")
def put(self, auth_type, _id):
cli = AuthenticateDataCRUD(auth_type)
if auth_type not in cli.get_support_type_list():
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
params = request.json
data = params.get('data', {})
if auth_type in cli.common_type_list:
data['encrypt'] = False
res = cli.update(_id, data)
return self.jsonify(res.to_dict())
@role_required("acl_admin")
def delete(self, auth_type, _id):
cli = AuthenticateDataCRUD(auth_type)
if auth_type not in cli.get_support_type_list():
abort(400, ErrFormat.not_support_auth_type.format(auth_type))
cli.delete(_id)
return self.jsonify({})
class AuthEnableListView(APIView):
url_prefix = (f'{prefix}/enable_list',)
method_decorators = []
def get(self):
return self.jsonify(AuthenticateDataCRUD.get_enable_list())
class AuthConfigTestView(APIView):
url_prefix = (f'{prefix}/<string:auth_type>/test',)
def post(self, auth_type):
test_type = request.values.get('test_type', TestType.Connect)
params = request.json
return self.jsonify(AuthenticateDataCRUD(auth_type).test(test_type, params.get('data')))

View File

@@ -1,7 +1,9 @@
# -*- coding:utf-8 -*-
from flask import abort
from flask import request
from api.lib.common_setting.company_info import CompanyInfoCRUD
from api.lib.common_setting.resp_format import ErrFormat
from api.resource import APIView
prefix = '/company'

View File

@@ -1,5 +1,7 @@
# -*- coding:utf-8 -*-
from flask import abort
import os
from flask import abort, current_app, send_from_directory
from flask import request
from werkzeug.datastructures import MultiDict

View File

@@ -3,10 +3,9 @@ import os
from flask import request, abort, current_app, send_from_directory
from werkzeug.utils import secure_filename
import lz4.frame
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name
from api.resource import APIView
prefix = '/file'
@@ -29,8 +28,7 @@ class GetFileView(APIView):
url_prefix = (f'{prefix}/<string:_filename>',)
def get(self, _filename):
file_stream = CommonFileCRUD.get_file(_filename)
return self.send_file(file_stream, as_attachment=True, download_name=_filename)
return send_from_directory(current_app.config['UPLOAD_DIRECTORY_FULL'], _filename, as_attachment=True)
class PostFileView(APIView):
@@ -55,20 +53,11 @@ class PostFileView(APIView):
filename = file.filename
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
new_filename = generate_new_file_name(filename)
new_filename = secure_filename(new_filename)
file_content = file.read()
compressed_data = lz4.frame.compress(file_content)
try:
CommonFileCRUD.add_file(
origin_name=filename,
file_name=new_filename,
binary=compressed_data,
)
filename = generate_new_file_name(filename)
filename = secure_filename(filename)
file.save(os.path.join(
current_app.config['UPLOAD_DIRECTORY_FULL'], filename))
return self.jsonify(file_name=new_filename)
except Exception as e:
current_app.logger.error(e)
abort(400, ErrFormat.upload_failed.format(e))
return self.jsonify(file_name=filename)
abort(400, ErrFormat.file_type_not_allowed.format(filename))
abort(400, 'Extension not allow')

View File

@@ -47,7 +47,7 @@ class CheckEmailServer(APIView):
def post(self):
receive_address = request.args.get('receive_address')
info = request.values.get('info', {})
info = request.values.get('info')
try:

View File

@@ -12,7 +12,7 @@ Flask==2.3.2
Flask-Bcrypt==1.0.1
Flask-Caching==2.0.2
Flask-Cors==4.0.0
Flask-Login>=0.6.2
Flask-Login==0.6.2
Flask-Migrate==2.5.2
Flask-RESTful==0.3.10
Flask-SQLAlchemy==2.5.0
@@ -29,12 +29,12 @@ MarkupSafe==2.1.3
marshmallow==2.20.2
more-itertools==5.0.0
msgpack-python==0.5.6
Pillow>=10.0.1
cryptography>=41.0.2
Pillow==9.3.0
cryptography==41.0.2
PyJWT==2.4.0
PyMySQL==1.1.0
ldap3==2.9.1
PyYAML==6.0.1
PyYAML==6.0
redis==4.6.0
requests==2.31.0
requests_oauthlib==1.3.1
@@ -45,9 +45,9 @@ supervisor==4.0.3
timeout-decorator==0.5.0
toposort==1.10
treelib==1.6.1
Werkzeug>=2.3.6
Werkzeug==2.3.6
WTForms==3.0.0
shamir~=17.12.0
hvac~=2.0.0
pycryptodomex>=3.19.0
colorama>=0.4.6
lz4>=4.3.2

View File

@@ -11,10 +11,10 @@ from environs import Env
env = Env()
env.read_env()
ENV = env.str('FLASK_ENV', default='production')
DEBUG = ENV == 'development'
SECRET_KEY = env.str('SECRET_KEY')
BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13)
ENV = env.str("FLASK_ENV", default="production")
DEBUG = ENV == "development"
SECRET_KEY = env.str("SECRET_KEY")
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
DEBUG_TB_ENABLED = DEBUG
DEBUG_TB_INTERCEPT_REDIRECTS = False
@@ -23,7 +23,7 @@ ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
# # database
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
SQLALCHEMY_BINDS = {
'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
"user": 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
}
SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -32,11 +32,11 @@ SQLALCHEMY_ENGINE_OPTIONS = {
}
# # cache
CACHE_TYPE = 'redis'
CACHE_REDIS_HOST = '127.0.0.1'
CACHE_TYPE = "redis"
CACHE_REDIS_HOST = "127.0.0.1"
CACHE_REDIS_PORT = 6379
CACHE_REDIS_PASSWORD = ''
CACHE_KEY_PREFIX = 'CMDB::'
CACHE_REDIS_PASSWORD = ""
CACHE_KEY_PREFIX = "CMDB::"
CACHE_DEFAULT_TIMEOUT = 3000
# # log
@@ -55,10 +55,10 @@ DEFAULT_MAIL_SENDER = ''
# # queue
CELERY = {
'broker_url': 'redis://127.0.0.1:6379/2',
'result_backend': 'redis://127.0.0.1:6379/2',
'broker_vhost': '/',
'broker_connection_retry_on_startup': True
"broker_url": 'redis://127.0.0.1:6379/2',
"result_backend": "redis://127.0.0.1:6379/2",
"broker_vhost": "/",
"broker_connection_retry_on_startup": True
}
ONCE = {
'backend': 'celery_once.backends.Redis',
@@ -67,81 +67,33 @@ ONCE = {
}
}
# =============================== Authentication ===========================================================
# # SSO
CAS_SERVER = "http://sso.xxx.com"
CAS_VALIDATE_SERVER = "http://sso.xxx.com"
CAS_LOGIN_ROUTE = "/cas/login"
CAS_LOGOUT_ROUTE = "/cas/logout"
CAS_VALIDATE_ROUTE = "/cas/serviceValidate"
CAS_AFTER_LOGIN = "/"
DEFAULT_SERVICE = "http://127.0.0.1:8000"
# # CAS
CAS = dict(
enabled=False,
cas_server='https://{your-CASServer-hostname}',
cas_validate_server='https://{your-CASServer-hostname}',
cas_login_route='/cas/built-in/cas/login',
cas_logout_route='/cas/built-in/cas/logout',
cas_validate_route='/cas/built-in/cas/serviceValidate',
cas_after_login='/',
cas_user_map={
'username': {'tag': 'cas:user'},
'nickname': {'tag': 'cas:attribute', 'attrs': {'name': 'displayName'}},
'email': {'tag': 'cas:attribute', 'attrs': {'name': 'email'}},
'mobile': {'tag': 'cas:attribute', 'attrs': {'name': 'phone'}},
'avatar': {'tag': 'cas:attribute', 'attrs': {'name': 'avatar'}},
}
)
# # OAuth2.0
OAUTH2 = dict(
enabled=False,
client_id='',
client_secret='',
authorize_url='https://{your-OAuth2Server-hostname}/login/oauth/authorize',
token_url='https://{your-OAuth2Server-hostname}/api/login/oauth/access_token',
scopes=['profile', 'email'],
user_info={
'url': 'https://{your-OAuth2Server-hostname}/api/userinfo',
'email': 'email',
'username': 'name',
'avatar': 'picture'
},
after_login='/'
)
# # OIDC
OIDC = dict(
enabled=False,
client_id='',
client_secret='',
authorize_url='https://{your-OIDCServer-hostname}/login/oauth/authorize',
token_url='https://{your-OIDCServer-hostname}/api/login/oauth/access_token',
scopes=['openid', 'profile', 'email'],
user_info={
'url': 'https://{your-OIDCServer-hostname}/api/userinfo',
'email': 'email',
'username': 'name',
'avatar': 'picture'
},
after_login='/'
)
# # LDAP
LDAP = dict(
enabled=False,
ldap_server='',
ldap_domain='',
ldap_user_dn='cn={},ou=users,dc=xxx,dc=com'
)
# ==========================================================================================================
# # ldap
AUTH_WITH_LDAP = False
LDAP_SERVER = ''
LDAP_DOMAIN = ''
LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
# # pagination
DEFAULT_PAGE_COUNT = 50
# # permission
WHITE_LIST = ['127.0.0.1']
WHITE_LIST = ["127.0.0.1"]
USE_ACL = True
# # elastic search
ES_HOST = '127.0.0.1'
USE_ES = False
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, 'Yes', 'YES', 'yes', 'Y', 'y']
BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
# # messenger
USE_MESSENGER = True

View File

@@ -63,15 +63,14 @@
},
"devDependencies": {
"@ant-design/colors": "^3.2.2",
"@babel/core": "^7.23.2",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.23.2",
"@vue/cli-plugin-babel": "4.5.17",
"@vue/cli-plugin-eslint": "^4.0.5",
"@vue/cli-plugin-unit-jest": "^4.0.5",
"@vue/cli-service": "^4.0.5",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.30",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"babel-plugin-import": "^1.11.0",
"babel-plugin-transform-remove-console": "^6.9.4",

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1702544951995') format('woff2'),
url('iconfont.woff?t=1702544951995') format('woff'),
url('iconfont.ttf?t=1702544951995') format('truetype');
src: url('iconfont.woff2?t=1698273699449') format('woff2'),
url('iconfont.woff?t=1698273699449') format('woff'),
url('iconfont.ttf?t=1698273699449') format('truetype');
}
.iconfont {
@@ -13,274 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.OAUTH2:before {
content: "\e8d8";
}
.OIDC:before {
content: "\e8d6";
}
.CAS:before {
content: "\e8d7";
}
.ops-setting-auth:before {
content: "\e8d5";
}
.ops-setting-auth-selected:before {
content: "\e8d4";
}
.a-itsm-knowledge2:before {
content: "\e8d2";
}
.itsm-qrdownload:before {
content: "\e8d3";
}
.oneterm-playback:before {
content: "\e8d1";
}
.oneterm-disconnect:before {
content: "\e8d0";
}
.ops-oneterm-publickey-selected:before {
content: "\e8cf";
}
.ops-oneterm-publickey:before {
content: "\e8ce";
}
.ops-oneterm-gateway:before {
content: "\e8b9";
}
.ops-oneterm-gateway-selected:before {
content: "\e8bf";
}
.ops-oneterm-account:before {
content: "\e8c0";
}
.ops-oneterm-account-selected:before {
content: "\e8c1";
}
.ops-oneterm-command:before {
content: "\e8c2";
}
.ops-oneterm-command-selected:before {
content: "\e8c3";
}
.ops-oneterm-assetlist:before {
content: "\e8c4";
}
.ops-oneterm-assetlist-selected:before {
content: "\e8c5";
}
.ops-oneterm-sessiononline:before {
content: "\e8c6";
}
.ops-oneterm-sessiononline-selected:before {
content: "\e8c7";
}
.ops-oneterm-sessionhistory-selected:before {
content: "\e8c8";
}
.ops-oneterm-sessionhistory:before {
content: "\e8c9";
}
.ops-oneterm-login:before {
content: "\e8ca";
}
.ops-oneterm-login-selected:before {
content: "\e8cb";
}
.ops-oneterm-operation:before {
content: "\e8cc";
}
.ops-oneterm-operation-selected:before {
content: "\e8cd";
}
.ops-oneterm-workstation-selected:before {
content: "\e8b7";
}
.ops-oneterm-workstation:before {
content: "\e8b8";
}
.oneterm-file-selected:before {
content: "\e8be";
}
.oneterm-file:before {
content: "\e8bc";
}
.oneterm-time:before {
content: "\e8bd";
}
.oneterm-download:before {
content: "\e8bb";
}
.oneterm-commandrecord:before {
content: "\e8ba";
}
.oneterm-asset:before {
content: "\e8b6";
}
.oneterm-total_asset:before {
content: "\e8b5";
}
.oneterm-switch:before {
content: "\e8b4";
}
.oneterm-session:before {
content: "\e8b3";
}
.oneterm-connect:before {
content: "\e8b2";
}
.oneterm-login:before {
content: "\e8b1";
}
.ops-oneterm-dashboard:before {
content: "\e8af";
}
.ops-oneterm-dashboard-selected:before {
content: "\e8b0";
}
.oneterm-recentsession:before {
content: "\e8ae";
}
.oneterm-myassets:before {
content: "\e8ad";
}
.ops-oneterm-log:before {
content: "\e8aa";
}
.ops-oneterm-session-selected:before {
content: "\e8ab";
}
.ops-oneterm-session:before {
content: "\e8ac";
}
.ops-oneterm-log-selected:before {
content: "\e8a9";
}
.ops-oneterm-assets:before {
content: "\e8a7";
}
.ops-oneterm-assets-selected:before {
content: "\e8a8";
}
.itsm-down:before {
content: "\e8a5";
}
.itsm-up:before {
content: "\e8a6";
}
.itsm-download:before {
content: "\e8a4";
}
.itsm-print:before {
content: "\e8a3";
}
.itsm-view:before {
content: "\e8a2";
}
.itsm-word:before {
content: "\e8a1";
}
.datainsight-custom:before {
content: "\e89e";
}
.datainsight-prometheus:before {
content: "\e89f";
}
.datainsight-zabbix:before {
content: "\e8a0";
}
.setting-mainpeople:before {
content: "\e89a";
}
.setting-deputypeople:before {
content: "\e89d";
}
.ops-setting-duty:before {
content: "\e89c";
}
.ops-setting-duty-selected:before {
content: "\e89b";
}
.datainsight-sequential:before {
content: "\e899";
}
.datainsight-close:before {
content: "\e898";
}
.datainsight-handle:before {
content: "\e897";
}
.datainsight-table:before {
content: "\e896";
}
.icon-xianxing-password:before {
content: "\e894";
}
@@ -289,11 +21,11 @@
content: "\e895";
}
.itsm-download-all:before {
.a-itsm-oneclickdownload:before {
content: "\e892";
}
.itsm-download-package:before {
.a-itsm-packagedownload:before {
content: "\e893";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,475 +5,6 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "38566548",
"name": "OAuth2.0",
"font_class": "OAUTH2",
"unicode": "e8d8",
"unicode_decimal": 59608
},
{
"icon_id": "38566584",
"name": "OIDC",
"font_class": "OIDC",
"unicode": "e8d6",
"unicode_decimal": 59606
},
{
"icon_id": "38566578",
"name": "cas",
"font_class": "CAS",
"unicode": "e8d7",
"unicode_decimal": 59607
},
{
"icon_id": "38547395",
"name": "setting-authentication",
"font_class": "ops-setting-auth",
"unicode": "e8d5",
"unicode_decimal": 59605
},
{
"icon_id": "38547389",
"name": "setting-authentication-selected",
"font_class": "ops-setting-auth-selected",
"unicode": "e8d4",
"unicode_decimal": 59604
},
{
"icon_id": "38533133",
"name": "itsm-knowledge (2)",
"font_class": "a-itsm-knowledge2",
"unicode": "e8d2",
"unicode_decimal": 59602
},
{
"icon_id": "38531868",
"name": "itsm-QRcode",
"font_class": "itsm-qrdownload",
"unicode": "e8d3",
"unicode_decimal": 59603
},
{
"icon_id": "38413515",
"name": "oneterm-playback",
"font_class": "oneterm-playback",
"unicode": "e8d1",
"unicode_decimal": 59601
},
{
"icon_id": "38413481",
"name": "oneterm-disconnect",
"font_class": "oneterm-disconnect",
"unicode": "e8d0",
"unicode_decimal": 59600
},
{
"icon_id": "38407867",
"name": "oneterm-key-selected",
"font_class": "ops-oneterm-publickey-selected",
"unicode": "e8cf",
"unicode_decimal": 59599
},
{
"icon_id": "38407915",
"name": "oneterm-key",
"font_class": "ops-oneterm-publickey",
"unicode": "e8ce",
"unicode_decimal": 59598
},
{
"icon_id": "38311855",
"name": "oneterm-gateway",
"font_class": "ops-oneterm-gateway",
"unicode": "e8b9",
"unicode_decimal": 59577
},
{
"icon_id": "38311938",
"name": "oneterm-gateway-selected",
"font_class": "ops-oneterm-gateway-selected",
"unicode": "e8bf",
"unicode_decimal": 59583
},
{
"icon_id": "38311957",
"name": "oneterm-account",
"font_class": "ops-oneterm-account",
"unicode": "e8c0",
"unicode_decimal": 59584
},
{
"icon_id": "38311961",
"name": "oneterm-account-selected",
"font_class": "ops-oneterm-account-selected",
"unicode": "e8c1",
"unicode_decimal": 59585
},
{
"icon_id": "38311974",
"name": "oneterm-command",
"font_class": "ops-oneterm-command",
"unicode": "e8c2",
"unicode_decimal": 59586
},
{
"icon_id": "38311976",
"name": "oneterm-command-selected",
"font_class": "ops-oneterm-command-selected",
"unicode": "e8c3",
"unicode_decimal": 59587
},
{
"icon_id": "38311979",
"name": "oneterm-asset_list",
"font_class": "ops-oneterm-assetlist",
"unicode": "e8c4",
"unicode_decimal": 59588
},
{
"icon_id": "38311985",
"name": "oneterm-asset_list-selected",
"font_class": "ops-oneterm-assetlist-selected",
"unicode": "e8c5",
"unicode_decimal": 59589
},
{
"icon_id": "38312030",
"name": "oneterm-online",
"font_class": "ops-oneterm-sessiononline",
"unicode": "e8c6",
"unicode_decimal": 59590
},
{
"icon_id": "38312152",
"name": "oneterm-online-selected",
"font_class": "ops-oneterm-sessiononline-selected",
"unicode": "e8c7",
"unicode_decimal": 59591
},
{
"icon_id": "38312154",
"name": "oneterm-history-selected",
"font_class": "ops-oneterm-sessionhistory-selected",
"unicode": "e8c8",
"unicode_decimal": 59592
},
{
"icon_id": "38312155",
"name": "oneterm-history",
"font_class": "ops-oneterm-sessionhistory",
"unicode": "e8c9",
"unicode_decimal": 59593
},
{
"icon_id": "38312404",
"name": "oneterm-entry_log",
"font_class": "ops-oneterm-login",
"unicode": "e8ca",
"unicode_decimal": 59594
},
{
"icon_id": "38312423",
"name": "oneterm-entry_log-selected",
"font_class": "ops-oneterm-login-selected",
"unicode": "e8cb",
"unicode_decimal": 59595
},
{
"icon_id": "38312426",
"name": "oneterm-operation_log",
"font_class": "ops-oneterm-operation",
"unicode": "e8cc",
"unicode_decimal": 59596
},
{
"icon_id": "38312445",
"name": "oneterm-operation_log-selected",
"font_class": "ops-oneterm-operation-selected",
"unicode": "e8cd",
"unicode_decimal": 59597
},
{
"icon_id": "38307876",
"name": "oneterm-workstation-selected",
"font_class": "ops-oneterm-workstation-selected",
"unicode": "e8b7",
"unicode_decimal": 59575
},
{
"icon_id": "38307871",
"name": "oneterm-workstation",
"font_class": "ops-oneterm-workstation",
"unicode": "e8b8",
"unicode_decimal": 59576
},
{
"icon_id": "38302246",
"name": "oneterm-file-selected",
"font_class": "oneterm-file-selected",
"unicode": "e8be",
"unicode_decimal": 59582
},
{
"icon_id": "38302255",
"name": "oneterm-file",
"font_class": "oneterm-file",
"unicode": "e8bc",
"unicode_decimal": 59580
},
{
"icon_id": "38203528",
"name": "oneterm-time",
"font_class": "oneterm-time",
"unicode": "e8bd",
"unicode_decimal": 59581
},
{
"icon_id": "38203331",
"name": "oneterm-download",
"font_class": "oneterm-download",
"unicode": "e8bb",
"unicode_decimal": 59579
},
{
"icon_id": "38201351",
"name": "oneterm-command record",
"font_class": "oneterm-commandrecord",
"unicode": "e8ba",
"unicode_decimal": 59578
},
{
"icon_id": "38199341",
"name": "oneterm-connected assets",
"font_class": "oneterm-asset",
"unicode": "e8b6",
"unicode_decimal": 59574
},
{
"icon_id": "38199350",
"name": "oneterm-total assets",
"font_class": "oneterm-total_asset",
"unicode": "e8b5",
"unicode_decimal": 59573
},
{
"icon_id": "38199303",
"name": "oneterm-switch (3)",
"font_class": "oneterm-switch",
"unicode": "e8b4",
"unicode_decimal": 59572
},
{
"icon_id": "38199317",
"name": "oneterm-session",
"font_class": "oneterm-session",
"unicode": "e8b3",
"unicode_decimal": 59571
},
{
"icon_id": "38199339",
"name": "oneterm-connection",
"font_class": "oneterm-connect",
"unicode": "e8b2",
"unicode_decimal": 59570
},
{
"icon_id": "38198321",
"name": "oneterm-log in",
"font_class": "oneterm-login",
"unicode": "e8b1",
"unicode_decimal": 59569
},
{
"icon_id": "38194554",
"name": "oneterm-dashboard",
"font_class": "ops-oneterm-dashboard",
"unicode": "e8af",
"unicode_decimal": 59567
},
{
"icon_id": "38194525",
"name": "oneterm-dashboard-selected",
"font_class": "ops-oneterm-dashboard-selected",
"unicode": "e8b0",
"unicode_decimal": 59568
},
{
"icon_id": "38194352",
"name": "oneterm-recent session",
"font_class": "oneterm-recentsession",
"unicode": "e8ae",
"unicode_decimal": 59566
},
{
"icon_id": "38194383",
"name": "oneterm-my assets",
"font_class": "oneterm-myassets",
"unicode": "e8ad",
"unicode_decimal": 59565
},
{
"icon_id": "38194089",
"name": "oneterm-log",
"font_class": "ops-oneterm-log",
"unicode": "e8aa",
"unicode_decimal": 59562
},
{
"icon_id": "38194088",
"name": "oneterm-conversation-selected",
"font_class": "ops-oneterm-session-selected",
"unicode": "e8ab",
"unicode_decimal": 59563
},
{
"icon_id": "38194065",
"name": "oneterm-conversation",
"font_class": "ops-oneterm-session",
"unicode": "e8ac",
"unicode_decimal": 59564
},
{
"icon_id": "38194105",
"name": "oneterm-log-selected",
"font_class": "ops-oneterm-log-selected",
"unicode": "e8a9",
"unicode_decimal": 59561
},
{
"icon_id": "38194054",
"name": "oneterm-assets",
"font_class": "ops-oneterm-assets",
"unicode": "e8a7",
"unicode_decimal": 59559
},
{
"icon_id": "38194055",
"name": "oneterm-assets-selected",
"font_class": "ops-oneterm-assets-selected",
"unicode": "e8a8",
"unicode_decimal": 59560
},
{
"icon_id": "38123087",
"name": "itsm-down",
"font_class": "itsm-down",
"unicode": "e8a5",
"unicode_decimal": 59557
},
{
"icon_id": "38123084",
"name": "itsm-up",
"font_class": "itsm-up",
"unicode": "e8a6",
"unicode_decimal": 59558
},
{
"icon_id": "38105374",
"name": "itsm-download",
"font_class": "itsm-download",
"unicode": "e8a4",
"unicode_decimal": 59556
},
{
"icon_id": "38105235",
"name": "itsm-print",
"font_class": "itsm-print",
"unicode": "e8a3",
"unicode_decimal": 59555
},
{
"icon_id": "38104997",
"name": "itsm-view",
"font_class": "itsm-view",
"unicode": "e8a2",
"unicode_decimal": 59554
},
{
"icon_id": "38105129",
"name": "itsm-word",
"font_class": "itsm-word",
"unicode": "e8a1",
"unicode_decimal": 59553
},
{
"icon_id": "38095730",
"name": "datainsight-custom",
"font_class": "datainsight-custom",
"unicode": "e89e",
"unicode_decimal": 59550
},
{
"icon_id": "38095729",
"name": "datainsight-prometheus",
"font_class": "datainsight-prometheus",
"unicode": "e89f",
"unicode_decimal": 59551
},
{
"icon_id": "38095728",
"name": "datainsight-zabbix",
"font_class": "datainsight-zabbix",
"unicode": "e8a0",
"unicode_decimal": 59552
},
{
"icon_id": "37944507",
"name": "setting-main people",
"font_class": "setting-mainpeople",
"unicode": "e89a",
"unicode_decimal": 59546
},
{
"icon_id": "37944503",
"name": "setting-deputy people",
"font_class": "setting-deputypeople",
"unicode": "e89d",
"unicode_decimal": 59549
},
{
"icon_id": "37940080",
"name": "ops-setting-duty",
"font_class": "ops-setting-duty",
"unicode": "e89c",
"unicode_decimal": 59548
},
{
"icon_id": "37940033",
"name": "ops-setting-duty-selected",
"font_class": "ops-setting-duty-selected",
"unicode": "e89b",
"unicode_decimal": 59547
},
{
"icon_id": "37841524",
"name": "datainsight-sequential",
"font_class": "datainsight-sequential",
"unicode": "e899",
"unicode_decimal": 59545
},
{
"icon_id": "37841535",
"name": "datainsight-close",
"font_class": "datainsight-close",
"unicode": "e898",
"unicode_decimal": 59544
},
{
"icon_id": "37841537",
"name": "datainsight-handle",
"font_class": "datainsight-handle",
"unicode": "e897",
"unicode_decimal": 59543
},
{
"icon_id": "37841515",
"name": "datainsight-table",
"font_class": "datainsight-table",
"unicode": "e896",
"unicode_decimal": 59542
},
{
"icon_id": "37830610",
"name": "icon-xianxing-password",
@@ -491,14 +22,14 @@
{
"icon_id": "37822199",
"name": "itsm-oneclick download",
"font_class": "itsm-download-all",
"font_class": "a-itsm-oneclickdownload",
"unicode": "e892",
"unicode_decimal": 59538
},
{
"icon_id": "37822198",
"name": "itsm-package download",
"font_class": "itsm-download-package",
"font_class": "a-itsm-packagedownload",
"unicode": "e893",
"unicode_decimal": 59539
},

Binary file not shown.

View File

@@ -1,39 +0,0 @@
import { axios } from '@/utils/request'
export function getAuthData(data_type) {
return axios({
url: `/common-setting/v1/auth_config/${data_type}`,
method: 'get',
})
}
export function postAuthData(data_type, data) {
return axios({
url: `/common-setting/v1/auth_config/${data_type}`,
method: 'post',
data,
})
}
export function putAuthData(data_type, id, data) {
return axios({
url: `/common-setting/v1/auth_config/${data_type}/${id}`,
method: 'put',
data,
})
}
export function getAuthDataEnable() {
return axios({
url: `/common-setting/v1/auth_config/enable_list`,
method: 'get',
})
}
export function testLDAP(test_type, data) {
return axios({
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`,
method: 'post',
data,
})
}

View File

@@ -1,6 +1,8 @@
import config from '@/config/setting'
const api = {
Login: '/v1/acl/login',
Logout: '/v1/acl/logout',
Login: config.useSSO ? '/api/sso/login' : '/v1/acl/login',
Logout: config.useSSO ? '/api/sso/logout' : '/v1/acl/logout',
ForgePassword: '/auth/forge-password',
Register: '/auth/register',
twoStepCode: '/auth/2step-code',

View File

@@ -1,5 +1,6 @@
import api from './index'
import { axios } from '@/utils/request'
import config from '@/config/setting'
/**
* login func
* parameter: {
@@ -11,10 +12,9 @@ import { axios } from '@/utils/request'
* @param parameter
* @returns {*}
*/
export function login(data, auth_type) {
if (auth_type) {
localStorage.setItem('ops_auth_type', auth_type)
window.location.href = `/api/${auth_type.toLowerCase()}/login`
export function login(data) {
if (config.useSSO) {
window.location.href = config.ssoLoginUrl
} else {
return axios({
url: api.Login,
@@ -43,15 +43,17 @@ export function getInfo() {
}
export function logout() {
const auth_type = localStorage.getItem('ops_auth_type')
localStorage.clear()
return axios({
url: auth_type ? `/${auth_type.toLowerCase()}/logout` : api.Logout,
method: auth_type ? 'get' : 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
if (config.useSSO) {
window.location.replace(api.Logout)
} else {
return axios({
url: api.Logout,
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
}
/**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -95,10 +95,6 @@ export default {
const unsubTree = subscribeTreeView(citypeId, '')
Promise.all([unsubCIType, unsubTree]).then(() => {
that.$message.success('取消订阅成功')
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (Number(citypeId) === Number(lastTypeId)) {
localStorage.setItem('ops_ci_typeid', '')
}
// 删除路由
const href = window.location.href
const hrefSplit = href.split('/')

View File

@@ -66,8 +66,10 @@ export default {
this.$confirm({
title: '提示',
content: '确认注销登录 ?',
content: '真的要注销登录 ?',
onOk() {
// localStorage.removeItem('ops_cityps_currentId')
localStorage.clear()
return that.Logout()
},
onCancel() {},

View File

@@ -2,6 +2,7 @@ const appConfig = {
buildModules: ['cmdb', 'acl'], // 需要编译的模块
redirectTo: '/cmdb', // 首页的重定向路径
buildAclToModules: true, // 是否在各个应用下 内联权限管理
ssoLogoutURL: '/api/sso/logout',
showDocs: false,
useEncryption: false,
}

View File

@@ -1,5 +1,6 @@
/**
* 项目默认配置项
* useSSO - 是否启用单点登录, 默认为否, 可以根据需要接入到公司的单点登录系统
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
* navTheme - sidebar theme ['dark', 'light'] 两种主题
* colorWeak - 色盲模式
@@ -14,6 +15,8 @@
*/
export default {
useSSO: false,
ssoLoginUrl: '/api/sso/login',
primaryColor: '#1890ff', // primary color of ant design
navTheme: 'dark', // theme for nav menu
layout: 'sidemenu', // nav menu position: sidemenu or topmenu

View File

@@ -6,6 +6,7 @@ import store from './store'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import config from '@/config/setting'
import { ACCESS_TOKEN } from './store/global/mutation-types'
NProgress.configure({ showSpinner: false })
@@ -15,16 +16,16 @@ const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/lo
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
// 登录页面处理处理 是否使用单点登录
router.beforeEach(async (to, from, next) => {
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
const authed = store.state.authed
const auth_type = localStorage.getItem('ops_auth_type')
if (whitePath.includes(to.path)) {
next()
} else if ((auth_type || (!auth_type && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
store.dispatch('GetAuthDataEnable')
} else if ((config.useSSO || (!config.useSSO && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => {
const roles = res.result && res.result.role
store.dispatch("loadAllUsers")
@@ -45,17 +46,10 @@ router.beforeEach(async (to, from, next) => {
}).catch((e) => {
setTimeout(() => { store.dispatch('Logout') }, 3000)
})
} else if (to.path === '/user/login' && !auth_type && store.getters.roles.length !== 0) {
} else if (to.path === '/user/login' && !config.useSSO && store.getters.roles.length !== 0) {
next({ path: '/' })
} else if (!auth_type && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
await store.dispatch('GetAuthDataEnable')
const { enable_list = [] } = store?.state?.user?.auth_enable ?? {}
const _enable_list = enable_list.filter(en => en.auth_type !== 'LDAP')
if (_enable_list.length === 1) {
next({ path: '/user/logout', query: { redirect: to.fullPath } })
} else {
next({ path: '/user/login', query: { redirect: to.fullPath } })
}
} else if (!config.useSSO && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
next({ path: '/user/login', query: { redirect: to.fullPath } })
} else {
next()
}

View File

@@ -68,7 +68,6 @@
ref="xTable"
row-id="id"
show-overflow
resizable
>
<!-- 1 -->
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column>

View File

@@ -133,8 +133,8 @@ export default {
if (newVal) {
this.tableData = this.allUsers.filter(
(item) =>
(item.username && item.username.toLowerCase().includes(newVal.toLowerCase())) ||
(item.nickname && item.nickname.toLowerCase().includes(newVal.toLowerCase()))
item.username.toLowerCase().includes(newVal.toLowerCase()) ||
item.nickname.toLowerCase().includes(newVal.toLowerCase())
)
} else {
this.tableData = this.allUsers

View File

@@ -1,13 +1,13 @@
import { axios } from '@/utils/request'
export function getFirstCIsByCiId(ciId) {
export function getFirstCIs(ciId) {
return axios({
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
method: 'GET'
})
}
export function getSecondCIsByCiId(ciId) {
export function getSecondCIs(ciId) {
return axios({
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
method: 'GET'
@@ -30,11 +30,11 @@ export function statisticsCIRelation(params) {
}
// 批量添加子节点
export function batchUpdateCIRelationChildren(ciIds, parents, ancestor_ids = undefined) {
export function batchUpdateCIRelationChildren(ciIds, parents) {
return axios({
url: '/v0.1/ci_relations/batch',
method: 'POST',
data: { ci_ids: ciIds, parents, ancestor_ids }
data: { ci_ids: ciIds, parents: parents }
})
}
@@ -48,28 +48,26 @@ export function batchUpdateCIRelationParents(ciIds, children) {
}
// 批量删除
export function batchDeleteCIRelation(ciIds, parents, ancestor_ids = undefined) {
export function batchDeleteCIRelation(ciIds, parents) {
return axios({
url: '/v0.1/ci_relations/batch',
method: 'DELETE',
data: { ci_ids: ciIds, parents, ancestor_ids }
data: { ci_ids: ciIds, parents: parents }
})
}
// 单个添加
export function addCIRelationView(firstCiId, secondCiId, data) {
export function addCIRelationView(firstCiId, secondCiId) {
return axios({
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
method: 'POST',
data
})
}
// 单个删除
export function deleteCIRelationView(firstCiId, secondCiId, data) {
export function deleteCIRelationView(firstCiId, secondCiId) {
return axios({
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
method: 'DELETE',
data
})
}

View File

@@ -68,10 +68,3 @@ export function getRecursive_level2children(type_id) {
method: 'GET'
})
}
export function getCanEditByParentIdChildId(parent_id, child_id) {
return axios({
url: `/v0.1/ci_type_relations/${parent_id}/${child_id}/can_edit`,
method: 'GET'
})
}

View File

@@ -16,14 +16,12 @@ export function processFile(fileObj) {
}
export function uploadData(ciId, data) {
data.ci_type = ciId
data.exist_policy = 'replace'
return axios({
url: '/v0.1/ci',
method: 'POST',
data: {
...data,
ci_type: ciId,
exist_policy: 'replace'
},
data,
isShowMessage: false
})
}

View File

@@ -1,16 +1,11 @@
<template>
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
<div id="title">
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
<ci-type-choice @getCiTypeAttr="showCiType" />
</div>
<a-row>
<a-col :span="12">
<upload-file-form
:isUploading="isUploading"
:ciType="ciType"
ref="uploadFileForm"
@uploadDone="uploadDone"
></upload-file-form>
<upload-file-form :ciType="ciType" ref="uploadFileForm" @uploadDone="uploadDone"></upload-file-form>
</a-col>
<a-col :span="24" v-if="ciType && uploadData.length">
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
@@ -18,19 +13,15 @@
<a-space size="large">
<a-button type="primary" ghost @click="handleCancel">取消</a-button>
<a-button @click="handleUpload" type="primary">上传</a-button>
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">失败下载</a-button>
</a-space>
</div>
</a-col>
<a-col :span="24" v-if="ciType">
<a-col :span="24">
<upload-result
ref="uploadResult"
:upLoadData="uploadData"
:ciType="ciType"
:unique-field="uniqueField"
:isUploading="isUploading"
@uploadResultDone="uploadResultDone"
@uploadResultError="uploadResultError"
></upload-result>
</a-col>
</a-row>
@@ -38,7 +29,6 @@
</template>
<script>
import moment from 'moment'
import { mapState } from 'vuex'
import CiTypeChoice from './modules/CiTypeChoice'
import CiUploadTable from './modules/CiUploadTable'
@@ -61,8 +51,7 @@ export default {
ciType: 0,
uniqueField: '',
uniqueId: 0,
isUploading: false,
hasError: false,
displayUpload: true,
}
},
computed: {
@@ -70,12 +59,13 @@ export default {
windowHeight: (state) => state.windowHeight,
}),
},
inject: ['reload'],
methods: {
showCiType(message) {
this.ciTypeAttrs = message ?? {}
this.ciType = message?.type_id ?? 0
this.uniqueField = message?.unique ?? ''
this.uniqueId = message?.unique_id ?? 0
this.ciTypeAttrs = message
this.ciType = message.type_id
this.uniqueField = message.unique
this.uniqueId = message.unique_id
},
uploadDone(dataList) {
const _uploadData = filterNull(dataList).map((item, i) => {
@@ -83,20 +73,7 @@ export default {
const _ele = {}
item.forEach((ele, j) => {
if (ele !== undefined && ele !== null) {
const _find = this.ciTypeAttrs.attributes.find(
(attr) => attr.alias === dataList[0][j] || attr.name === dataList[0][j]
)
if (_find?.value_type === '4' && typeof ele === 'number') {
_ele[dataList[0][j]] = moment(Math.round((ele - 25569) * 86400 * 1000 - 28800000)).format('YYYY-MM-DD')
} else if (_find?.value_type === '3' && typeof ele === 'number') {
_ele[dataList[0][j]] = moment(Math.round((ele - 25569) * 86400 * 1000 - 28800000)).format(
'YYYY-MM-DD HH:mm:ss'
)
} else if (_find?.value_type === '5' && typeof ele === 'number') {
_ele[dataList[0][j]] = moment(Math.round(ele * 86400 * 1000 - 28800000)).format('HH:mm:ss')
} else {
_ele[dataList[0][j]] = ele
}
_ele[dataList[0][j]] = ele
}
})
return _ele
@@ -104,9 +81,6 @@ export default {
return item
})
this.uploadData = _uploadData.slice(1)
this.hasError = false
this.isUploading = false
this.$refs.uploadResult.visible = false
},
handleUpload() {
if (!this.ciType) {
@@ -114,7 +88,6 @@ export default {
return
}
if (this.uploadData && this.uploadData.length > 0) {
this.isUploading = true
this.$nextTick(() => {
this.$refs.uploadResult.upload2Server()
})
@@ -123,24 +96,7 @@ export default {
}
},
handleCancel() {
if (!this.isUploading) {
this.showCiType(null)
this.$refs.ciTypeChoice.selectNum = null
this.hasError = false
} else {
this.$message.warning('批量上传已取消')
this.isUploading = false
}
},
uploadResultDone() {
this.isUploading = false
},
uploadResultError(index) {
this.hasError = true
this.$refs.ciUploadTable.uploadResultError(index)
},
downloadError() {
this.$refs.ciUploadTable.downloadError()
this.reload()
},
},
}

View File

@@ -8,7 +8,6 @@
:style="{ width: '300px' }"
class="ops-select"
:filter-option="filterOption"
v-model="selectNum"
>
<a-select-option v-for="ciType in ciTypeList" :key="ciType.name" :value="ciType.id">{{
ciType.alias
@@ -41,7 +40,7 @@
全选
</a-checkbox>
<br />
<a-checkbox-group style="width:100%" v-model="checkedAttrs">
<a-checkbox-group v-model="checkedAttrs">
<a-row>
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
@@ -88,11 +87,10 @@
</template>
<script>
import _ from 'lodash'
import { downloadExcel } from '../../../utils/helper'
import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
export default {
name: 'CiTypeChoice',
@@ -100,7 +98,7 @@ export default {
return {
ciTypeList: [],
ciTypeName: '',
selectNum: null,
selectNum: 0,
selectCiTypeAttrList: [],
visible: false,
checkedAttrs: [],
@@ -109,7 +107,6 @@ export default {
parentsType: [],
parentsForm: {},
checkedParents: [],
canEdit: {},
}
},
created: function() {
@@ -132,6 +129,7 @@ export default {
methods: {
selectCiType(el) {
// 当选择好模板类型时的回调函数
this.selectNum = el
getCITypeAttributesById(el).then((res) => {
this.$emit('getCiTypeAttr', res)
this.selectCiTypeAttrList = res
@@ -145,16 +143,8 @@ export default {
},
openModal() {
getCITypeParent(this.selectNum).then(async (res) => {
for (let i = 0; i < res.parents.length; i++) {
await getCanEditByParentIdChildId(res.parents[i].id, this.selectNum).then((p_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.parents[i].id]: p_res.result,
}
})
}
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
getCITypeParent(this.selectNum).then((res) => {
this.parentsType = res.parents
const _parentsForm = {}
res.parents.forEach((item) => {
const _find = item.attributes.find((attr) => attr.id === item.unique_id)

View File

@@ -1,7 +1,6 @@
<template>
<div class="cmdb-batch-upload-table">
<vxe-table
ref="xTable"
stripe
show-header-overflow
show-overflow=""
@@ -9,8 +8,6 @@
class="ops-stripe-table"
:max-height="200"
:data="dataSource"
resizable
:row-style="rowStyle"
>
<vxe-column type="seq" width="40" />
<vxe-column
@@ -39,9 +36,7 @@ export default {
},
},
data() {
return {
errorIndexList: [],
}
return {}
},
computed: {
columns() {
@@ -69,33 +64,7 @@ export default {
return _.cloneDeep(this.uploadData)
},
},
watch: {
uploadData() {
this.errorIndexList = []
},
},
methods: {
uploadResultError(index) {
const _errorIndexList = _.cloneDeep(this.errorIndexList)
_errorIndexList.push(index)
this.errorIndexList = _errorIndexList
},
rowStyle({ rowIndex }) {
if (this.errorIndexList.includes(rowIndex)) {
return 'color:red;'
}
},
downloadError() {
const data = this.uploadData.filter((item, index) => this.errorIndexList.includes(index))
this.$refs.xTable.exportData({
data,
type: 'xlsx',
columnFilterMethod({ column }) {
return column.property
},
})
},
},
methods: {},
}
</script>
<style lang="less" scoped>

View File

@@ -7,7 +7,7 @@
accept=".xls,.xlsx"
:showUploadList="false"
:fileList="fileList"
:disabled="!ciType || isUploading"
:disabled="!ciType"
>
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
<p class="ant-upload-text">点击或拖拽文件至此上传</p>
@@ -29,11 +29,7 @@ export default {
ciType: {
type: Number,
default: 0,
},
isUploading: {
type: Boolean,
default: false,
},
}
},
data() {
return {
@@ -44,20 +40,7 @@ export default {
percent: 0,
}
},
watch: {
ciType: {
handler(newValue) {
if (!newValue) {
this.ciItemNum = 0
this.fileList = []
this.dataList = []
this.progressStatus = 'active'
this.percent = 0
this.$emit('uploadDone', this.dataList)
}
},
},
},
methods: {
customRequest(data) {
this.fileList = [data.file]

View File

@@ -34,10 +34,6 @@ export default {
required: true,
type: String,
},
isUploading: {
type: Boolean,
default: false,
},
},
data: function() {
return {
@@ -55,38 +51,33 @@ export default {
},
},
methods: {
async sleep(n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, n || 5)
})
},
async upload2Server() {
this.visible = true
this.success = 0
this.errorNum = 0
this.errorItems = []
const floor = Math.ceil(this.total / 6)
for (let i = 0; i < floor; i++) {
if (this.isUploading) {
const itemList = this.upLoadData.slice(6 * i, 6 * i + 6)
const promises = itemList.map((x) => uploadData(this.ciType, x))
await Promise.allSettled(promises)
.then((res) => {
res.forEach((r, j) => {
if (r.status === 'fulfilled') {
this.success += 1
} else {
this.errorItems.push(r?.reason?.response?.data.message ?? '请求出现错误,请稍后再试')
this.errorNum += 1
this.$emit('uploadResultError', 6 * i + j)
}
})
})
.finally(() => {
this.complete += 6
})
} else {
break
}
}
if (this.isUploading) {
this.$emit('uploadResultDone')
this.$message.success('批量上传已完成')
for (let i = 0; i < this.total; i++) {
// await this.sleep(20)
const item = this.upLoadData[i]
await uploadData(this.ciType, item)
.then((res) => {
console.log(res)
this.success += 1
})
.catch((err) => {
this.errorNum += 1
this.errorItems.push(((err.response || {}).data || {}).message || '请求出现错误,请稍后再试')
})
.finally(() => {
this.complete += 1
})
}
},
},

View File

@@ -108,7 +108,7 @@
<span>{{ col.title }}</span>
</span>
</template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
@@ -145,18 +145,6 @@
</span>
</a-select-option>
</a-select>
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
:style="{ width: '100%', height: '32px' }"
v-model="row[col.field]"
placeholder="请选择"
v-else-if="col.is_list"
:showArrow="false"
mode="tags"
class="ci-table-edit-select"
allowClear
>
</a-select>
</template>
<template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -666,19 +654,13 @@ export default {
let errorNum = 0
this.loading = true
this.loadTip = `正在删除...`
const floor = Math.ceil(this.selectedRowKeys.length / 6)
for (let i = 0; i < floor; i++) {
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
const promises = itemList.map((x) => deleteCI(x, false))
await Promise.allSettled(promises)
.then((res) => {
res.forEach((r) => {
if (r.status === 'fulfilled') {
successNum += 1
} else {
errorNum += 1
}
})
for (let i = 0; i < this.selectedRowKeys.length; i++) {
await deleteCI(this.selectedRowKeys[i], false)
.then(() => {
successNum += 1
})
.catch(() => {
errorNum += 1
})
.finally(() => {
this.loadTip = `正在删除${this.selectedRowKeys.length}成功${successNum}失败${errorNum}`
@@ -891,10 +873,6 @@ export default {
unsubscribe(ciType, type = 'all') {
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')]
Promise.all(promises).then(() => {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (Number(ciType) === Number(lastTypeId)) {
localStorage.setItem('ops_ci_typeid', '')
}
this.$message.success('取消订阅成功')
this.resetRoute()
this.$router.push('/cmdb/preference')

View File

@@ -122,12 +122,12 @@
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button>
</a-form>
</template>
<!-- </a-form> -->
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</CustomDrawer>
</template>
<script>
import _ from 'lodash'
import moment from 'moment'
import { Select, Option } from 'element-ui'
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
@@ -135,7 +135,7 @@ import { addCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import { valueTypeMap } from '../../../utils/const'
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
export default {
name: 'CreateInstanceForm',
@@ -166,7 +166,6 @@ export default {
attributesByGroup: [],
parentsType: [],
parentsForm: {},
canEdit: {},
}
},
computed: {
@@ -301,16 +300,8 @@ export default {
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
})
if (action === 'create') {
getCITypeParent(this.typeId).then(async (res) => {
for (let i = 0; i < res.parents.length; i++) {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.parents[i].id]: p_res.result,
}
})
}
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
getCITypeParent(this.typeId).then((res) => {
this.parentsType = res.parents
const _parentsForm = {}
res.parents.forEach((item) => {
const _find = item.attributes.find((attr) => attr.id === item.unique_id)

View File

@@ -59,9 +59,6 @@
{{ ci[attr.name] }}
</span>
</template>
<template v-else-if="attr.is_list">
<span> {{ ci[attr.name].join(',') }}</span>
</template>
<template v-else>{{ getName(ci[attr.name]) }}</template>
</span>
<template v-else>
@@ -78,6 +75,7 @@
placeholder="请选择"
v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'"
:multiple="attr.is_list"
showSearch
allowClear
size="small"
@@ -105,23 +103,6 @@
</span>
</a-select-option>
</a-select>
<a-select
:style="{ width: '100%' }"
v-decorator="[
attr.name,
{
rules: [{ required: attr.is_required }],
},
]"
placeholder="请选择"
v-else-if="attr.is_list"
mode="tags"
showSearch
allowClear
size="small"
:getPopupContainer="(trigger) => trigger.parentElement"
>
</a-select>
<a-input-number
size="small"
v-decorator="[
@@ -241,7 +222,7 @@ export default {
this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) {
this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] || null,
[`${this.attr.name}`]: this.ci[this.attr.name].join(',') || null,
})
return
}

View File

@@ -15,7 +15,6 @@
<div class="ci-detail-relation-table-title">
{{ parent.alias || parent.name }}
<a
:disabled="!canEdit[parent.id]"
@click="
() => {
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
@@ -24,7 +23,6 @@
><a-icon
type="plus-square"
/></a>
<span v-if="!canEdit[parent.id]">当前模型关系为多对多请前往关系视图进行增删操作</span>
</div>
<vxe-grid
v-if="firstCIs[parent.name]"
@@ -40,14 +38,7 @@
>
<template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)">
<a
:disabled="!canEdit[parent.id]"
:style="{
color: !canEdit[parent.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
}"
><a-icon
type="delete"
/></a>
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
</a-popconfirm>
</template>
</vxe-grid>
@@ -59,7 +50,6 @@
<div class="ci-detail-relation-table-title">
{{ child.alias || child.name }}
<a
:disabled="!canEdit[child.id]"
@click="
() => {
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
@@ -68,7 +58,6 @@
><a-icon
type="plus-square"
/></a>
<span v-if="!canEdit[child.id]">当前模型关系为多对多请前往关系视图进行增删操作</span>
</div>
<vxe-grid
v-if="secondCIs[child.name]"
@@ -83,14 +72,7 @@
>
<template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)">
<a
:disabled="!canEdit[child.id]"
:style="{
color: !canEdit[child.id] ? 'rgba(0, 0, 0, 0.25)' : 'red',
}"
><a-icon
type="delete"
/></a>
<a :style="{ color: 'red' }"><a-icon type="delete"/></a>
</a-popconfirm>
</template>
</vxe-grid>
@@ -103,7 +85,7 @@
<script>
import _ from 'lodash'
import { getCITypeChildren, getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import { getCITypeChildren, getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
import Node from './ciDetailRelationTopo/node.js'
@@ -136,7 +118,6 @@ export default {
secondCIColumns: {},
firstCIJsonAttr: {},
secondCIJsonAttr: {},
canEdit: {},
}
},
computed: {
@@ -312,85 +293,76 @@ export default {
.catch((e) => {})
},
async getParentCITypes() {
const res = await getCITypeParent(this.typeId)
this.parentCITypes = res.parents
for (let i = 0; i < res.parents.length; i++) {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.parents[i].id]: p_res.result,
}
})
}
const firstCIColumns = {}
const firstCIJsonAttr = {}
res.parents.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
firstCIJsonAttr[item.id] = jsonAttr
firstCIColumns[item.id] = columns
firstCIColumns[item.id].push({
key: 'p_operation',
field: 'operation',
title: '操作',
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
await getCITypeParent(this.typeId)
.then((res) => {
this.parentCITypes = res.parents
this.firstCIColumns = firstCIColumns
this.firstCIJsonAttr = firstCIJsonAttr
const firstCIColumns = {}
const firstCIJsonAttr = {}
res.parents.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
firstCIJsonAttr[item.id] = jsonAttr
firstCIColumns[item.id] = columns
firstCIColumns[item.id].push({
key: 'p_operation',
field: 'operation',
title: '操作',
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.firstCIColumns = firstCIColumns
this.firstCIJsonAttr = firstCIJsonAttr
})
.catch((e) => {})
},
async getChildCITypes() {
const res = await getCITypeChildren(this.typeId)
await getCITypeChildren(this.typeId)
.then((res) => {
this.childCITypes = res.children
this.childCITypes = res.children
for (let i = 0; i < res.children.length; i++) {
await getCanEditByParentIdChildId(this.typeId, res.children[i].id).then((c_res) => {
this.canEdit = {
..._.cloneDeep(this.canEdit),
[res.children[i].id]: c_res.result,
}
})
}
const secondCIColumns = {}
const secondCIJsonAttr = {}
res.children.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
secondCIJsonAttr[item.id] = jsonAttr
secondCIColumns[item.id] = columns
secondCIColumns[item.id].push({
key: 'c_operation',
field: 'operation',
title: '操作',
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
const secondCIColumns = {}
const secondCIJsonAttr = {}
res.children.forEach((item) => {
const columns = []
const jsonAttr = []
item.attributes.forEach((attr) => {
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' })
if (attr.value_type === '6') {
jsonAttr.push(attr.name)
}
})
secondCIJsonAttr[item.id] = jsonAttr
secondCIColumns[item.id] = columns
secondCIColumns[item.id].push({
key: 'c_operation',
field: 'operation',
title: '操作',
width: '60px',
fixed: 'right',
slots: {
default: 'operation_default',
},
align: 'center',
})
})
this.secondCIColumns = secondCIColumns
this.secondCIJsonAttr = secondCIJsonAttr
this.secondCIColumns = secondCIColumns
this.secondCIJsonAttr = secondCIJsonAttr
})
.catch((e) => {})
},
reload() {
this.init()

View File

@@ -28,6 +28,7 @@
placeholder="请选择"
v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'"
:multiple="attr.is_list"
showSearch
allowClear
>

View File

@@ -53,8 +53,8 @@ export default {
return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 })
})
await Promise.all(promises)
.then((res) => {
this.getCITypeDiscovery(res[0].id)
.then(() => {
this.getCITypeDiscovery(this.selectedIds[0].id)
this.$message.success('添加成功')
})
.catch(() => {

View File

@@ -2,22 +2,20 @@
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }">
<div v-if="adCITypeList && adCITypeList.length">
<a-tabs size="small" v-model="currentTab">
<a-tab-pane v-for="item in adCITypeList" :key="item.id">
<a-tab-pane v-for="item in adCITypeList" :key="item.adr_id">
<a-space slot="tab">
<span v-if="item.extra_option && item.extra_option.alias">{{ item.extra_option.alias }}</span>
<span v-else>{{ getADCITypeParam(item.adr_id) }}</span>
<span>{{ getADCITypeParam(item.adr_id) }}</span>
<a-icon type="close-circle" @click="(e) => deleteADT(e, item)" />
</a-space>
<AttrADTabpane
:ref="`attrAdTabpane_${item.id}`"
:adr_id="item.adr_id"
:ref="`attrAdTabpane_${item.adr_id}`"
:currentTab="item.adr_id"
:adrList="adrList"
:adCITypeList="adCITypeList"
:currentAdt="item"
:ciTypeAttributes="ciTypeAttributes"
:currentAdr="getADCITypeParam(item.adr_id, undefined, true)"
@openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)"
@handleSave="getCITypeDiscovery"
/>
</a-tab-pane>
<a-space
@@ -137,7 +135,7 @@ export default {
await getCITypeDiscovery(this.CITypeId).then((res) => {
this.adCITypeList = res.filter((item) => item.adr_id)
if (res && res.length && !this.currentTab) {
this.currentTab = res[0].id
this.currentTab = res[0].adr_id
}
if (currentTab) {
this.currentTab = currentTab
@@ -158,7 +156,7 @@ export default {
e.stopPropagation()
const that = this
this.$confirm({
title: `确认删除 ${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}`,
title: `确认删除 ${this.getADCITypeParam(item.adr_id)}`,
content: (h) => (
<div>
<a-checkbox v-model={that.deletePlugin}>删除插件</a-checkbox>
@@ -166,22 +164,18 @@ export default {
),
onOk() {
deleteCITypeDiscovery(item.id).then(async () => {
if (that.currentTab === item.id) {
if (that.currentTab === item.adr_id) {
that.currentTab = ''
}
that.deletePlugin = false
that.$message.success('删除成功!')
that.getCITypeDiscovery()
if (that.deletePlugin) {
await deleteDiscovery(item.adr_id).finally(() => {
that.deletePlugin = false
})
await deleteDiscovery(item.adr_id)
}
that.deletePlugin = false
})
},
onCancel() {
that.deletePlugin = false
},
onCancel() {},
})
},
openEditDrawer(data, type, adType) {
@@ -189,12 +183,12 @@ export default {
},
async updateNotInner(adr) {
const _idx = this.adCITypeList.findIndex((item) => item.adr_id === adr.id)
let res
if (_idx < 0) {
res = await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
}
await this.getDiscovery()
await this.getCITypeDiscovery(res?.id ?? undefined)
await this.getCITypeDiscovery()
this.currentTab = adr.id
this.$nextTick(() => {
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
})

View File

@@ -14,7 +14,6 @@
<span>编辑</span>
</a-space>
</a>
<div>别名<a-input v-model="alias" style="width:200px;" /></div>
<div class="attr-ad-header">字段映射</div>
<vxe-table
v-if="adrType === 'agent'"
@@ -57,7 +56,7 @@
:ruleName="adrName"
:ciTypeAttributes="ciTypeAttributes"
:adCITypeList="adCITypeList"
:currentTab="adr_id"
:currentTab="currentTab"
:style="{ marginBottom: '20px' }"
/>
<a-form-model
@@ -134,7 +133,7 @@ export default {
name: 'AttrADTabpane',
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
props: {
adr_id: {
currentTab: {
type: Number,
default: 0,
},
@@ -188,7 +187,6 @@ export default {
},
],
form3: this.$form.createForm(this, { name: 'snmp_form' }),
alias: '',
}
},
computed: {
@@ -207,7 +205,7 @@ export default {
},
agentTypeRadioList() {
const { permissions = [] } = this.userRoles
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') {
if (permissions.includes('cmdb_admin') || permissions.includes('admin')) {
return [
{ value: 'all', label: '所有节点' },
{ value: 'agent_id', label: '指定节点' },
@@ -223,9 +221,8 @@ export default {
mounted() {},
methods: {
init() {
const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id))
const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id))
this.alias = _findADT?.extra_option?.alias ?? ''
const _find = this.adrList.find((item) => Number(item.id) === Number(this.currentTab))
const _findADT = this.adCITypeList.find((item) => Number(item.adr_id) === Number(this.currentTab))
if (this.adrType === 'http') {
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
this.form2 = {
@@ -297,7 +294,7 @@ export default {
this.cron = cron
},
handleSave() {
const { currentAdt, alias } = this
const { currentAdt } = this
let params
if (this.adrType === 'http') {
params = {
@@ -363,15 +360,9 @@ export default {
return
}
}
if (params.extra_option) {
params.extra_option.alias = alias
} else {
params.extra_option = {}
params.extra_option.alias = alias
}
putCITypeDiscovery(currentAdt.id, params).then((res) => {
this.$message.success('保存成功')
this.$emit('handleSave')
})
},
handleOpenCmdb() {

View File

@@ -372,7 +372,7 @@ export default {
},
async open(property, attrList) {
this.visible = true
await this.getNoticeConfigAppBot()
this.getNoticeConfigAppBot()
this.attrList = attrList
if (property.has_trigger) {
this.triggerId = property.trigger.id

View File

@@ -372,7 +372,7 @@ export default {
width: 3,
fontColor: '#ffffff',
bgColor: ['#6ABFFE', '#5375EB'],
chartColor: '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF', // 图表颜色
chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色
isShowPreview: false,
filterExp: undefined,
previewData: null,
@@ -410,7 +410,7 @@ export default {
this.width = width
this.chartType = chartType
this.filterExp = item?.options?.filter ?? ''
this.chartColor = item?.options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF'
this.chartColor = item?.options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD'
this.isShadow = item?.options?.isShadow ?? false
if (chartType === 'count') {

View File

@@ -24,7 +24,7 @@ export const category_1_bar_options = (data, options) => {
})
return {
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
grid: {
top: 15,
left: 'left',
@@ -83,7 +83,7 @@ export const category_1_bar_options = (data, options) => {
export const category_1_line_options = (data, options) => {
const xData = Object.keys(data)
return {
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
grid: {
top: 15,
left: 'left',
@@ -117,7 +117,7 @@ export const category_1_line_options = (data, options) => {
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[0] // 0% 处的颜色
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[0] // 0% 处的颜色
}, {
offset: 1, color: '#ffffff' // 100% 处的颜色
}],
@@ -131,7 +131,7 @@ export const category_1_line_options = (data, options) => {
export const category_1_pie_options = (data, options) => {
return {
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
grid: {
top: 10,
left: 'left',
@@ -181,7 +181,7 @@ export const category_2_bar_options = (data, options, chartType) => {
})
const legend = [...new Set(_legend)]
return {
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
grid: {
top: 15,
left: 'left',
@@ -249,7 +249,7 @@ export const category_2_bar_options = (data, options, chartType) => {
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(',')[index % 8] // 0% 处的颜色
offset: 0, color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(',')[index % 8] // 0% 处的颜色
}, {
offset: 1, color: '#ffffff' // 100% 处的颜色
}],
@@ -269,7 +269,7 @@ export const category_2_pie_options = (data, options) => {
})
})
return {
color: (options?.chartColor ?? '#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF').split(','),
color: (options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD').split(','),
grid: {
top: 15,
left: 'left',

View File

@@ -24,8 +24,10 @@ export default {
data() {
return {
list: [
'#5DADF2,#86DFB7,#5A6F96,#7BD5FF,#FFB980,#4D58D6,#D9B6E9,#8054FF',
'#9BA1F9,#0F2BA8,#A2EBFE,#4982F6,#FEB09C,#6C78E8,#FFDDAB,#4D66BD',
'#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD',
'#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB',
'#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB',
'#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467',
],
}
},

View File

@@ -61,7 +61,7 @@
</vxe-column>
<vxe-column field="type_id" title="模型" width="150px">
<template #default="{ row }">
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }}
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id}}
</template>
</vxe-column>
<vxe-column field="changeDescription" title="描述">

View File

@@ -314,12 +314,6 @@ export default {
}
Promise.all(promises).then(() => {
if (type === 'all' || type === 'ci') {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (Number(ciType.id) === Number(lastTypeId)) {
localStorage.setItem('ops_ci_typeid', '')
}
}
that.$message.success('取消订阅成功')
that.resetRoute()
})

View File

@@ -25,7 +25,7 @@
:expandedKeys="expandedKeys"
>
<a-icon slot="switcherIcon" type="down" />
<template #title="{ key: treeKey, title, isLeaf }">
<template #title="{ key: treeKey, title,isLeaf }">
<ContextMenu
:title="title"
:treeKey="treeKey"
@@ -135,7 +135,7 @@
{{ col.title }}</span
>
</template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }">
<template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
@@ -172,18 +172,6 @@
</span>
</a-select-option>
</a-select>
<a-select
:getPopupContainer="(trigger) => trigger.parentElement"
:style="{ width: '100%', height: '32px' }"
v-model="row[col.field]"
placeholder="请选择"
v-else-if="col.is_list"
:showArrow="false"
mode="tags"
class="ci-table-edit-select"
allowClear
>
</a-select>
</template>
<template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -404,7 +392,6 @@ export default {
origShowTypes: [],
leaf2showTypes: {},
node2ShowTypes: {},
level2constraint: {},
leaf: [],
typeId: null,
viewId: null,
@@ -495,11 +482,11 @@ export default {
},
inject: ['reload'],
watch: {
'$route.path': function (newPath, oldPath) {
'$route.path': function(newPath, oldPath) {
this.viewId = this.$route.params.viewId
this.reload()
},
pageNo: function (newPage, oldPage) {
pageNo: function(newPage, oldPage) {
this.loadData({ pageNo: newPage }, undefined, this.sortByTable)
},
},
@@ -608,17 +595,6 @@ export default {
}
} else {
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
q += `&ancestor_ids=${this.treeKeys
.slice(0, this.treeKeys.length - 1)
.map((item) => item.split('%')[0])
.join(',')}`
}
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
let level = []
@@ -673,19 +649,7 @@ export default {
if (refreshType === 'refreshNumber') {
const promises = this.treeKeys.map((key, index) => {
let ancestor_ids
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
ancestor_ids = `${this.treeKeys
.slice(0, index)
.map((item) => item.split('%')[0])
.join(',')}`
}
statisticsCIRelation({
ancestor_ids,
root_ids: key.split('%')[0],
level: this.treeKeys.length - index,
type_ids: this.showTypes.map((type) => type.id).join(','),
@@ -816,36 +780,16 @@ export default {
const index = topo_flatten.findIndex((id) => id === typeId)
const _type = topo_flatten[index + 1]
if (_type) {
let q = `q=_type:${_type}&root_id=${rootId}&level=1&count=10000`
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
q += `&ancestor_ids=${this.treeKeys
.slice(0, this.treeKeys.length - 1)
.map((item) => item.split('%')[0])
.join(',')}`
}
searchCIRelation(q).then(async (res) => {
searchCIRelation(`q=_type:${_type}&root_id=${rootId}&level=1&count=10000`).then(async (res) => {
const facet = []
const ciIds = []
res.result.forEach((item) => {
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
ciIds.push(item._id)
})
let ancestor_ids
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
ancestor_ids = `${this.treeKeys.map((item) => item.split('%')[0]).join(',')}`
}
const promises = level.map((_level) => {
if (_level > 1) {
return statisticsCIRelation({
ancestor_ids,
root_ids: ciIds.join(','),
level: _level - 1,
type_ids: this.showTypes.map((type) => type.id).join(','),
@@ -945,7 +889,6 @@ export default {
this.origShowTypeIds = showTypeIds
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_types
this.node2ShowTypes = this.relationViews.views[this.viewName].node2show_types
this.level2constraint = this.relationViews.views[this.viewName].level2constraint
this.leaf = this.relationViews.views[this.viewName].leaf
this.currentView = `${this.viewId}`
this.typeId = this.levels[0][0]
@@ -980,17 +923,6 @@ export default {
const _tempTree = splitTreeKey[splitTreeKey.length - 1].split('%')
const firstCIObj = JSON.parse(_tempTree[2])
const firstCIId = _tempTree[0]
let ancestor_ids
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
const ancestor = treeKey
.split('@^@')
.slice(0, menuKey === 'delete' ? treeKey.split('@^@').length - 2 : treeKey.split('@^@').length - 1)
ancestor_ids = ancestor.map((item) => item.split('%')[0]).join(',')
}
if (menuKey === 'delete') {
const _tempTreeParent = splitTreeKey[splitTreeKey.length - 2].split('%')
const that = this
@@ -1003,17 +935,16 @@ export default {
</div>
),
onOk() {
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
deleteCIRelationView(_tempTreeParent[0], _tempTree[0]).then((res) => {
that.$message.success('删除成功!')
setTimeout(() => {
that.reload()
}, 500)
that.reload()
})
},
})
} else {
const childTypeId = menuKey
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
console.log(menuKey)
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children')
}
}
},
@@ -1033,21 +964,9 @@ export default {
onOk() {
const _tempTree = that.treeKeys[that.treeKeys.length - 1].split('%')
const first_ci_id = Number(_tempTree[0])
let ancestor_ids
if (
Object.keys(that.level2constraint).some(
(le) => le < Object.keys(that.level2constraint).length && that.level2constraint[le] === '2'
)
) {
ancestor_ids = `${that.treeKeys
.slice(0, that.treeKeys.length - 1)
.map((item) => item.split('%')[0])
.join(',')}`
}
batchDeleteCIRelation(
that.selectedRowKeys.map((item) => item._id),
[first_ci_id],
ancestor_ids
[first_ci_id]
).then((res) => {
that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve()
@@ -1211,10 +1130,7 @@ export default {
const $table = this.$refs['xTable']
const data = {}
this.columns.forEach((item) => {
if (
!(item.field in this.initialPasswordValue) &&
!_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])
) {
if (!(item.field in this.initialPasswordValue) && !_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])) {
data[item.field] = row[item.field] ?? null
}
})
@@ -1264,18 +1180,7 @@ export default {
},
sumbitFromCreateInstance({ ci_id }) {
const first_ci_id = this.treeKeys[this.treeKeys.length - 1].split('%')[0]
let ancestor_ids
if (
Object.keys(this.level2constraint).some(
(le) => le < Object.keys(this.level2constraint).length && this.level2constraint[le] === '2'
)
) {
ancestor_ids = `${this.treeKeys
.slice(0, this.treeKeys.length - 1)
.map((item) => item.split('%')[0])
.join(',')}`
}
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
addCIRelationView(first_ci_id, ci_id).then((res) => {
setTimeout(() => {
this.loadData({}, 'refreshNumber')
}, 500)
@@ -1365,7 +1270,9 @@ export default {
})
Promise.all(promises)
.then((res) => {
that.$message.success('删除成功')
that.$message.success({
message: '删除成功',
})
})
.catch((e) => {
console.log(e)

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