Compare commits

..

662 Commits

Author SHA1 Message Date
pycook
5aff5d728d fix(api): check rack u slot 2024-11-27 15:39:53 +08:00
pycook
464b9d5394 chore: release v2.4.15 2024-11-27 15:14:58 +08:00
Leo Song
9c4cc20e13 Merge pull request #643 from veops/dev_ui_dcim
Dev UI dcim
2024-11-27 11:15:15 +08:00
songlh
c3c8602207 feat(ui): dcim - update rack list filter 2024-11-27 11:14:21 +08:00
songlh
c961e288af feat(ui): add dcim 2024-11-27 10:26:05 +08:00
pycook
e22b0c5290 feat(api): dcim dev (#642) 2024-11-26 18:56:59 +08:00
pycook
900cf1f617 feat(api): update ipam 2024-11-25 20:19:01 +08:00
Leo Song
f28ad4d041 Merge pull request #639 from veops/dev_ui_ipam
feat(ui): ipam - add batch assign
2024-11-13 10:04:53 +08:00
songlh
d2698b05c0 feat(ui): ipam - add batch assign 2024-11-13 10:03:13 +08:00
Leo Song
6f1332148c Merge pull request #638 from veops/dev_ui_ipam
fix(ui): ipam - filter search value error
2024-11-12 10:59:04 +08:00
songlh
5fa18eeb00 fix(ui): ipam - filter search value error 2024-11-12 10:58:09 +08:00
pycook
03fdf5c004 chore: release v2.4.14 2024-11-11 19:02:05 +08:00
pycook
f277cf088e fix(api): ipam assign address 2024-11-11 18:56:09 +08:00
pycook
b1f8a0024b Dev api ipam (#637)
* feat: ipam api

* fix: ipam
2024-11-11 18:17:37 +08:00
Leo Song
aae43a53b5 Merge pull request #636 from veops/dev_ui_ipam
feat(ui): add ipam
2024-11-11 16:50:35 +08:00
songlh
8c2cdb1ca4 feat(ui): add ipam 2024-11-11 16:49:53 +08:00
thexqn
57d4bf5548 fix(search): correct type_id usage in CI relation filtering (#633) 2024-11-11 15:53:24 +08:00
dagongren
5e7c6199bf feat:add employee work_region (#634)
* feat:add employee work_region

* env
2024-11-07 11:52:55 +08:00
Zhuohao Li
28dca7f086 fix permission bug (#632)
不同的appid下可能有相同的resource type name.
2024-10-27 14:04:34 +08:00
pycook
4a3c21eec4 feat(api): add builtin attributes (#631) 2024-10-22 18:21:07 +08:00
Leo Song
5d28c28023 Merge pull request #630 from veops/dev_ui_241022
fix(ui): update userPanel style
2024-10-22 14:12:14 +08:00
songlh
ba6edb3abe fix(ui): update userPanel style 2024-10-22 14:11:36 +08:00
Leo Song
2f1d57cee1 Merge pull request #629 from veops/dev_ui_241022
feat(ui): add userPanel component
2024-10-22 14:01:02 +08:00
songlh
4111c634d9 feat(ui): add userPanel component 2024-10-22 13:59:38 +08:00
pycook
f1fababa3d feat(api): save relation search option 2024-10-18 11:03:31 +08:00
pycook
fe6373422e chore: release v2.4.13 2024-10-18 09:52:26 +08:00
pycook
b3ea776886 Merge pull request #628 from veops/dev_api_relation_path_search
feat(api): relation path search
2024-10-17 19:47:34 +08:00
pycook
c4d2ce313d feat(api): relation path search 2024-10-17 19:46:39 +08:00
Leo Song
20103a0fe6 Merge pull request #627 from veops/dev_ui_241017
feat(ui): add relation search
2024-10-17 17:56:13 +08:00
songlh
394e2aeac6 feat(ui): add relation search 2024-10-17 17:55:36 +08:00
pycook
8f7d78c26c Merge pull request #623 from veops/dev_api_relation_path_search
Dev api relation path search
2024-09-30 17:33:45 +08:00
pycook
7eecf3cec3 feat(api): add api /ci_type_relations/path 2024-09-26 20:32:21 +08:00
pycook
f6e9c443f7 Merge pull request #622 from novohool/master
Update cache support for environment variables in settings.example.py
2024-09-26 18:09:54 +08:00
pycook
857cbd82fd feat(api): add relation path search 2024-09-26 17:59:08 +08:00
novohool
9a14296e02 Update settings.example.py 2024-09-26 17:00:51 +08:00
pycook
f638b52759 fix(api): change records of attribute values for date and datetime 2024-09-25 19:37:08 +08:00
pycook
78da728105 fix(api): search for multiple CIType 2024-09-24 17:46:27 +08:00
pycook
eb69029a51 fix(api): ci relations search 2024-09-23 19:46:43 +08:00
Leo Song
07a097eba2 Merge pull request #619 from veops/dev_ui_240920
feat: update computed attr tip
2024-09-20 15:36:55 +08:00
songlh
e843e3eac9 feat: update computed attr tip 2024-09-20 15:36:19 +08:00
Leo Song
7308cfa6c2 Merge pull request #617 from veops/dev_ui_240914
dev_ui_240914
2024-09-14 17:28:42 +08:00
songlh
64ea4fb21f fix(ui): operation history search expand error 2024-09-14 17:27:57 +08:00
songlh
e15cefaa38 fix(ui): employeeTreeSelect display error 2024-09-14 17:26:33 +08:00
pycook
f32339b969 Merge pull request #616 from thexqn/optimize_history
feat: Add show_attr value column to operation history table
2024-09-14 11:55:01 +08:00
thexqn
131d213a73 优化CITypeCache的调用方式 2024-09-14 11:30:45 +08:00
thexqn
ff98777689 feat(cmdb): 添加操作历史表的唯一值列 (Add unique value column to operation history table) 2024-09-14 01:13:07 +08:00
thexqn
383d4c88ed feat: Add unique value column to operation history table 2024-09-13 23:44:40 +08:00
Leo Song
bb7157e292 Merge pull request #615 from veops/dev_ui_240913
feat(ui): add employeeTreeSelect otherOptions prop
2024-09-13 18:36:48 +08:00
songlh
b1a82f1a67 feat(ui): add employeeTreeSelect otherOptions prop 2024-09-13 18:36:24 +08:00
pycook
de86ea3852 fix(api): remote ip for login log 2024-09-10 11:41:35 +08:00
pycook
bf05ea240e feat(api): acl supports channel 2024-09-09 15:28:20 +08:00
Leo Song
8ec0d619d7 Merge pull request #613 from veops/dev_ui_240909
feat(ui): add SplitPane calcBasedParent prop
2024-09-09 10:45:27 +08:00
songlh
61f8c463bc feat(ui): add SplitPane calcBasedParent prop 2024-09-09 10:44:58 +08:00
Leo Song
9b4dc3e43b Merge pull request #611 from veops/dev_ui_240903
feat: update icon select
2024-09-03 16:41:18 +08:00
songlh
9e69be8256 feat: update icon select 2024-09-03 16:40:46 +08:00
pycook
251b9e7fd5 chore: release v2.4.12 2024-09-03 14:18:53 +08:00
Leo Song
f3cc12f1f9 Merge pull request #610 from veops/dev_ui_240903
fix(ui): build error
2024-09-03 13:15:43 +08:00
songlh
56f03e1624 fix(ui): build error 2024-09-03 13:14:56 +08:00
Leo Song
42ad2b6dde Merge pull request #609 from veops/dev_ui_240903
feat: update resource search
2024-09-03 11:30:08 +08:00
songlh
5aba1ff257 feat: update resource search 2024-09-03 11:29:32 +08:00
pycook
417e8fe349 perf(api): resource search supports recent searches and my favorites 2024-09-02 16:56:06 +08:00
Leo Song
02235d8cc0 Merge pull request #607 from veops/dev_ui_240828
fix(ui): ci choice attr error
2024-08-28 18:52:43 +08:00
songlh
00c7a644a2 fix(ui): ci choice attr error 2024-08-28 18:52:20 +08:00
pycook
f3e8757450 fix(api): CIType templates import 2024-08-28 17:52:15 +08:00
Leo Song
f0749341ba Merge pull request #606 from veops/dev_ui_240828
feat(ui): update ui
2024-08-28 16:55:45 +08:00
songlh
89da671e46 feat(ui): update ui 2024-08-28 16:55:07 +08:00
Leo Song
0e60aae076 Merge pull request #605 from veops/dev_ui_240827
Dev UI 240827
2024-08-27 10:33:30 +08:00
songlh
4dfa97d404 fix(ui): resource search export error 2024-08-27 10:32:53 +08:00
songlh
9b778f9bc7 fix(ui): update create attr icon 2024-08-27 10:32:25 +08:00
pycook
eafb5f053a fix(api): custom dashboard for enum type 2024-08-26 22:31:58 +08:00
Leo Song
834054e216 Merge pull request #604 from veops/dev_ui_240826
feat: export remove reference attr
2024-08-26 22:22:43 +08:00
LH_R
a97cabbedc feat: export remove reference attr 2024-08-26 22:21:25 +08:00
Leo Song
ae77852d5f Merge pull request #603 from veops/dev_ui_240826
fix(ui): define value filter error
2024-08-26 21:40:03 +08:00
LH_R
611ee40dca fix(ui): define value filter error 2024-08-26 21:38:02 +08:00
pycook
c0d55b2126 Merge branch 'master' of github.com:veops/cmdb 2024-08-26 19:50:44 +08:00
pycook
2cc4499ef9 fix(api): custom dashboard 2024-08-26 19:50:22 +08:00
Leo Song
1268404bca Merge pull request #602 from veops/dev_ui_240826
fix(ui): menu icon display
2024-08-26 19:49:46 +08:00
songlh
570a9203c4 fix(ui): menu icon display 2024-08-26 19:47:23 +08:00
pycook
adae7b5519 chore: release v2.4.11 2024-08-26 18:44:23 +08:00
Leo Song
8a91ec7b11 Merge pull request #601 from veops/dev_ui_240826
fix(ui): some bugs
2024-08-26 18:35:44 +08:00
songlh
92fca65383 fix(ui): some bugs 2024-08-26 18:34:42 +08:00
Leo Song
4b8e6c2841 Merge pull request #600 from veops/dev_ui_240826
fix(ui): update builtIn params
2024-08-26 16:03:37 +08:00
songlh
ab240cb003 fix(ui): update builtIn params 2024-08-26 16:02:05 +08:00
Leo Song
61e62e4740 Merge pull request #599 from veops/dev_ui_240826
feat(ui) update CMDBFilterComp label
2024-08-26 15:16:07 +08:00
songlh
1fd72d6c78 feat(ui) update CMDBFilterComp label 2024-08-26 15:14:52 +08:00
Leo Song
51e16f6b23 Merge pull request #598 from veops/dev_ui_240826
Dev UI 240826
2024-08-26 15:09:01 +08:00
songlh
037378e384 fix(ui): create ad plugin params 2024-08-26 15:08:19 +08:00
songlh
631871a8cf feat(ui): update ci type choice config 2024-08-26 15:05:11 +08:00
pycook
6e02f6a21f fix(api): in query 2024-08-26 13:29:03 +08:00
pycook
a2224ba2ac Merge pull request #597 from veops/dev_api_0826
feat(api): enum supports
2024-08-26 12:15:05 +08:00
pycook
11a289aac9 feat(api): enum supports 2024-08-26 12:14:14 +08:00
Leo Song
55ab04dd28 Merge pull request #596 from thexqn/fix_order_bug
修复在继承模型的情况下,非继承属性与继承属性的排序以及其他分组的排序提示问题Fix order bug
2024-08-26 11:20:15 +08:00
thexqn
256a4f4844 清理多余的router-view 2024-08-23 16:55:21 +08:00
thexqn
018a349336 feat: 修复在继承模型的情况下,非继承属性与继承属性的排序以及其他分组的排序的问题 2024-08-23 16:50:37 +08:00
thexqn
8f62227adb feat: 修复在继承模型的情况下,非继承属性与继承属性的排序以及其他分组的排序的问题 2024-08-23 16:29:29 +08:00
thexqn
de51cb3e21 Merge branch 'veops:master' into master 2024-08-23 14:56:12 +08:00
Leo Song
ecb069cf14 Merge pull request #594 from veops/dev_ui_240820
feat(ui): add bool and reference type
2024-08-20 15:31:45 +08:00
songlh
937cb84393 feat(ui): add bool and reference type 2024-08-20 15:31:11 +08:00
pycook
40a4db06b5 Merge pull request #593 from veops/dev_api_0820
feat(api): supports bool and reference
2024-08-20 13:51:44 +08:00
pycook
cc98f903ea feat(api): supports bool and reference 2024-08-20 13:49:51 +08:00
kinyXu
fb7471ce04 feat: add attribute sorted tips for non-inherited attributes 2024-08-20 11:48:44 +08:00
Leo Song
e2872f041e Merge pull request #591 from veops/dev_ui_240813
refactor(ui): ci table
2024-08-13 17:15:15 +08:00
songlh
250fde127c refactor(ui): ci table 2024-08-13 17:14:05 +08:00
pycook
73dbb14944 Merge pull request #590 from lgphone/patch-1
bugfix: cmdb-api  auto_discovery add unique_value param
2024-08-07 16:05:56 +08:00
YangEver
73c9a6fa72 bugfix: cmdb-api auto_discovery add unique_value param
自动发现接口需要根据unique_value参数进行数据唯一性校验,此参数为必填项
2024-08-07 15:50:42 +08:00
Leo Song
09d957db79 Merge pull request #589 from veops/dev_ui_240807
Dev UI 240807
2024-08-07 14:42:01 +08:00
songlh
b73d796891 fix(ui): dashboard chart config 2024-08-07 14:41:22 +08:00
songlh
e7cbd0caa9 feat(ui): update common settings btn 2024-08-07 14:40:54 +08:00
pycook
3e4c385d91 fix(api): Dashboard using display attributes 2024-08-06 19:59:16 +08:00
pycook
3aac012ee9 chore: release v2.4.10 2024-07-31 16:42:26 +08:00
Leo Song
78d762cacc Merge pull request #588 from veops/dev_ui_240731
feat(ui): update ci type
2024-07-31 16:01:17 +08:00
songlh
c668ba7d3f feat(ui): update ci type 2024-07-31 16:00:40 +08:00
pycook
542a876ead fix(api): delete item for multi-value attributes 2024-07-30 20:05:21 +08:00
pycook
68b7497bba Merge pull request #587 from thexqn/master
修复在用了计算属性的情况下,批量上传功能可能出现的错误.
2024-07-30 09:18:30 +08:00
thexqn
dfbf3d462d 修复在用了计算属性的情况下,批量上传功能可能出现的错误. 2024-07-30 01:17:53 +08:00
pycook
692708fcba feat(api): Multi-valued attribute values ​​support adding and deleting 2024-07-29 19:55:07 +08:00
pycook
330b64edb3 chore: release v2.4.9 2024-07-26 17:03:00 +08:00
Leo Song
63a3074cb7 Merge pull request #585 from veops/fix_ui_240726
fix: discovery card eye btn
2024-07-26 16:50:43 +08:00
songlh
31b8cf49dc fix: discovery card eye btn 2024-07-26 16:49:28 +08:00
Leo Song
b01c335456 Merge pull request #584 from veops/dev_ui_240726
feat(ui): update auto discovery
2024-07-26 10:41:19 +08:00
songlh
002fef09e2 feat(ui): update auto discovery 2024-07-26 10:40:37 +08:00
pycook
175778a162 perf(api): auto discovery (#582) 2024-07-25 17:45:26 +08:00
Leo Song
5050a1bef5 Merge pull request #581 from veops/dev_ui_240722
feat: add accounts config
2024-07-22 17:39:18 +08:00
songlh
46a6cf67d6 feat: add accounts config 2024-07-22 17:38:48 +08:00
Leo Song
4e857c2775 Merge pull request #580 from veops/dev_ui_240716
feat: add history export
2024-07-16 13:46:40 +08:00
songlh
835df1bdeb feat: add history export 2024-07-16 13:45:31 +08:00
ivonGwy
579339d13c change pic 2024-07-15 16:23:34 +08:00
ivonGwy
629967ce82 change pic 2024-07-15 16:22:20 +08:00
pycook
3a00bfd236 chore: update docker compose 2024-07-11 14:29:34 +08:00
pycook
2e97ebd895 chore: release v2.4.8 2024-07-10 19:43:01 +08:00
Leo Song
eb6a813cbc Merge pull request #578 from veops/dev_ui_24071002
feat(ui): update
2024-07-10 19:19:08 +08:00
songlh
ff78face48 feat(ui): update 2024-07-10 19:18:22 +08:00
pycook
d55433c438 fix(api): computed attributes for multi values (#577) 2024-07-10 19:18:03 +08:00
Leo Song
daf0254616 Merge pull request #575 from veops/dev_ui_240710
fix: topoview search error
2024-07-10 10:12:11 +08:00
songlh
6b32009955 fix: topoview search error 2024-07-10 10:11:40 +08:00
Leo Song
d53288c1fb Merge pull request #574 from veops/dev_ui_240709
feat: update auto discovery
2024-07-09 09:45:25 +08:00
songlh
586d820a08 feat: update auto discovery 2024-07-09 09:44:28 +08:00
pycook
6776be4599 fix(api): auto discovery update
fix(api): auto discovery update
2024-07-08 18:03:21 +08:00
pycook
ff2b8ea198 perf(api): relationships built by attribute values (#572) 2024-07-08 11:42:18 +08:00
Leo Song
ed46a1e1c1 Merge pull request #571 from veops/dev_ui_240703
feat: add http attr mapping
2024-07-03 18:49:23 +08:00
songlh
0dc614fb46 feat: add http attr mapping 2024-07-03 18:47:55 +08:00
pycook
bc66d33ce0 fix(api): auto discovery configuration save password
fix(api): auto discovery configuration save password
2024-07-02 21:32:30 +08:00
pycook
d5db68d7d0 feat(api): auto discovery supports mapping (#569) 2024-07-02 20:19:50 +08:00
Leo Song
b22b8b286b Merge pull request #568 from veops/dev_ui_240628
dev_ui_240628
2024-06-28 17:43:30 +08:00
songlh
dd4f3b0e9c feat: update model export 2024-06-28 17:42:20 +08:00
songlh
688f4e0ea4 fix(ui): load ci type error 2024-06-28 17:42:10 +08:00
pycook
c1813f525d chore: update docker compose 2024-06-27 21:33:19 +08:00
pycook
b405e28498 chore: release v2.4.7 2024-06-27 21:30:02 +08:00
pycook
fa32758462 perf(api): CIType templates download (#567) 2024-06-27 20:54:38 +08:00
Leo Song
29995b660a Merge pull request #566 from veops/dev_ui_0627
feat: update model config
2024-06-27 19:41:58 +08:00
songlh
b96fc06a62 feat: update model config 2024-06-27 19:41:24 +08:00
Leo Song
c7f30b63ff Merge pull request #565 from veops/dev_ui_0625
fix(ui): auto discovery
2024-06-25 17:36:00 +08:00
songlh
5bff69a8a8 fix(ui): auto discovery 2024-06-25 17:35:29 +08:00
pycook
d5e60fab88 chore: update ui dockerfile 2024-06-24 21:40:38 +08:00
Jared Tan
190f452118 chore: fix UI docker build and makes UI/API docker build parallel execution (#563)
* try fix UI docker build

* parallel execution

* polish

* polish

* polish

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

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

* feat(api): topology view

* feat(api): topology view api done

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

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

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

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

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

* fix: unseal secret function

* fix: remove depens_on in docker-compose

* fix: support sealing and unsealing secret in multiple process(more than one workers started by gunicorn)
2024-04-15 18:08:47 +08:00
dagongren
a042b4fe39 fix(cmdb-ui):ci detail relation repeatly ciid (#468) 2024-04-15 13:50:50 +08:00
dagongren
a0631414dc style: global static.less (#467) 2024-04-12 15:18:52 +08:00
pycook
5266cb5b88 release: 2.4.2 2024-04-03 15:55:13 +08:00
dagongren
c7d4bec988 feat(cmdb-ui): attributes relation (#463) 2024-04-03 15:27:54 +08:00
pycook
099ddd6ca9 feat(api): rebuild relation by attribute (#462) 2024-04-03 15:13:43 +08:00
pycook
bd813174b1 feat(api): build relation by attributes (#461) 2024-04-02 09:19:51 +08:00
dagongren
0a43680d6e feat:add icons (#460) 2024-04-01 17:37:00 +08:00
dagongren
976c6cfe91 fix:topmenu shake & change logo (#459) 2024-04-01 15:11:24 +08:00
pycook
cf594f04ba fix(api): import CIType 2024-03-29 15:50:07 +08:00
ivonGwy
4232094aed fix: discover scripts (#458)
Co-authored-by: wang-liang0615 <dhuwl0615@163.com>
2024-03-29 15:48:46 +08:00
dagongren
d08827d086 style and 文案变更 (#457) 2024-03-29 15:02:18 +08:00
pycook
d25ae532cd fix(api): CIType template import 2024-03-29 14:20:56 +08:00
pycook
9fbb6ee64d docs: docker-compose changed to docker compose 2024-03-29 13:27:23 +08:00
pycook
b62f0e96fd Merge branch 'master' of github.com:veops/cmdb 2024-03-29 13:14:00 +08:00
pycook
c1bcd0ce45 fix(acl): del resource 2024-03-29 13:13:38 +08:00
dagongren
c8b55c34eb i18n (#456) 2024-03-29 13:11:52 +08:00
pycook
4b5906770f release: v2.4.1 2024-03-29 12:47:23 +08:00
dagongren
4188ac7252 i18n (#455) 2024-03-29 12:23:23 +08:00
dagongren
2efbc6474a style && service tree define (#454) 2024-03-29 11:53:43 +08:00
pycook
03eac0c4d2 pref(api): error tips for out of range value (#453) 2024-03-29 11:46:50 +08:00
dagongren
2a861250eb fix:icon/filter/router...and some bugs (#451) 2024-03-29 10:50:14 +08:00
dagongren
8fc19d8b7c icon font && opsTable (#450) 2024-03-28 20:59:32 +08:00
dagongren
430d2ff6d0 fix:cmdbgrant (#449) 2024-03-28 20:16:47 +08:00
dagongren
2517009d70 fix:Login (#448) 2024-03-28 19:56:54 +08:00
dagongren
67da360d80 Dev UI 240328 (#447)
* feat:ui 全面升级

* feat:ui全面升级
2024-03-28 19:53:54 +08:00
dagongren
24c56fb259 feat:ui 全面升级 (#446)
* feat:ui 全面升级

* feat:ui全面升级
2024-03-28 19:47:46 +08:00
pycook
37d5da65de Dev api 240328 (#445)
* feat(api): login api supports parameter auth_with_ldap

* fix(api): transfer attribute
2024-03-28 19:12:47 +08:00
dagongren
2224ebd533 feat:ui 全面升级 (#444) 2024-03-28 18:38:15 +08:00
pycook
bf6331d215 fix(api): batch import ci relation 2024-03-26 20:38:39 +08:00
pycook
b18b90ab4e fix(api): import CIType
fix(api): import CIType
2024-03-26 16:53:10 +08:00
pycook
702e17a7a4 fix(api): revoke service tree node permissions
fix(api): revoke service tree node permissions
2024-03-26 12:05:22 +08:00
pycook
a7586aa140 feat(api): support service tree editing (#437) 2024-03-26 10:58:11 +08:00
simontigers
ad3f96431c Merge pull request #431 from simontigers/common_employee_edit_department_in_acl
fix(api): common_employee_edit department in acl role
2024-03-25 11:46:30 +08:00
hu.sima
1515820713 fix(api): common_employee_edit department in acl role 2024-03-25 11:46:04 +08:00
simontigers
7728b57878 Merge pull request #430 from simontigers/common_file_ext_check
fix(api): check file ext with magic
2024-03-25 11:17:36 +08:00
hu.sima
a419eefd72 fix(api): check file ext with magic 2024-03-25 11:16:04 +08:00
simontigers
a44e5f6cf1 Merge pull request #429 from simontigers/common_check_new_columns
fix(api): common check new columns
2024-03-22 17:52:19 +08:00
simontigers
7d46e92c2d fix(api): common check new columns 2024-03-22 16:48:16 +08:00
pycook
4117cf87ec Merge branch 'master' of github.com:veops/cmdb 2024-03-20 11:56:49 +08:00
pycook
9e0fe0b818 fix: custom dashboard 2024-03-20 11:56:39 +08:00
dagongren
2a8f1ab9a4 style:update global.less (#426) 2024-03-19 10:01:38 +08:00
pycook
c0fe99b8c7 release: 2.3.13 2024-03-18 20:35:51 +08:00
dagongren
42feb4b862 feat(cmdb-ui):service tree grant (#425) 2024-03-18 19:59:16 +08:00
pycook
482d34993b Dev api 0308 (#424)
* feat(api): grant by node in relation view

* fix(api): When removing attributes, remove the unique constraint

* feat(api): grant by service tree
2024-03-18 19:57:25 +08:00
simontigers
7ff309b8b8 fix(api): edit employee depart with rid=0 (#420) 2024-03-12 17:46:50 +08:00
rustrover
98eb47d44f fix: some typos (#415)
Signed-off-by: gcmutator <329964069@qq.com>
Co-authored-by: gcmutator <329964069@qq.com>
2024-03-11 15:04:38 +08:00
pycook
9ab0f624ef fix(api): remove ACL resources when deleting CIType (#414) 2024-03-08 16:31:03 +08:00
pycook
3f3eda8b3c fix(api): issule #412, unique value restrictions (#413) 2024-03-05 16:21:27 +08:00
pycook
f788adc8cf feat(api): multi-id search (#411)
_id:(id1;id2)
2024-03-04 15:15:34 +08:00
simontigers
693ae4ff05 fix: deploy init common (#407) 2024-03-01 17:21:32 +08:00
pycook
a1a9d99eb4 release: v2.3.12 2024-03-01 17:04:38 +08:00
dagongren
e045e0fb43 fix(cmdb-ui):to lowercase (#406) 2024-03-01 13:52:30 +08:00
pycook
09376dbd2b feat(api): CIType inheritance (#405) 2024-03-01 13:51:13 +08:00
dagongren
7fda5a1e7b feat(cmdb-ui):ci type inherit (#404) 2024-03-01 13:39:20 +08:00
dagongren
113b84763f feat:ci detail share (#403) 2024-02-27 16:13:28 +08:00
dagongren
190170acad fix(cmdb-ui):triggers webhook headers (#402) 2024-02-26 13:46:40 +08:00
pycook
513d2af4b8 feat(api): Remove many-to-many restrictions (#401) 2024-02-26 10:17:53 +08:00
pycook
4588bd8996 fix(api): db-setup commands (#399)
fix(api): db-setup commands
2024-02-23 11:05:11 +08:00
dagongren
082da5fade fix(cmdb-ui):resource search common attrs (#397) 2024-02-22 16:19:12 +08:00
pycook
013b116eb5 feat(acl): login channel add ssh options (#396) 2024-02-21 18:10:44 +08:00
simontigers
208d29165b fix: grant common perm after create new employee (#394) 2024-02-04 13:48:02 +08:00
dagongren
d510330cde fix(cmdb-ui):fix multiple default value (#395) 2024-02-04 11:49:44 +08:00
wang-liang0615
ea4f0fc2a5 fix(ui):login email-》username (#393) 2024-01-31 15:52:27 +08:00
pycook
9bcdaacdc4 docs: update init sql
docs: update init sql
2024-01-26 13:57:36 +08:00
pycook
5045581ddf feat(api): Auto-increment id can be used as primary key (#391) 2024-01-26 13:12:17 +08:00
simontigers
232913172c fix: change common_setting task queue (#390) 2024-01-25 17:39:52 +08:00
pycook
157e1809ed release: 2.3.11 2024-01-13 15:06:51 +08:00
pycook
ab8ccf7d1b ui: lint 2024-01-12 18:21:47 +08:00
wang-liang0615
ae1f0f6b4f lint regexSelect (#382) 2024-01-12 18:09:03 +08:00
wang-liang0615
ff47e0ade6 feat:citype regex check & pref:edit is_list (#380) 2024-01-12 17:09:44 +08:00
pycook
0dd272fb04 feat(api): password supports regular check 2024-01-12 16:56:10 +08:00
pycook
6bc5c1516d feat(api): Attributes support regular check (#379)
feat(api): Attributes support regular check
2024-01-12 13:05:37 +08:00
wang-liang0615
4a4b9e6ef0 feat(cmdb-ui):preference citype order (#378) 2024-01-12 11:14:53 +08:00
pycook
6e3f9478b3 Dev api 240111 (#377)
* feat(api): My subscription supports CIType sorting

* feat(api): db change
2024-01-11 18:01:37 +08:00
pycook
691051c254 perf(api): /api/v0.1/ci/adc/statistics
perf(api): /api/v0.1/ci/adc/statistics
2024-01-11 10:10:01 +08:00
pycook
8f066e95a6 fix(api): grant by attr (#373) 2024-01-10 16:52:27 +08:00
pycook
75bca39bf6 fix(api): commands add-user 2024-01-10 11:56:39 +08:00
pycook
3360e4d0fe feat(db): set variable sql_mode
feat(db): set variable sql_mode
2024-01-10 10:28:15 +08:00
wang-liang0615
521fcd0ba2 fix(ui):some fix (#370)
* pref(ui):some bugfix & some style

* fix(ui):some fix
2024-01-10 09:46:02 +08:00
wang-liang0615
fc113425cb pref(ui):some bugfix & some style (#369) 2024-01-09 14:48:13 +08:00
pycook
81a76a9632 fix(api): cmdb-init-acl commands (#368) 2024-01-09 10:04:53 +08:00
wang-liang0615
9ec105ca37 fix(ui):logout (#365) 2024-01-04 16:12:20 +08:00
wang-liang0615
1e1c92a3ef refactor(ui):extract pager components (#364) 2024-01-04 13:41:07 +08:00
pycook
0b3cad8215 feat: update docker-compose 2024-01-04 10:26:47 +08:00
pycook
ed41a72900 release: v2.3.10 2024-01-03 19:23:42 +08:00
wang-liang0615
27854d4e0a fix(acl-ui):operation history (#363) 2024-01-03 17:41:42 +08:00
wang-liang0615
0372459f9d fix(ui) (#362) 2024-01-03 17:09:25 +08:00
simontigers
f56378e2b5 fix(api): common edit department return (#359)
fix(api): common edit department return (#359)
2024-01-03 16:42:14 +08:00
wang-liang0615
00b276d5e7 format(ui) (#361) 2024-01-03 16:35:50 +08:00
wang-liang0615
3faefbe8d3 feat(ui):i18n (#360)
* feat(ui):i18n

* i18n

* feat(ui):i18n
2024-01-03 16:03:15 +08:00
simontigers
4261f6fb57 fix(api): common department allow parent (#358)
* fix(api): common department edit method

* fix(api): common department edit method

* fix(api): common department allow parent
2024-01-03 15:19:08 +08:00
wang-liang0615
c98199e98e feat(ui):i18n (#357)
* feat(ui):i18n

* i18n
2024-01-03 14:58:47 +08:00
simontigers
82ea1ddc79 fix(api): common department edit method (#356)
* fix(api): common department edit method

* fix(api): common department edit method
2024-01-03 14:31:54 +08:00
simontigers
995e581315 fix(api): common department edit method (#355) 2024-01-03 14:26:40 +08:00
pycook
ec8f626b8f docs: update install (#354) 2024-01-03 13:58:35 +08:00
wang-liang0615
ec884c92e1 feat(ui):i18n (#352) 2024-01-03 13:29:38 +08:00
simontigers
9ee2776bdd fix(api): common i18n wide (#351)
* fix(api): common_i18n wide

* fix(api): department i18n
2024-01-03 13:26:58 +08:00
simontigers
7186bdac9c fix(api): common_i18n wide (#350) 2024-01-03 12:29:49 +08:00
wang-liang0615
0a9964375a feat(ui):add packages (#349) 2024-01-03 09:42:32 +08:00
wang-liang0615
bff45d00b6 i18n (#348) 2024-01-02 18:04:53 +08:00
wang-liang0615
e429ad59ff feat(ui):i18n (#347)
* feat(acl-ui):i18n

* feat(base-ui):i18n

* feat(cmdb-ui):i18n
2024-01-02 17:53:07 +08:00
pycook
ace160ae19 Dev api 231229 (#345)
* fix(api): predefined value for float

* feat(api): update public clouds config

* feat(api): commands add-user support is_admin
2023-12-29 13:44:23 +08:00
wang-liang0615
7036fe023c style(ui):global.less (#344) 2023-12-28 11:00:00 +08:00
wang-liang0615
341f5dba53 fix(cmdb-ui):model relation table (#343) 2023-12-27 13:24:01 +08:00
wang-liang0615
5883c1616e feat:Batch import and download templates support predefined values (#342) 2023-12-27 13:18:05 +08:00
simontigers
bee0a3a3e5 feat(api): common i18n (#340) 2023-12-26 10:06:15 +08:00
pycook
7a79a8bbf7 feat(api): i18n
feat(api): i18n
2023-12-25 21:51:44 +08:00
pycook
100a889cb8 fix(api): CI revoke permission (#337) 2023-12-25 12:15:20 +08:00
kdyq007
b093569453 [更新] 修复 LDAP 登录失败的问题 (#336)
Co-authored-by: sherlock <sherlock@gmail.com>
2023-12-25 09:36:31 +08:00
pycook
3919dfdfbb release: 2.3.9 2023-12-23 12:51:09 +08:00
pycook
ef85ba2542 feat(api): update cmdb-init-acl commands (#335) 2023-12-23 12:44:01 +08:00
pycook
3d5c2ec5bc Merge branch 'master' of github.com:veops/cmdb 2023-12-23 12:31:52 +08:00
pycook
c143d6ae5b fix(api): role grant 2023-12-23 12:30:52 +08:00
simontigers
10b273ee81 feat(api): add update_last_login_by_uid (#333) 2023-12-22 18:43:20 +08:00
wang-liang0615
855cb91b31 feat(cmdb-ui):ci type import&export,pref(cmdb-ui):download ci xlsx name, pref(cmdb-ui):ci detail history merge row method (#331)
* pref(cmdb-ui):download ci xlsx name

* pref(cmdb-ui):ci detail history merge row method

* feat(cmdb-ui):ci type import&export
2023-12-22 15:42:20 +08:00
pycook
ffae57642c fix(api): ci relation search
fix(api): ci relation search
2023-12-22 15:35:02 +08:00
simontigers
9ed1108c20 feat(api): add get_file_binary_str and save (#329) 2023-12-22 15:33:05 +08:00
simontigers
72b2f8b6de fix(api): refresh rid after create and import employee (#328) 2023-12-22 15:24:48 +08:00
pycook
20f3e917fe pref(api): import and export of CIType templates
pref(api): import and export of CIType templates
2023-12-22 14:32:03 +08:00
pycook
c430515377 fix(api): add CI (#326) 2023-12-22 11:19:16 +08:00
simontigers
a6e2aca281 fix(api): svg upload (#321) 2023-12-21 18:58:35 +08:00
wang-liang0615
18313b7bd1 fix(acl_ui):permission (#325) 2023-12-21 17:22:49 +08:00
wang-liang0615
dbc44a8ad6 fix(ui):common double menu (#324) 2023-12-21 14:59:39 +08:00
wang-liang0615
f143e30cf5 feat(ui):批量导入模型根据create权限过滤&&模型配置页面权限 (#323) 2023-12-21 14:23:38 +08:00
wang-liang0615
4beece5a6e fix:open triggerForm from attributeCard (#322) 2023-12-21 14:18:00 +08:00
pycook
920295d955 feat: Fixed db volume name 2023-12-21 10:26:51 +08:00
pycook
6eb8ae1dac fix(api): CAS authentication 2023-12-20 12:10:00 +08:00
wang-liang0615
f1f86ce25a feat(ui):api_host annotation (#320) 2023-12-19 14:22:40 +08:00
pycook
090007487d fix(api): oauth2.0 authentication 2023-12-19 13:07:21 +08:00
wang-liang0615
aa98a304c1 feat:add auth common api_host (#319) 2023-12-19 11:23:27 +08:00
pycook
fe22e363b4 fix(api): ldap authentication 2023-12-19 00:16:56 +08:00
pycook
2d2fb6e1d6 release: v2.3.8 2023-12-18 20:08:19 +08:00
pycook
9894c77300 feat(ui): lint 2023-12-18 19:25:22 +08:00
simontigers
4ea947f741 fix: auth config (#318) 2023-12-18 18:27:06 +08:00
simontigers
e5ab2c2573 fix: auth config (#317) 2023-12-18 16:52:24 +08:00
wang-liang0615
5581fa8d0f lint(ui) (#316) 2023-12-18 16:41:21 +08:00
wang-liang0615
76c939fe5c fix(ui):401 redirect && feat(ui):add auth ldap test (#315) 2023-12-18 16:30:02 +08:00
wang-liang0615
6e9ce08e2c pref(cmdb-ui):change adt key & add adt alias (#314) 2023-12-18 16:07:52 +08:00
pycook
092a8b9b92 pref(api): A CIType allows repeated binding of auto-discovery rules (#313) 2023-12-16 17:56:14 +08:00
wang-liang0615
b45fd0cbbb fix:is_list edit bug (#312)
* feat(ui):auth setting

* fix:is_list edit bug
2023-12-15 13:19:55 +08:00
simontigers
5320ecfd62 fix(api): common_data (#311) 2023-12-15 10:56:04 +08:00
wang-liang0615
1d253d7ad3 feat(ui):auth setting (#310) 2023-12-15 10:33:38 +08:00
pycook
73d53f0440 pref(api): authentication and login log (#308)
* pref(api): authentication and login log

* feat(api): ldap and OAuth2.0
2023-12-14 19:53:08 +08:00
simontigers
d4a37af183 feat(api): auth config api (#309) 2023-12-14 19:39:21 +08:00
wang-liang0615
e03849b054 fix(cmdb-ui):batch upload cancel bug && download error (#306) 2023-12-13 14:50:53 +08:00
simontigers
faed3fe6a2 fix(api): get_employee_notice check data None (#305)
* fix(api): get_employee_notice check data None

* fix(api): remove path when save messager url
2023-12-13 09:45:42 +08:00
pycook
21c9d9accd feat(api): support OAuth2.0 and OIDC authentication, it has been tested with casdoor
feat(api): support OAuth2.0 and OIDC authentication, it has been tested with casdoor
2023-12-12 20:29:57 +08:00
wang-liang0615
a06599ce33 pref(cmdb-ui):batch upload for date type (#301) 2023-12-12 14:53:12 +08:00
pycook
2b69217136 fix(api): time data format
fix(api): time data format
2023-12-12 14:37:21 +08:00
wang-liang0615
03a3b8b169 Revert "pref(cmdb-ui):batch upload for date type (#298)" (#299)
This reverts commit cd319421d5.
2023-12-12 13:51:19 +08:00
wang-liang0615
cd319421d5 pref(cmdb-ui):batch upload for date type (#298)
* fix(cmdb-ui):set localstorage '' after unsubscribe ci

* pref(cmdb-ui):batch upload for date type
2023-12-12 13:38:08 +08:00
pycook
c918d54ea5 perf(api): ci delete (#297) 2023-12-12 11:09:32 +08:00
wang-liang0615
cf0ad7bad6 fix(cmdb-ui):set localstorage '' after unsubscribe ci (#296) 2023-12-12 09:38:50 +08:00
pycook
e0c8263542 feat(api): cas is compatible with casdoor
feat(api): cas is compatible with casdoor
2023-12-11 20:58:18 +08:00
pycook
275e8b15f3 Dev api 231211 (#294)
* fix(api): cas authentication

* feat(api): add lz4 package
2023-12-11 19:30:09 +08:00
simontigers
6ff942c107 feat(api): upload file save db (#292) 2023-12-11 18:22:33 +08:00
gmailnovo
d3c87ee500 feat: Handle '/dev/stdout' in Logger Configuration 2023-12-07 11:27:45 +08:00
simontigers
a4f65e7fc6 fix(api): Common create employee (#287)
* fix(api): add add_from arg in create employee

* fix(api): add check in acl call common after add user
2023-12-06 17:12:37 +08:00
wang-liang0615
10527bf9b8 pref(cmdb-ui):ci upload&delete concurrent 6 (#286) 2023-12-06 14:33:25 +08:00
pycook
0414121c27 docs: update local install 2023-11-30 16:18:33 +08:00
simontigers
edde467c87 fix(api): common_data delele (#282) 2023-11-30 13:03:09 +08:00
pycook
d525e1ec54 feat(api): only the role cmdb_admin can modify the CIType group (#280) 2023-11-29 17:40:12 +08:00
pycook
91c49b690f fix(api): get relation history 2023-11-28 20:37:36 +08:00
pycook
05453becf9 release: 2.3.7 2023-11-24 14:53:53 +08:00
pycook
5fe27f8678 feat(api): issue #212 (#279) 2023-11-24 10:26:48 +08:00
wang-liang0615
0924b8846f feat(cmdb-ui):多对多关系&&仪表盘色卡调整 (#271) 2023-11-24 10:25:56 +08:00
loveiwei
62a669159a doc: change content in readme_en (#278) 2023-11-23 20:10:25 +08:00
loveiwei
2933bf1efa feature: add a new management script, such as install,start,pause,del… (#277)
* feature: add a new management script, such as install,start,pause,delete,uninstall

* doc: add install.sh method in readme

* doc: add install.sh method in readme, change install.sh for macos support

* doc: add install.sh method in readme

* doc: add install.sh method in readme
2023-11-23 17:45:53 +08:00
loveiwei
981f8b0145 Fix deploy 1700028675 (#272)
* fix: Solving the timezone issue in Redis, as well as the problem of MySQL logs always being in UTC timezone.

* fix: change the config path of slow_log into /tmp directory in mysqld.cnf file
2023-11-22 18:34:58 +08:00
pycook
213bda671c docs: local install (#270) 2023-11-16 20:54:08 +08:00
loveiwei
837aabfe77 fix: Solving the timezone issue in Redis, as well as the problem of MySQL logs always being in UTC timezone. (#268) 2023-11-15 20:48:52 +08:00
wang-liang0615
cc599d414a feat(acl-ui):resources table resizable (#267) 2023-11-14 09:37:45 +08:00
pycook
01fcd7f80e Dev api 231108 (#264)
* perf(api): commands add-user

* feat(api): add commands cmdb-agent-init
2023-11-09 11:32:54 +08:00
pycook
af254ddbeb Dev api 1107 (#263)
feat: update cmdb.sql
2023-11-08 16:11:36 +08:00
wang-liang0615
422e89c6c6 ui package update (#261)
* update(cmdb-ui):update packages && delete yarn.lock

* update:gitignore
2023-11-08 16:06:48 +08:00
wang-liang0615
c2c11b38ed Revert "Dev UI 231108 (#256)" (#260)
This reverts commit 45d3f57228.
2023-11-08 14:43:37 +08:00
wang-liang0615
45d3f57228 Dev UI 231108 (#256)
* pref(cmdb-ui):update packages

* pref(cmdb-ui):update packages
2023-11-08 14:32:17 +08:00
pycook
c711c3d3fd feat(api): encrypting webhook configurations (#255) 2023-11-07 17:15:24 +08:00
pycook
2ae4aeee67 fix(api): Code scanning alerts (#254) 2023-11-06 14:27:30 +08:00
pycook
46238b8b51 Dev api 1103 (#252)
feat(api): update requirements
2023-11-06 13:24:08 +08:00
dependabot[bot]
b1528ec511 build(deps): bump browserify-sign from 4.2.1 to 4.2.2 in /cmdb-ui (#241)
Bumps [browserify-sign](https://githubfast.com/crypto-browserify/browserify-sign) from 4.2.1 to 4.2.2.
- [Changelog](https://githubfast.com/browserify/browserify-sign/blob/main/CHANGELOG.md)
- [Commits](https://githubfast.com/crypto-browserify/browserify-sign/compare/v4.2.1...v4.2.2)

---
updated-dependencies:
- dependency-name: browserify-sign
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 13:13:29 +08:00
pycook
8ef4edb7d2 fix(api): search sort misses cache (#251) 2023-11-03 12:01:44 +08:00
pycook
0172206c7c fix(api commands): cmdb-password-data-migrate (#249) 2023-11-02 20:43:39 +08:00
simontigers
9d5bdf1b0d style(common-setting): clean warning (#246) 2023-10-30 17:35:34 +08:00
pycook
c0726b228d fix(api): secrets 2023-10-30 17:23:42 +08:00
pycook
5b314aa907 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
c9f0de9838 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
2db41dd992 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
89e492c1f3 fix: delete CI password data (#243) 2023-10-29 10:53:29 +08:00
pycook
1aeb9a2702 fix(cmdb-ui): CI update password 2023-10-28 23:07:01 +08:00
pycook
b090a88b76 release: v2.3.6 2023-10-28 17:51:13 +08:00
pycook
d5c479f7e5 feat(cmdb-api): ci password 2023-10-28 16:55:02 +08:00
pycook
b342258e75 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
3d716eff3e feat:预定义值支持脚本&&密码存储&&一些bugfix (#239) 2023-10-27 11:10:43 +08:00
ivonGwy
9791a184e3 Doc (#235)
* change assignees
2023-10-25 16:00:04 +08:00
ivonGwy
142b8c95c5 Doc (#234)
* final template
2023-10-25 15:52:12 +08:00
ivonGwy
363b89011f Doc (#232)
* fix bugs
2023-10-25 14:54:23 +08:00
ivonGwy
321372fa88 Update issue templates 2023-10-25 14:46:44 +08:00
ivonGwy
fc27dc6e64 Doc (#231)
* add issue template
2023-10-25 14:36:36 +08:00
ivonGwy
8ad98f4ba4 Update issue templates 2023-10-25 14:06:24 +08:00
ivonGwy
9b050fa7fa Update issue templates 2023-10-25 14:04:53 +08:00
pycook
a30826827a fix(api): add ci (#230) 2023-10-25 13:51:29 +08:00
ivonGwy
691b50aec4 Doc (#229)
* change reandme
2023-10-25 13:19:30 +08:00
kdyq007
55ca2d5eb5 关闭前端密码加密;加强 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
779cd1ea9e feat: Predefined values support executing scripts (#227) 2023-10-24 19:32:43 +08:00
simontigers
7d53180a2f fix: add_employee_from_acl (#225) 2023-10-24 14:20:40 +08:00
wang-liang0615
b6c41c00dd fix:关系视图删除关系接口传参修改 (#224)
* fix:acl新增用户展示异常问题

* fix:关系视图删除关系接口传参修改
2023-10-24 06:04:21 +08:00
wang-liang0615
fff5679f6e fix:acl新增用户展示异常问题 (#223) 2023-10-24 05:59:08 +08:00
pycook
7556dfe56b feat: add cryptography to requirements 2023-10-23 14:37:01 +08:00
pycook
44b6f2b2ad fix: acl cache 2023-10-23 13:57:06 +08:00
Evan Sung
f5607d96f3 fix(common): fix 'ACLManager' object has no attribute 'create_app' (#217) 2023-10-21 11:38:19 +08:00
Evan Sung
2e85a9971b fix(ci_cache): ci cache async args (#215) 2023-10-20 12:05:19 +08:00
kdyq007
8d177266dc feat: python-ldap 更新到 ldap3 (#214)
Co-authored-by: sherlock <sherlock@gmail.com>
2023-10-20 09:36:38 +08:00
pycook
42d870ea4e 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
424cad2130 fix:ci relation add type filter (#208) 2023-10-18 14:06:28 +08:00
pycook
430308a679 fix: ci relation statistics 2023-10-18 13:35:01 +08:00
pycook
cc7570a4b2 docs: api doc 2023-10-17 12:06:37 +08:00
wang-liang0615
413c0dfdfe feat:webhook body 支持非json (#203) 2023-10-17 10:44:38 +08:00
Evan Sung
f8fee771c4 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
3d05cef7cd feat(db): support flask migrate (#201)
Co-authored-by: s01249 <songbing@smyfinancial.com>
2023-10-13 15:55:26 +08:00
simontigers
30477f736e fix: common perms (#200) 2023-10-12 16:02:35 +08:00
wang-liang0615
0cd5c0277b pref:批量上传&资源管理小优化 (#199) 2023-10-12 15:06:39 +08:00
ivonGwy
4fcddd1010 Merge pull request #197 from veops/doc
Doc
2023-10-11 14:34:05 +08:00
ivonGwy
b269ef894f change wechat pic size 2023-10-11 14:33:19 +08:00
ivonGwy
ea13432e4c change wechat pic 2023-10-11 14:28:49 +08:00
pycook
c8a68bd185 Dev api (#196)
* docs: update

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

* fix:chartColor undefined
2023-10-11 09:12:04 +08:00
pycook
59d9f2c79a docs: update (#194) 2023-10-10 16:53:30 +08:00
wang-liang0615
f629558d60 pref:用户密钥非必填 (#193) 2023-10-10 09:25:24 +08:00
pycook
4cbf70e756 release: v2.3.5 2023-10-09 20:55:30 +08:00
pycook
40b9bec122 feat: get messenger url from common setting 2023-10-09 20:25:27 +08:00
wang-liang0615
17b27d492a 前端更新 (#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
dd8f66a3fa 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
d501436e3d 前端更新 (#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
8c6389b4f8 feat: notice_config access messenger (#190) 2023-10-09 17:32:20 +08:00
pycook
63ed73fa14 fix: delete user role 2023-10-09 15:40:18 +08:00
pycook
ef90506916 feat: The definition of attribute choice values supports webhook and other model attribute values. 2023-10-09 15:33:18 +08:00
wang-liang0615
c0541b7e50 Dev UI (#186)
* fix:add package

* fix:notice_info为null的情况

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

* fix:notice_info为null的情况
2023-09-27 09:18:33 +08:00
pycook
373346e71e feat: ci triggers 2023-09-26 21:18:34 +08:00
wang-liang0615
983cb4041c fix:add package (#182) 2023-09-26 21:12:10 +08:00
wang-liang0615
718fca57d2 Merge pull request #181 from veops/dev_ui
前端更新
2023-09-26 20:34:27 +08:00
wang-liang0615
2b1b0333ff Merge branch 'master' into dev_ui 2023-09-26 20:34:14 +08:00
wang-liang0615
9bb787d3f4 fix:topo图相同节点出现两次的bug 2023-09-26 20:13:12 +08:00
wang-liang0615
9f5979c1b1 feat:wangeditor 注册自定义组件 2023-09-26 20:07:00 +08:00
wang-liang0615
b58230fd56 delete:删除getwx 2023-09-26 20:04:38 +08:00
simontigers
8e6ce05c04 feat: common notice config (#180) 2023-09-26 19:44:20 +08:00
wang-liang0615
b8d93bc9eb feat: UI更新 触发器 (#179)
* feat:新增api&适配

* feat:触发器

* add packages & 注释代码

* feat: webhook tips
2023-09-26 18:25:04 +08:00
wang-liang0615
968ef93153 feat: webhook tips 2023-09-26 18:17:23 +08:00
wang-liang0615
e0a0113e69 add packages & 注释代码 2023-09-26 17:35:41 +08:00
wang-liang0615
7181f2879a feat:触发器 2023-09-26 17:01:31 +08:00
wang-liang0615
ff1626ff07 feat:新增api&适配 2023-09-26 16:26:25 +08:00
pycook
dca8781a03 fix: ci_cache 2023-09-25 15:46:07 +08:00
wang-liang0615
43c5d74661 Merge pull request #178 from veops/dev_ui
前端更新:仪表盘优化
2023-09-25 14:52:09 +08:00
wang-liang0615
b1d0bdb536 pref:仪表盘优化 2023-09-25 14:50:08 +08:00
wang-liang0615
2420631ed5 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-25 14:43:34 +08:00
pycook
deabebb922 refactor: CI triggers 2023-09-22 17:39:54 +08:00
simontigers
cd451060e3 fix: icon svg support (#177) 2023-09-20 15:56:57 +08:00
pycook
82d4c4f961 fix date search 2023-09-18 18:15:02 +08:00
pycook
79f5dac661 fix dashboard compute 2023-09-18 13:04:50 +08:00
pycook
1a7c3fcd21 release v2.3.3 2023-09-15 17:57:39 +08:00
pycook
606be7f8e8 dashboard ui update 2023-09-15 17:36:10 +08:00
simontigers
7f66f7688b feat: init resource for backend (#176) 2023-09-15 15:30:30 +08:00
pycook
25ad2192fd enhance dashboard 2023-09-15 15:26:20 +08:00
pycook
881b8cc1fa cmdb-api/api/lib/resp_format.py 2023-09-12 20:01:30 +08:00
pycook
68905281f2 Detect circular dependencies when adding CIType relationships 2023-09-12 20:00:56 +08:00
wang-liang0615
3cb78f30d2 计算属性 触发计算 (#174) 2023-09-11 19:16:05 +08:00
pycook
ebce839eaa 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
3170048ea9 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-11 17:35:44 +08:00
wang-liang0615
976cac6742 计算属性 触发计算 2023-09-11 17:34:51 +08:00
pycook
99e5a932ae release 2.3.2 2023-09-07 13:44:51 +08:00
pycook
058585504f Merge pull request #172 from veops/dev_ui
新建ci及批量导入时,新建关系
2023-09-07 11:04:49 +08:00
wang-liang0615
6d50a13cf0 新建ci及批量导入时,新建关系 2023-09-07 10:25:18 +08:00
pycook
198ecad13a Merge branch 'master' of github.com:veops/cmdb 2023-09-07 10:12:55 +08:00
pycook
1660139b27 Add CI relationship when creating CI, the text value removes the escape 2023-09-07 10:12:42 +08:00
pycook
0155eb98a2 Merge pull request #171 from ronething/fix/makefile
optimize: makefile help
2023-09-05 20:34:16 +08:00
ashing
c0c05bca86 fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:33:07 +08:00
ashing
32227d375a fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:29:29 +08:00
ashing
eb9c20a295 fix: review
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:21:20 +08:00
ashing
b826de4195 optimize: makefile help
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 20:06:31 +08:00
pycook
c3b55c2850 Merge pull request #170 from ronething/feat/xx
feat: support docker deploy mysql and redis
2023-09-05 19:28:47 +08:00
ivonGwy
33313b6206 Merge pull request #169 from veops/doc
add document link
2023-09-05 15:41:52 +08:00
ivonGwy
00fbe15a19 add document link 2023-09-05 15:40:31 +08:00
ashing
9e5c926bfd feat: support docker deploy mysql and redis
Signed-off-by: ashing <axingfly@gmail.com>
2023-09-05 15:26:50 +08:00
wang-liang0615
f0467c3d3b Merge pull request #168 from veops/dev_ui
UI更新
2023-09-05 15:23:43 +08:00
wang-liang0615
6758b994f8 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-05 15:22:18 +08:00
wang-liang0615
d8646f8e7c 模型关联 展示反向关系 2023-09-05 15:22:08 +08:00
pycook
0fa95aed36 Merge branch 'master' of github.com:veops/cmdb 2023-09-05 14:49:53 +08:00
pycook
d82356444a move Dockerfile to docs 2023-09-05 14:49:34 +08:00
wang-liang0615
b8fa68ec8d Merge pull request #167 from veops/dev_ui
sub menu color
2023-09-04 16:34:26 +08:00
wang-liang0615
78c542e71d sub menu color 2023-09-04 16:33:35 +08:00
wang-liang0615
6594863934 Merge pull request #166 from veops/dev_ui
ui更新
2023-09-04 13:15:27 +08:00
wang-liang0615
61e8c61fc6 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-09-04 13:14:35 +08:00
wang-liang0615
ec9b5a0fa0 sidebar 2023-09-04 13:14:11 +08:00
pycook
3664994cc5 import format 2023-09-02 12:09:41 +08:00
pycook
366311e59b format 2023-09-01 18:07:44 +08:00
pycook
03f2ff912d fix delete choice values 2023-08-31 16:02:24 +08:00
pycook
ff9af51465 fix delete choice values 2023-08-31 15:18:15 +08:00
wang-liang0615
04fc588935 Merge pull request #165 from veops/dev_ui
proxy
2023-08-31 13:31:26 +08:00
wang-liang0615
f215e887c9 proxy 2023-08-31 13:28:15 +08:00
pycook
3e6955fc7a Merge pull request #162 from simontigers/cmdb_icon_manage
feat: add cmdb custom icon manage
2023-08-31 11:15:09 +08:00
pycook
b1cfb88308 Merge pull request #163 from veops/dev_ui
支持上传自定义图标
2023-08-31 11:14:42 +08:00
hu.sima
d1af0eba79 feat: add cmdb custom icon manage 2023-08-31 10:49:56 +08:00
wang-liang0615
f41873dc8c 支持上传自定义图标 2023-08-31 10:05:11 +08:00
pycook
63562007df fix update attribute 2023-08-30 13:34:10 +08:00
pycook
a99ecb9ea5 Merge branch 'master' of github.com:veops/cmdb 2023-08-29 14:49:21 +08:00
pycook
08f9bd9071 The default value of USE_ACL is set to True 2023-08-29 14:49:09 +08:00
pycook
17c851f354 Merge pull request #161 from simontigers/common_setting_format
fix: company info create
2023-08-29 11:01:25 +08:00
hu.sima
3382195a25 fix: company info create 2023-08-29 10:56:48 +08:00
pycook
cd70b16eb3 Merge branch 'master' of github.com:veops/cmdb 2023-08-25 11:01:24 +08:00
pycook
2a98c00d97 update ad_ci when deleting ci 2023-08-25 10:59:38 +08:00
wang-liang0615
5044af5490 Merge pull request #160 from veops/dev_ui
前端更新
2023-08-25 10:12:31 +08:00
wang-liang0615
320dc07676 fix 新增类型回车键发送两次请求 2023-08-25 10:11:09 +08:00
wang-liang0615
95d3b0233a fix 新增类型回车键发送两次请求 2023-08-25 10:08:04 +08:00
pycook
093466ef06 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
ceacc0ab15 perf(ad_ci_relation): optimize ad_ci relation 2023-08-24 14:16:12 +08:00
pycook
cb3a9d9432 docker-compose add flask db-setup 2023-08-24 11:32:09 +08:00
pycook
23fd56cf28 vxe-table-plugin-export-xlsx==2.0.0 2023-08-24 11:06:28 +08:00
pycook
dd1c783919 add config CACHE_REDIS_PASSWORD and fix delete ci_type 2023-08-23 18:05:28 +08:00
pycook
8296e9a552 fix update ci 2023-08-22 11:34:40 +08:00
pycook
590565bdf0 Register api and commands with absolute paths 2023-08-21 20:08:23 +08:00
pycook
9e2bf3d1f0 fix merge conflict 2023-08-21 11:55:49 +08:00
pycook
7547b67805 fix g.user 2023-08-21 11:54:33 +08:00
pycook
4abe4d7e8f version: 2.3.1 2023-08-20 11:24:53 +08:00
pycook
0ebd52f3fd lint 2023-08-20 11:23:55 +08:00
pycook
4cc2e47f02 Merge pull request #157 from EvanSung/fix_20230817_guser_issue
fix(acl): g user issue
2023-08-17 22:12:45 +08:00
EvanSung
b16bfdfa03 fix(acl): g user issue 2023-08-17 18:40:45 +08:00
pycook
ece24080d5 fix MyJSONEncoder 2023-08-16 21:28:27 +08:00
pycook
a0ea8a193b Merge pull request #155 from veops/dev_ui
前端更新
2023-08-16 13:01:13 +08:00
pycook
d8e09d449e Merge branch 'master' of github.com:veops/cmdb 2023-08-16 13:00:44 +08:00
pycook
44c99e0f2e Delete user without soft delete 2023-08-16 13:00:30 +08:00
wang-liang0615
7110b4bcb3 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-16 10:09:47 +08:00
wang-liang0615
3989859945 delete user 2023-08-16 10:09:25 +08:00
pycook
29ed4dd708 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
dbcbe33ba0 fix: init-import-user-from-acl 2023-08-15 20:45:28 +08:00
pycook
ed340a1c33 Merge pull request #153 from simontigers/common_setting_format
fix: import_user_from_acl
2023-08-15 20:25:09 +08:00
hu.sima
17ee7622b4 fix: import_user_from_acl 2023-08-15 20:19:45 +08:00
pycook
51877778bf Merge branch 'master' of github.com:veops/cmdb 2023-08-15 19:48:11 +08:00
pycook
1de8b492ea [update] delete roles, users, attributes 2023-08-15 19:47:59 +08:00
wang-liang0615
1fe38c4ec3 Merge pull request #152 from veops/dev_ui
前端更新
2023-08-15 19:47:03 +08:00
wang-liang0615
92bff08b84 属性库 2023-08-15 19:34:17 +08:00
wang-liang0615
37d0dacd2e 属性库 2023-08-15 19:26:49 +08:00
wang-liang0615
e22f45fd25 属性库 2023-08-15 19:21:09 +08:00
wang-liang0615
56ef0a055a 属性库 2023-08-15 19:10:26 +08:00
wang-liang0615
522991e23b 后台管理-模型关联 关系删除&&筛选 2023-08-15 15:02:46 +08:00
pycook
ff061d4d2e update gitattributes 2023-08-15 13:41:45 +08:00
wang-liang0615
5b84a704b1 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
3ba83821f2 Merge pull request #148 from simontigers/common_setting_format
fix: default arg value
2023-08-10 19:31:18 +08:00
pycook
45b82aa52d Merge pull request #149 from veops/dev_ui
ui更新:password
2023-08-10 19:28:24 +08:00
EvanSung
c99790bda2 refactor(fe): reduce the width of resource mgt table 2023-08-10 19:23:41 +08:00
wang-liang0615
7272880062 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-10 19:21:28 +08:00
wang-liang0615
8130dd92ab 增加密码明文传输 2023-08-10 19:21:10 +08:00
hu.sima
9ca2d38307 fix: default arg value 2023-08-10 19:05:56 +08:00
pycook
e61bbdbcb7 Merge pull request #147 from simontigers/common_setting_format
fix: remove useless
2023-08-10 19:01:25 +08:00
hu.sima
88960c3082 fix: remove useless 2023-08-10 18:55:32 +08:00
pycook
0aa433882e Merge pull request #146 from simontigers/common_setting_format
Common setting format
2023-08-10 18:23:24 +08:00
hu.sima
d4f5713e0a fix: remove unused column 2023-08-10 16:29:52 +08:00
hu.sima
790204ea28 style: format common setting 2023-08-10 15:30:01 +08:00
pycook
927ed393d3 Merge pull request #145 from EvanSung/optimize_20230810_auth_require
optimize(auth): auth request json
2023-08-10 11:24:23 +08:00
EvanSung
bf92b89a5f optimize(auth): auth request json 2023-08-10 10:43:59 +08:00
pycook
7ddaa143da fix celery config 2023-08-08 16:33:24 +08:00
pycook
157361cc7a Merge branch 'master' of github.com:veops/cmdb 2023-08-08 13:16:14 +08:00
pycook
dcf768376a upgrade celery 2023-08-08 13:16:07 +08:00
pycook
e339ad2c23 Merge pull request #144 from veops/dev_ui
UI更新:fix preferenceList=>attrList
2023-08-08 09:21:10 +08:00
wang-liang0615
8b10c5c72d Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-08 09:11:24 +08:00
wang-liang0615
bd13f6b744 fix preferenceList=>attrList 2023-08-08 09:11:03 +08:00
pycook
934d00e87d upgrade flask to 2.3.2 and replace g.user with current_user 2023-08-06 21:54:18 +08:00
pycook
9d421993a0 Merge pull request #138 from lovvvve/fix_ldap
fix ldap login
2023-08-04 11:31:58 +08:00
pycook
6751f673d5 Merge pull request #142 from veops/dev_ui
ci 批量更新和删除的异步处理
2023-08-04 09:27:55 +08:00
wang-liang0615
c420a2195a Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-08-03 16:54:47 +08:00
wang-liang0615
ec3a6e0b6e ci 批量更新和删除的异步处理 2023-08-03 16:54:27 +08:00
pycook
ab0a5399f7 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
33e63c762f fix(trigger): session invalid issue 2023-08-02 18:22:42 +08:00
lovvvve
53b2a228ae fix ldap login 2023-08-01 11:27:29 +00:00
pycook
c687e7ad9b 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
ea3cdbd5f5 Merge pull request #135 from simontigers/remove_pandas
fix: remove pandas
2023-08-01 15:55:15 +08:00
hu.sima
1ebe74d966 fix: remove pandas 2023-08-01 15:32:44 +08:00
dependabot[bot]
1f935d25a2 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
14e4dbacac Merge branch 'master' of github.com:veops/cmdb 2023-08-01 13:47:34 +08:00
pycook
f7a9257e16 fix dependabot alerts 2023-08-01 13:47:11 +08:00
pycook
99562df6cd fix dependabot alerts 2023-08-01 13:46:47 +08:00
pycook
630013cb85 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
6351d3af64 Merge pull request #132 from veops/dev_ui
删除角色相关
2023-07-31 19:54:19 +08:00
wang-liang0615
2d96048828 删除角色相关 2023-07-31 19:52:06 +08:00
pycook
3e67bb94bc Merge branch 'master' of github.com:veops/cmdb 2023-07-31 18:39:46 +08:00
pycook
a26c9ff542 fix delete ci_type 2023-07-31 18:39:33 +08:00
pycook
252003f76d Merge pull request #131 from veops/dev_ui
前端acl
2023-07-28 18:03:36 +08:00
wang-liang0615
928149f2a0 common-setting 2023-07-27 15:47:13 +08:00
wang-liang0615
0283af812c acl 2023-07-27 15:30:27 +08:00
wang-liang0615
6ccb2e74e0 fix acl change page size 2023-07-27 15:08:25 +08:00
dependabot[bot]
5016464016 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
b787bd9b7b Merge pull request #129 from veops/dev_ui
前端更新
2023-07-25 18:19:47 +08:00
wang-liang0615
11f69d8679 Merge branch 'master' of github.com:veops/cmdb into dev_ui 2023-07-25 13:11:03 +08:00
wang-liang0615
c3168035ed 授权高亮提示 2023-07-25 13:10:45 +08:00
pycook
5140c0a58f add command cmdb-index-table-upgrade 2023-07-25 10:31:30 +08:00
wang-liang0615
ee97582579 style 新建属性行错乱 2023-07-25 10:18:22 +08:00
pycook
83e9172722 废弃3个表: c_value_datetime c_value_floats c_value_integers, time类型属性值增加写入校验 2023-07-24 21:55:00 +08:00
pycook
341e687987 禁止删除唯一标识的属性 2023-07-21 15:58:41 +08:00
pycook
93d9804127 Merge branch 'master' of github.com:veops/cmdb 2023-07-20 18:37:14 +08:00
pycook
f3d42cb356 fix docker-compose 2023-07-20 18:36:32 +08:00
pycook
04b9a3c929 Merge pull request #127 from veops/dev_ui
fix currentValueType
2023-07-20 15:39:11 +08:00
wang-liang0615
e98160b84a fix currentValueType 2023-07-20 15:30:12 +08:00
pycook
7c769a53da 更新架构图 2023-07-20 11:01:25 +08:00
pycook
08dc343083 update readme 2023-07-20 11:01:25 +08:00
pycook
52e654ff60 update readme 2023-07-20 11:01:25 +08:00
226 changed files with 5274 additions and 9862 deletions

1
.gitignore vendored
View File

@@ -78,3 +78,4 @@ cmdb-ui/npm-debug.log*
cmdb-ui/yarn-debug.log*
cmdb-ui/yarn-error.log*
cmdb-ui/package-lock.json
start.sh

View File

@@ -1,13 +0,0 @@
# Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)

195
README.md
View File

@@ -1,138 +1,119 @@
<p align="center">
<a href="https://veops.cn">
<img src="https://github.com/user-attachments/assets/c5cfb272-899b-418d-9e69-8e1dd07db0f6" alt="维易CMDB"/>
</a>
<a href="https://veops.cn"><img src="docs/images/logo.png" alt="维易CMDB" width="300"/></a>
</p>
<h4 align="center">简单、轻量、通用的运维配置管理数据库</h4>
<h3 align="center">简单、轻量、通用的运维配置管理数据库</h3>
<p align="center">
<a href="https://github.com/veops/cmdb/blob/master/LICENSE"><img src="https://img.shields.io/badge/License-AGPLv3-brightgreen" alt="License: GPLv3"></a>
<a href="https://github.com/veops/cmdb/releases"><img alt="the latest release version" src="https://img.shields.io/github/v/release/veops/cmdb?color=75C1C4&include_prereleases&label=Release&logo=github&logoColor=white"></a>
<a href="https:https://github.com/sendya/ant-design-pro-vue"><img src="https://img.shields.io/badge/UI-Ant%20Design%20Pro%20Vue-green" alt="UI"></a>
<a href="https://github.com/pallets/flask"><img src="https://img.shields.io/badge/API-Flask-bright" alt="API"></a>
<a href="https://github.com/veops/cmdb/stargazers"><img src="https://img.shields.io/github/stars/veops/cmdb" alt="Stars Badge"/></a>
<a href="https://github.com/veops/cmdb"><img src="https://img.shields.io/github/forks/veops/cmdb" alt="Forks Badge"/></a>
</p>
<p align="center">
中文(简体) · <a href="docs/README_en.md">English</a>
<a href="https:https://github.com/sendya/ant-design-pro-vue"><img src="https://img.shields.io/badge/UI-Ant%20Design%20Pro%20Vue-brightgreen" alt="UI"></a>
<a href="https://github.com/pallets/flask"><img src="https://img.shields.io/badge/API-Flask-brightgreen" alt="API"></a>
</p>
------------------------------
[English](docs/README_en.md) / [中文](README.md)
- 产品文档https://veops.cn/docs/
- 在线体验:<a href="https://cmdb.veops.cn" target="_blank">CMDB</a>
- username: demo 或者 admin
- password: 123456
> **重要提示**: `master` 分支在开发过程中可能处于 _不稳定的状态_ 。
> 请通过[releases](https://github.com/veops/cmdb/releases)获取
## 系统介绍
维易CMDB是一个简洁、轻量且高度可定制的运维配置管理数据库CMDB。它支持灵活的模型配置和资源自动发现旨在为企业提供便捷的资产管理解决方案帮助运维团队高效地管理 IT 基础设施和服务。
### 系统概览
- 产品文档:[https://veops.cn/docs/](https://veops.cn/docs/)
- 在线体验:[https://cmdb.veops.cn](https://cmdb.veops.cn)
- 用户名demo 或者 admin
- 密码123456
- **重要提示**`master` 分支在开发过程中可能处于**不稳定的状态**。请通过 [releases](https://github.com/veops/cmdb/releases) 获取最新稳定版本。
<img src=docs/images/dashboard.png />
[查看更多展示](docs/screenshot.md)
### 相关文章
- <a href="https://mp.weixin.qq.com/s/v3eANth64UBW5xdyOkK3tg" target="_blank">概要设计</a>
- <a href="https://github.com/veops/cmdb/tree/master/docs/cmdb_api.md" target="_blank">API 文档</a>
- <a href="https://mp.weixin.qq.com/s/rQaf4AES7YJsyNQG_MKOLg" target="_blank">自动发现</a>
- 更多文章可以在公众号 **维易科技OneOps** 里查看
### 特点
- 灵活性
1. 配置灵活,不设定任何运维场景,有内置模板
2. 自动发现、入库 IT 资产
- 安全性
1. 细粒度权限控制
2. 完备操作日志
- 多应用
1. 丰富视图展示维度
2. API简单强大
3. 支持定义属性触发器、计算属性
### 主要功能
- **自定义模型和模型关系**:支持模型属性的自定义,包括下拉列表、字体颜色计算属性等高级功能,满足不同业务需求。
- **自动发现资源**:支持计算机、网络设备、存储设备、数据库、中间件、公有云资源等自动发现。
- **多维度视图展示**:包括资源视图、层级视图、关系视图等,帮助运维人员全面管理资源。
- **细粒度权限控制**:通过精确的访问控制和完备的操作日志保障系统的安全性。
- **全面的资源搜索功能**:支持灵活的资源和关系搜索,快速定位和操作资源。
- **集成 IP 地址管理IPAM和数据中心基础设施管理DCIM**:简化网络资源和数据中心设备的管理。
- 模型属性支持索引、多值、默认排序、字体颜色,支持计算属性
- 支持自动发现、定时巡检、文件导入
- 支持资源、层级、关系视图展示
- 支持模型间关系配置和展示
- 细粒度访问控制,完备的操作日志
- 支持跨模型搜索
更多详细功能,请移步 [维易科技官网](https://veops.cn) 进行了解。
### 系统优势
- 灵活性
+ 无需指定固定运维场景,支持自由配置并内置多种模板
+ 支持自动发现和入库 IT 资产,快速搭建资产管理系统
- 安全性
+ 细粒度的权限控制机制,确保资源管理的安全性
+ 完整的操作日志记录,便于审计和问题追踪
- 多应用
+ 提供多种视图展示方式,满足不同场景的需求
+ 强大的 API 接口,支持深度集成
+ 支持定义属性触发器和计算属性,增强数据处理能力
### 技术栈
+ 后端Python [3.8-3.11]
+ 数据存储MySQL、Redis
+ 前端Vue.js
+ UI组件库Ant Design Vue
### 更多功能
### 系统概览
<table style="border-collapse: collapse;">
<tr>
<td style="padding: 5px;background-color:#fff;">
<img width="400" src="https://github.com/user-attachments/assets/6d2df835-ae93-4d91-9bd9-213c270eca7a"/>
</td>
<td style="padding: 5px;background-color:#fff;">
<img width="400" src="https://github.com/user-attachments/assets/cb8b598a-a1f9-4c74-adf1-6e59aea2c9b3"/>
</td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;">
<img width="400" src="https://github.com/user-attachments/assets/b440224f-53c3-4b7f-a9be-285d7a4b848f"/>
</td>
<td style="padding: 5px;background-color:#fff;">
<img width="400" src="https://github.com/user-attachments/assets/f457d5a0-b60b-4949-b94e-020f4c61444b"/>
</td>
</tr>
</table>
## 关注我们
欢迎 Star 加关注,第一时间获取更新动态!
![star us](https://github.com/user-attachments/assets/f9056d5a-171c-4f53-9fec-d40c9e5ff94d)
## 快速开始
### 1. 搭建
+ 方案一Docker 一键快速构建
- 第1步: 安装 Docker 环境和 Docker Composev2
- 第2步: 拷贝项目代码, `git clone https://github.com/veops/cmdb.git`
- 第3步进入主目录并启动, `docker compose up -d`
+ 方案二:[本地开发环境搭建](docs/local.md)
+ 方案三:[Makefile 安装](docs/makefile.md)
### 2. 访问
- 打开浏览器并访问: [http://127.0.0.1:8000](http://127.0.0.1:8000)
- 用户名: demo 或者 admin
- 密码: 123456
> 也欢迎移步[维易科技官网](https://veops.cn),发现更多免费运维系统。
## 接入公司
+ 欢迎使用开源CMDB的公司和团队,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
> 欢迎使用开源CMDB的公司在 [#112](https://github.com/veops/cmdb/issues/112) 登记
## 代码贡献
我们欢迎所有开发者贡献代码,改善和扩展这个项目。请先阅读我们的[贡献指南](docs/CONTRIBUTING.md)。此外,您还可以通过社交媒体、活动和分享来支持 Veops 的开源。
## 安装
<a href="https://github.com/veops/cmdb/graphs/contributors">
<img src="https://contrib.rocks/image?repo=veops/cmdb" />
</a>
### Docker 一键快速构建
## 更多开源
[//]: # (> 方法一)
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
- 第二步: 拷贝项目
```shell
git clone https://github.com/veops/cmdb.git
```
- 第三步:进入主目录,执行:
```
docker compose up -d
```
- [OneTerm](https://github.com/veops/oneterm): 一款简单、轻量、灵活的堡垒机服务。
- [messenger](https://github.com/veops/messenger): 一个简单轻量的消息发送服务。
- [ACL](https://github.com/veops/acl): 一个简单通用的权限管理系统设计与实践。
[//]: # (> 方法二, 该方法适用于linux系统)
## 相关文章
[//]: # (- 第一步: 先安装 Docker 环境, 以及Docker Compose &#40;v2&#41;)
- <a href="https://mp.weixin.qq.com/s/v3eANth64UBW5xdyOkK3tg" target="_blank">尽可能通用的运维CMDB的设计与实践() - 概览</a>
- <a href="https://mp.weixin.qq.com/s/rQaf4AES7YJsyNQG_MKOLg" target="_blank">尽可能通用的运维CMDB的设计与实践() - 自动发现</a>
- <a href="https://github.com/veops/cmdb/tree/master/docs/cmdb_api.md" target="_blank">CMDB接口文档</a>
[//]: # (- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`)
更多文章可以在公众号 **维易科技OneOps** 里查看
[//]: # (```shell)
## 与我联系
[//]: # (curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh)
+ 邮箱: <a href="mailto:bd@veops.cn">bd@veops.cn</a>
+ 公众号:**维易科技OneOps**。关注后可以加入微信群,参与产品和技术交流
<img src="docs/images/wechat.png" alt="公众号: 维易科技OneOps" />
[//]: # (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
---
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
<p align="center">
<img src="docs/images/wechat.png" alt="公众号: 维易科技OneOps" />
</p>

View File

@@ -1,79 +0,0 @@
line-length = 120
cache-dir = ".ruff_cache"
target-version = "py310"
unsafe-fixes = true
show-fixes = true
[lint]
select = [
"E",
"F",
"I",
"TCH",
# W
"W505",
# PT
"PT018",
# SIM
"SIM101",
"SIM114",
# PGH
"PGH004",
# PL
"PLE1142",
# RUF
"RUF100",
# UP
"UP007"
]
preview = true
ignore = ["FURB101"]
[lint.flake8-pytest-style]
mark-parentheses = false
parametrize-names-type = "list"
parametrize-values-row-type = "list"
parametrize-values-type = "tuple"
[lint.flake8-unused-arguments]
ignore-variadic-names = true
[lint.isort]
lines-between-types = 1
order-by-type = true
[lint.per-file-ignores]
"**/api/v1/*.py" = ["TCH"]
"**/model/*.py" = ["TCH003"]
"**/models/__init__.py" = ["F401", "F403"]
"**/tests/*.py" = ["E402"]
"celery_worker.py" = ["F401"]
"api/views/entry.py" = ["I001"]
"migrations/*.py" = ["I001", "E402"]
"*.py" = ["I001"]
"api/views/common_setting/department.py" = ["F841"]
"api/lib/common_setting/upload_file.py" = ["F841"]
"api/lib/common_setting/acl.py" = ["F841"]
"**/__init__.py" = ["F822"]
"api/tasks/*.py" = ["E722"]
"api/views/cmdb/*.py" = ["E722"]
"api/views/acl/*.py" = ["E722"]
"api/lib/secrets/*.py" = ["E722", "F841"]
"api/lib/utils.py" = ["E722", "E731"]
"api/lib/perm/authentication/cas/*" = ["E113", "F841"]
"api/lib/perm/acl/*" = ["E722"]
"api/lib/*" = ["E721", "F722"]
"api/lib/cmdb/*" = ["F722", "E722"]
"api/lib/cmdb/search/ci/es/search.py" = ["F841", "SIM114"]
"api/lib/cmdb/search/ci/db/search.py" = ["F841"]
"api/lib/cmdb/value.py" = ["F841"]
"api/lib/cmdb/history.py" = ["E501"]
"api/commands/common.py" = ["E722"]
"api/commands/click_cmdb.py" = ["E722"]
"api/lib/perm/auth.py" = ["SIM114"]
[format]
preview = true
quote-style = "single"
docstring-code-format = true
skip-magic-trailing-comma = false

View File

@@ -1,8 +1,6 @@
import click
from flask import current_app
from flask.cli import with_appcontext
from api.lib.exception import AbortException
from api.lib.perm.acl.user import UserCRUD
@@ -48,18 +46,11 @@ def add_user():
email = click.prompt('Enter email ', confirmation_prompt=False)
is_admin = click.prompt('Admin (Y/N) ', confirmation_prompt=False, type=bool, default=False)
current_app.test_request_context().push()
UserCRUD.add(username=username, password=password, email=email)
try:
if is_admin:
app = AppCache.get('acl') or App.create(name='acl')
acl_admin = RoleCache.get_by_name(app.id, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
rid = RoleCache.get_by_name(None, username).id
UserCRUD.add(username=username, password=password, email=email)
if is_admin:
app = AppCache.get('acl') or App.create(name='acl')
acl_admin = RoleCache.get_by_name(None, 'acl_admin') or RoleCRUD.add_role('acl_admin', app.id, True)
rid = RoleCache.get_by_name(None, username).id
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)
except AbortException as e:
print(f"Failed: {e}")
RoleRelationCRUD.add(acl_admin, acl_admin.id, [rid], app.id)

View File

@@ -228,12 +228,6 @@ def cmdb_trigger():
"""
from api.lib.cmdb.ci import CITriggerManager
current_app.test_request_context().push()
if not UserCache.get('worker'):
from api.lib.perm.acl.user import UserCRUD
UserCRUD.add(username='worker', password=uuid.uuid4().hex, email='worker@xxx.com')
login_user(UserCache.get('worker'))
current_day = datetime.datetime.today().strftime("%Y-%m-%d")
trigger2cis = dict()
trigger2completed = dict()
@@ -262,10 +256,10 @@ def cmdb_trigger():
trigger2cis[trigger.id] = (trigger, ready_cis)
else:
cur = trigger2cis[trigger.id]
cur_ci_ids = {_ci.ci_id for _ci in cur[1]}
cur_ci_ids = {i.ci_id for i in cur[1]}
trigger2cis[trigger.id] = (
trigger, cur[1] + [_ci for _ci in ready_cis if _ci.ci_id not in cur_ci_ids
and _ci.ci_id not in trigger2completed.get(trigger.id, {})])
trigger, cur[1] + [i for i in ready_cis if i.ci_id not in cur_ci_ids
and i.ci_id not in trigger2completed.get(trigger.id, {})])
for tid in trigger2cis:
trigger, cis = trigger2cis[tid]
@@ -352,7 +346,7 @@ def cmdb_inner_secrets_init(address):
if valid_address(address):
token = current_app.config.get("INNER_TRIGGER_TOKEN", "") if not token else token
if not token:
token = click.prompt('Enter root token', hide_input=True, confirmation_prompt=False)
token = click.prompt(f'Enter root token', hide_input=True, confirmation_prompt=False)
assert token is not None
resp = requests.post("{}/api/v0.1/secrets/auto_seal".format(address.strip("/")),
headers={"Inner-Token": token})

View File

@@ -415,7 +415,7 @@ class AttributeManager(object):
db.session.rollback()
current_app.logger.error("update attribute error, {0}".format(str(e)))
return abort(400, ErrFormat.update_attribute_failed.format(("id={}".format(_id))))
return abort(400, ErrFormat.update_attribute_failed.format(("id=".format(_id))))
new = attr.to_dict()
if not new['choice_web_hook'] and new['is_choice']:

View File

@@ -12,7 +12,6 @@ from sqlalchemy import func
from api.extensions import db
from api.lib.cmdb.auto_discovery.const import CLOUD_MAP
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
from api.lib.cmdb.auto_discovery.const import NET_DEVICE_NAMES
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import AutoDiscoveryMappingCache
@@ -253,7 +252,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
:return:
"""
result = []
db.session.commit()
rules = cls.cls.get_by(to_dict=True)
for rule in rules:
@@ -720,12 +718,6 @@ class AutoDiscoveryCICRUD(DBMixin):
build_relations_for_ad_accept.apply_async(args=(adc.to_dict(), ci_id, ad_key2attr), queue=CMDB_QUEUE)
ci_type = CITypeCache.get(adc.type_id)
if ci_type and ci_type.name in NET_DEVICE_NAMES and 'ports' in adc.instance:
from api.tasks.cmdb import add_net_device_ports
add_net_device_ports.apply_async(args=(ci_id, adc.instance['ports']),
queue=CMDB_QUEUE)
adc.update(is_accept=True,
accept_by=nickname or current_user.nickname,
accept_time=datetime.datetime.now(),

View File

@@ -4,8 +4,6 @@ from api.lib.cmdb.const import AutoDiscoveryType
PRIVILEGED_USERS = ("cmdb_agent", "worker", "admin")
NET_DEVICE_NAMES = {"switch", 'router', 'firewall', 'printer'}
DEFAULT_INNER = [
dict(name="阿里云", en="aliyun", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-aliyun'}, "en": "aliyun"}),
@@ -43,12 +41,8 @@ DEFAULT_INNER = [
option={'icon': {'name': 'caise-luyouqi'}}),
dict(name="防火墙", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-fanghuoqiang'}}),
# dict(name="打印机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
# option={'icon': {'name': 'caise-dayinji'}}),
dict(name="光纤交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-fiber'}}),
dict(name="F5", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-F5'}}),
dict(name="打印机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
option={'icon': {'name': 'caise-dayinji'}}),
]
CLOUD_MAP = {

View File

@@ -1,74 +1,37 @@
[
{
"name": "manufacturer",
"type": "文本",
"example": "Huawei",
"desc": "制造产商"
},
{
"name": "sn",
"type": "文本",
"example": "102030059898",
"desc": "设备序列号"
},
{
"name": "name",
"type": "文本",
"example": "USG6525E",
"desc": "设备名称"
},
{
"name": "model",
"type": "文本",
"example": "2011.2.321.1.205",
"desc": "设备细分类型 结合相关产商获取相应的产品类型"
},
{
"name": "description",
"type": "文本",
"example": "Huawei Vwersatile Routing Platform Software",
"desc": "设备描述"
},
{
"name": "manager_ip",
"type": "文本",
"example": "192.168.1.1",
"desc": "管理ip"
},
{
"name": "ips",
"type": "文本、多值",
"example": "192.168.1.1, 192.168.1.2",
"desc": "ips"
},
{
"name": "uptime",
"type": "文本",
"example": "2023-04-15 10:00:00",
"desc": "启动时间"
},
{
"name": "snmp_version",
"type": "文本",
"example": "v2c",
"desc": "SNMP版本"
},
{
"name": "port_num",
"type": "整数",
"example": 24,
"desc": "端口数量"
},
{
"name": "ports",
"type": "json",
"example": "",
"desc": "设备的端口列表"
},
{
"name": "neighbors",
"type": "json",
"example": "",
"desc": "设备的邻居列表"
}
[{
"name":"manufacturer",
"type": "文本",
"example":"HUAWEI Technology Co.,Ltd",
"desc":"制造产商"
},{
"name":"sn",
"type": "文本",
"example":"102030059898",
"desc":"设备序列号"
},{
"name":"device_name",
"type": "文本",
"example":"USG6525E",
"desc":"设备名称"
},{
"name":"device_model",
"type": "文本",
"example":"2011.2.321.1.205",
"desc":"设备细分类型 结合相关产商获取相应的产品类型"
},{
"name":"description",
"type": "文本",
"example":"Huawei Vwersatile Routing Platform Software",
"desc":"设备描述"
},{
"name":"manager_ip",
"type": "文本",
"example":"192.168.1.1",
"desc":"管理ip"
}, {
"name":"ips",
"type": "文本、多值",
"example":"192.168.1.1, 192.168.1.2",
"desc":"ips"
}
]

View File

@@ -295,7 +295,7 @@ class CIManager(object):
db.session.commit()
value_table = TableMap(attr_name=attr.name).table
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name), expire=10):
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)):
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
getattr(value_table, 'value').desc()).first()
if max_v is not None:
@@ -360,28 +360,16 @@ class CIManager(object):
_sync=False,
**ci_dict):
"""
Create a new Configuration Item (CI) or update existing based on unique constraints.
Handles complete CI creation workflow including validation, uniqueness checks,
password encryption, computed attributes, relationship creation, and caching.
Args:
ci_type_name (str): Name of the CI type to create
exist_policy (ExistPolicy): How to handle existing CIs (REPLACE/REJECT/NEED)
_no_attribute_policy (ExistPolicy): How to handle unknown attributes (IGNORE/REJECT)
is_auto_discovery (bool): Whether CI is created by auto-discovery process
_is_admin (bool): Whether to skip permission checks
ticket_id (int, optional): Associated ticket ID for audit trail
_sync (bool): Whether to execute cache/relation tasks synchronously
**ci_dict: CI attribute values as key-value pairs
Returns:
int: ID of the created or updated CI
Raises:
400: If unique constraints violated, required attributes missing, or validation fails
403: If user lacks permissions for restricted attributes
404: If CI type not found or referenced CI not exists
add ci
:param ci_type_name:
:param exist_policy: replace or reject or need
:param _no_attribute_policy: ignore or reject
:param is_auto_discovery: default is False
:param _is_admin: default is False
:param ticket_id:
:param _sync:
:param ci_dict:
:return:
"""
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci_type = CITypeManager.check_is_existed(ci_type_name)
@@ -405,7 +393,7 @@ class CIManager(object):
ci = None
record_id = None
password_dict = {}
with redis_lock.Lock(rd.r, ci_type.name, expire=10):
with redis_lock.Lock(rd.r, ci_type.name):
db.session.commit()
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
@@ -524,24 +512,6 @@ class CIManager(object):
return ci.id
def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict):
"""
Update an existing Configuration Item with new attribute values.
Performs comprehensive CI update including validation, constraint checks,
password handling, computed attributes processing, and change tracking.
Args:
ci_id (int): ID of the CI to update
_is_admin (bool): Whether to skip permission checks
ticket_id (int, optional): Associated ticket ID for audit trail
_sync (bool): Whether to execute cache/relation tasks synchronously
**ci_dict: CI attribute values to update as key-value pairs
Raises:
400: If unique constraints violated or validation fails
403: If user lacks permissions for restricted attributes
404: If CI not found
"""
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id)
ci_type = ci.ci_type
@@ -554,14 +524,10 @@ class CIManager(object):
raw_dict = copy.deepcopy(ci_dict)
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
unique_name = None
for _, attr in attrs:
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
ci_dict[attr.name] = now
if attr.id == ci_type.unique_id:
unique_name = attr.name
value_manager = AttributeValueManager()
password_dict = dict()
@@ -584,15 +550,14 @@ class CIManager(object):
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
record_id = None
with redis_lock.Lock(rd.r, ci_type.name, expire=10):
with redis_lock.Lock(rd.r, ci_type.name):
db.session.commit()
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
ci_attr2type_attr=ci_attr2type_attr,
unique_name=unique_name)
ci_attr2type_attr=ci_attr2type_attr)
if computed_attrs:
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
@@ -1303,9 +1268,7 @@ class CIRelationManager(object):
else:
type_relation = CITypeRelation.get_by_id(relation_type_id)
with redis_lock.Lock(rd.r,
"ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id),
expire=10):
with redis_lock.Lock(rd.r, "ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id)):
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
@@ -1450,31 +1413,14 @@ class CIRelationManager(object):
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
if attr_value != 0 and not attr_value:
continue
value_table = TableMap(attr=child_attr).table
attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
matching_cis = value_table.get_by(
attr_id=child_attr.id,
only_query=True
).join(
CI, CI.id == value_table.ci_id
).filter(
CI.type_id == item.child_id,
value_table.value.in_(attr_value_list)
).all()
for ci in matching_cis:
_relations.add((ci_dict['_id'], ci.ci_id))
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
_relations.add((ci_dict['_id'], child.ci_id))
if relations is None:
relations = _relations
else:
if item.constraint == ConstraintEnum.Many2Many:
relations |= _relations
else:
relations &= _relations
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
first_ci_id=ci_dict['_id'],
@@ -1494,31 +1440,14 @@ class CIRelationManager(object):
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(child_attr.name)
if attr_value != 0 and not attr_value:
continue
value_table = TableMap(attr=parent_attr).table
attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
matching_cis = value_table.get_by(
attr_id=parent_attr.id,
only_query=True
).join(
CI, CI.id == value_table.ci_id
).filter(
CI.type_id == item.parent_id,
value_table.value.in_(attr_value_list)
).all()
for ci in matching_cis:
_relations.add((ci.ci_id, ci_dict['_id']))
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
_relations.add((parent.ci_id, ci_dict['_id']))
if relations is None:
relations = _relations
else:
if item.constraint == ConstraintEnum.Many2Many:
relations |= _relations
else:
relations &= _relations
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
second_ci_id=ci_dict['_id'],
@@ -1605,8 +1534,7 @@ class CITriggerManager(object):
ci_dict.update(attr_dict)
@classmethod
def _exec_webhook(cls, operate_type, webhook, ci_dict, trigger_id, trigger_name, record_id,
ci_id=None, app=None, record_history=True):
def _exec_webhook(cls, operate_type, webhook, ci_dict, trigger_id, trigger_name, record_id, ci_id=None, app=None):
app = app or current_app
with app.app_context():
@@ -1624,20 +1552,19 @@ class CITriggerManager(object):
current_app.logger.warning("exec webhook failed: {}".format(e))
response = e
is_ok = False
if record_history:
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
trigger_name,
is_ok=is_ok,
webhook=response)
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
trigger_name,
is_ok=is_ok,
webhook=response)
return is_ok
@classmethod
def _exec_notify(cls, operate_type, notify, ci_dict, trigger_id, trigger_name, record_id,
ci_id=None, app=None, record_history=True):
def _exec_notify(cls, operate_type, notify, ci_dict, trigger_id, trigger_name, record_id, ci_id=None, app=None):
app = app or current_app
with app.app_context():
@@ -1661,14 +1588,13 @@ class CITriggerManager(object):
response = "{}\n{}".format(response, e)
is_ok = False
if record_history:
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
trigger_name,
is_ok=is_ok,
notify=response.strip())
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
trigger_name,
is_ok=is_ok,
notify=response.strip())
return is_ok
@@ -1745,47 +1671,25 @@ class CITriggerManager(object):
return result
@classmethod
def trigger_notify(cls, trigger, ci, only_test=False):
def trigger_notify(cls, trigger, ci):
"""
only for date attribute
:param trigger:
:param ci:
:param only_test:
:return:
"""
if (trigger.option.get('notifies', {}).get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
not trigger.option.get('notifies', {}).get('notify_at')) or only_test:
not trigger.option.get('notifies', {}).get('notify_at')):
if trigger.option.get('webhooks'):
threading.Thread(
target=cls._exec_webhook,
args=(None, trigger.option['webhooks'], None, trigger.id,
trigger.option.get('name'), None,
ci and ci.ci_id,
current_app._get_current_object(), not only_test)).start()
threading.Thread(target=cls._exec_webhook, args=(
None, trigger.option['webhooks'], None, trigger.id, trigger.option.get('name'), None, ci.ci_id,
current_app._get_current_object())).start()
elif trigger.option.get('notifies'):
threading.Thread(target=cls._exec_notify, args=(
None, trigger.option['notifies'], None, trigger.id,
trigger.option.get('name'), None,
ci and ci.ci_id,
current_app._get_current_object(), not only_test)).start()
None, trigger.option['notifies'], None, trigger.id, trigger.option.get('name'), None, ci.ci_id,
current_app._get_current_object())).start()
return True
return False
@classmethod
def trigger_notify_test(cls, type_id, trigger_id):
trigger = CITypeTrigger.get_by_id(trigger_id) or abort(
404, ErrFormat.ci_type_trigger_not_found.format(trigger_id))
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found.format(type_id))
attr = AttributeCache.get(ci_type.unique_id)
value_table = TableMap(attr=attr).table
if not value_table:
return
value = value_table.get_by(attr_id=attr.id, only_query=True).join(
CI, value_table.ci_id == CI.id).filter(CI.type_id == type_id).first()
cls.trigger_notify(trigger, value, only_test=True)

View File

@@ -421,10 +421,7 @@ class CITypeGroupManager(object):
group_types = set()
for group in groups:
for t in sorted(CITypeGroupItem.get_by(group_id=group['id']), key=lambda x: x['order'] or 0):
ci_type = CITypeCache.get(t['type_id'])
if ci_type is None:
continue
ci_type = ci_type.to_dict()
ci_type = CITypeCache.get(t['type_id']).to_dict()
if type_ids is not None and ci_type['id'] not in type_ids:
continue
if resources is None or (ci_type and ci_type['name'] in resources):
@@ -722,6 +719,9 @@ class CITypeAttributeManager(object):
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
for item in PreferenceShowAttributes.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
item.soft_delete(commit=False)
child_ids = CITypeInheritanceManager.recursive_children(type_id)
for _type_id in [type_id] + child_ids:
for item in CITypeUniqueConstraint.get_by(type_id=_type_id, to_dict=False):
@@ -737,9 +737,6 @@ class CITypeAttributeManager(object):
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
item and item.soft_delete(commit=False)
for item in PreferenceShowAttributes.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False):
item.soft_delete(commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []):
@@ -862,15 +859,15 @@ class CITypeRelationManager(object):
graph = nx.DiGraph()
def get_children(_id, _graph):
def get_children(_id):
children = CITypeRelation.get_by(parent_id=_id, to_dict=False)
for i in children:
if i.child_id != _id:
_graph.add_edge(i.parent_id, i.child_id)
get_children(i.child_id, _graph)
graph.add_edge(i.parent_id, i.child_id)
get_children(i.child_id)
get_children(source_type_id, graph)
get_children(source_type_id)
paths = list(nx.all_simple_paths(graph, source_type_id, target_type_ids))
@@ -1145,14 +1142,13 @@ class CITypeAttributeGroupManager(object):
else:
group_pos = group2pos[group['name']]
attr = None
for i in items:
if i.attr_id in id2attr:
attr = id2attr[i.attr_id]
attr['inherited'] = group['inherited']
attr['inherited_from'] = group.get('inherited_from')
result[group_pos]['attributes'].append(attr)
else:
continue
if i.attr_id in attr2pos:
result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1])
@@ -1399,7 +1395,6 @@ class CITypeTemplateManager(object):
i.pop('order', None)
i.pop('choice_web_hook', None)
i.pop('choice_other', None)
i.pop('choice_builtin', None)
i.pop('order', None)
i.pop('inherited_from', None)
choice_value = i.pop('choice_value', None)

View File

@@ -44,7 +44,7 @@ class RackManager(DCIMBase):
CIManager().update(_id, _sync=True, **kwargs)
if RackBuiltinAttributes.U_COUNT in kwargs:
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls.calc_u_free_count(_id)}
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls._calc_u_free_count(_id)}
CIManager().update(_id, _sync=True, **payload)
@@ -57,7 +57,7 @@ class RackManager(DCIMBase):
CIManager().update(ci['_id'], **payload)
@staticmethod
def calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
def _calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
rack = CIManager.get_ci_by_id(rack_id, need_children=False)
if not rack.get(RackBuiltinAttributes.U_COUNT):
return 0
@@ -122,8 +122,8 @@ class RackManager(DCIMBase):
CIManager().update(rack['_id'], **payload)
def add_device(self, rack_id, device_id, u_start, u_count=None):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), expire=10)):
self.calc_u_free_count(rack_id, device_id, u_start, u_count)
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
self._calc_u_free_count(rack_id, device_id, u_start, u_count)
self.add_relation(rack_id, device_id)
@@ -133,16 +133,16 @@ class RackManager(DCIMBase):
CIManager().update(device_id, _sync=True, **payload)
payload = {
RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id, device_id, u_start, u_count)}
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, u_start, u_count)}
CIManager().update(rack_id, _sync=True, **payload)
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=rack_id, ci_id=device_id)
def remove_device(self, rack_id, device_id):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), expire=10)):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id)}
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
CIManager().update(rack_id, _sync=True, **payload)
payload = {RackBuiltinAttributes.U_START: None}
@@ -151,8 +151,8 @@ class RackManager(DCIMBase):
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
def move_device(self, rack_id, device_id, to_u_start):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), expire=10)):
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id, device_id, to_u_start)}
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, to_u_start)}
CIManager().update(rack_id, _sync=True, **payload)
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
@@ -160,8 +160,8 @@ class RackManager(DCIMBase):
OperateHistoryManager().add(operate_type=OperateTypeEnum.MOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
def migrate_device(self, rack_id, device_id, to_rack_id, to_u_start):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), expire=10)):
self.calc_u_free_count(to_rack_id, device_id, to_u_start)
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
self._calc_u_free_count(to_rack_id, device_id, to_u_start)
if rack_id != to_rack_id:
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
@@ -169,14 +169,15 @@ class RackManager(DCIMBase):
self.add_relation(to_rack_id, device_id)
payload = {
RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(to_rack_id, device_id, to_u_start)}
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(to_rack_id, device_id, to_u_start)}
CIManager().update(to_rack_id, _sync=True, **payload)
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
if rack_id != to_rack_id:
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id)}
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
CIManager().update(rack_id, _sync=True, **payload)
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=to_rack_id, ci_id=device_id)

View File

@@ -31,11 +31,10 @@ class IpAddressManager(object):
return numfound, result
def _get_cis(self, subnet_id, ips):
q = "_type:{},{}:({})".format(self.type_id, IPAddressBuiltinAttributes.IP, ";".join(ips or []))
response, _, _, _, _, _ = RelationSearch([subnet_id], level=[1], query=q, count=1000000).search()
def _get_cis(self, ips):
response, _, _, _, _, _ = SearchFromDB(
"_type:{},{}:({})".format(self.type_id, IPAddressBuiltinAttributes.IP, ";".join(ips or [])),
count=10000000, parent_node_perm_passed=True).search()
return response
@@ -92,9 +91,8 @@ class IpAddressManager(object):
else:
return abort(400, ErrFormat.ipam_address_model_not_found)
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id),
expire=60, auto_renewal=True)):
cis = self._get_cis(subnet_id, ips)
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id))):
cis = self._get_cis(ips)
ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis}
ci_ids = []

View File

@@ -43,7 +43,7 @@ class ScanHistoryManager(DBMixin):
if kwargs.get('ips'):
from api.lib.cmdb.ipam.address import IpAddressManager
IpAddressManager().assign_ips(kwargs['ips'], ci_id, kwargs.get('cidr'),
IpAddressManager().assign_ips(kwargs['ips'], None, kwargs.get('cidr'),
**{IPAddressBuiltinAttributes.IS_USED: 1})
scan_rule = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False)

View File

@@ -163,7 +163,7 @@ class CIFilterPermsCRUD(DBMixin):
def add(self, **kwargs):
kwargs = self._can_add(**kwargs) or kwargs
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
request_id_filter = {}
if kwargs.get('id_filter'):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
@@ -232,7 +232,7 @@ class CIFilterPermsCRUD(DBMixin):
pass
def delete(self, **kwargs):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
id_filter=None,
@@ -249,7 +249,7 @@ class CIFilterPermsCRUD(DBMixin):
def delete2(self, **kwargs):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid'])):
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
rid=kwargs.get('rid'),
ci_filter=None,

View File

@@ -1,8 +1,9 @@
# -*- coding:utf-8 -*-
import copy
from collections import defaultdict
import copy
import six
import toposort
from flask import abort
from flask import current_app
@@ -15,7 +16,6 @@ from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import CMDBCounterCache
from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum
@@ -29,7 +29,6 @@ from api.lib.perm.acl.acl import ACLManager
from api.models.cmdb import CITypeGroup
from api.models.cmdb import CITypeGroupItem
from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceAutoSubscriptionConfig
from api.models.cmdb import PreferenceCITypeOrder
from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption
@@ -50,27 +49,15 @@ class PreferenceManager(object):
type2group = {}
for i in db.session.query(CITypeGroupItem, CITypeGroup).join(
CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter(
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)):
type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict()
if instance:
auto_types = PreferenceManager.get_auto_subscription_types(current_user.uid)
if auto_types is not None:
class TypeIdObj:
def __init__(self, type_id):
self.type_id = type_id
types = [TypeIdObj(t) for t in auto_types]
else:
types = db.session.query(PreferenceShowAttributes.type_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.type_id).all()
else:
types = []
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(ci_type_order)
if not i.is_tree}.get(x.type_id, 1))
types = db.session.query(PreferenceShowAttributes.type_id).filter(
PreferenceShowAttributes.uid == current_user.uid).filter(
PreferenceShowAttributes.deleted.is_(False)).group_by(
PreferenceShowAttributes.type_id).all() if instance else []
types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate(
ci_type_order) if not i.is_tree}.get(x.type_id, 1))
group_types = []
other_types = []
group2idx = {}
@@ -117,26 +104,19 @@ class PreferenceManager(object):
ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order)
if instance:
# Try auto subscription first, fallback to manual if not configured
auto_types = PreferenceManager.get_auto_subscription_types(current_user.uid)
if auto_types is not None:
result['self']['instance'] = auto_types
for type_id in auto_types:
result['self']['type_id2subs_time'][type_id] = ""
else:
types = db.session.query(PreferenceShowAttributes.type_id,
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
PreferenceShowAttributes.deleted.is_(False)).filter(
PreferenceShowAttributes.uid == current_user.uid).group_by(
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
for i in types:
result['self']['instance'].append(i.type_id)
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
result['self']['type_id2subs_time'][i.type_id] = i.created_at
types = db.session.query(PreferenceShowAttributes.type_id,
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
PreferenceShowAttributes.deleted.is_(False)).filter(
PreferenceShowAttributes.uid == current_user.uid).group_by(
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
for i in types:
result['self']['instance'].append(i.type_id)
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
result['self']['type_id2subs_time'][i.type_id] = i.created_at
instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
if len(instance_order) == len(result['self']['instance']):
result['self']['instance'] = instance_order
instance_order = [i.type_id for i in ci_type_order if not i.is_tree]
if len(instance_order) == len(result['self']['instance']):
result['self']['instance'] = instance_order
if tree:
types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False)
@@ -151,73 +131,15 @@ class PreferenceManager(object):
return result
@staticmethod
def get_auto_subscription_types(uid):
"""Get user's auto-subscribed CI types based on config rules"""
config = PreferenceAutoSubscriptionConfig.get_by(
uid=uid, enabled=True, first=True, to_dict=False
)
if not config:
return None
all_permitted_types = PreferenceManager._get_permitted_ci_types()
result_types = PreferenceManager._apply_subscription_config(config, all_permitted_types)
return result_types
@staticmethod
def _get_permitted_ci_types():
"""Get CI types that user has read permission for"""
from api.lib.perm.acl.acl import is_app_admin
if not current_app.config.get('USE_ACL') or is_app_admin('cmdb'):
return [t['id'] for t in CITypeManager.get_ci_types()]
# Regular user: filter by permissions
permitted_resources = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI)
permitted_names = {r.get('name') for r in permitted_resources}
return [ci_type_dict['id'] for ci_type_dict in CITypeManager.get_ci_types()
if ci_type_dict['name'] in permitted_names]
@staticmethod
def _apply_subscription_config(config, all_permitted_types):
"""Apply subscription rules: 'all' mode excludes, 'none' mode includes"""
result_types = set()
if config.base_strategy == 'all':
# Start with all types, then exclude
result_types = set(all_permitted_types)
if config.group_ids:
exclude_group_type_ids = PreferenceManager._get_types_by_group_ids(config.group_ids)
result_types.difference_update(exclude_group_type_ids)
if config.type_ids:
result_types.difference_update(config.type_ids)
else: # base_strategy == 'none'
# Start empty, then include
if config.group_ids:
include_group_type_ids = PreferenceManager._get_types_by_group_ids(config.group_ids)
result_types.update(t for t in include_group_type_ids if t in all_permitted_types)
if config.type_ids:
result_types.update(t for t in config.type_ids if t in all_permitted_types)
return list(result_types)
@staticmethod
def _get_types_by_group_ids(group_ids):
return [i.type_id for i in CITypeGroupItem.get_by(
__func_in___key_group_id=group_ids, to_dict=False, fl=['type_id'])]
@staticmethod
def get_show_attributes(type_id):
_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found)
type_id = _type and _type.id
if not isinstance(type_id, six.integer_types):
_type = CITypeCache.get(type_id)
type_id = _type and _type.id
attrs = PreferenceShowAttributes.get_by(uid=current_user.uid, type_id=type_id, to_dict=False)
result = []
@@ -248,11 +170,11 @@ class PreferenceManager(object):
i.update(dict(choice_value=AttributeManager.get_choice_values(
i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other"))))
if (_type.name in SysComputedAttributes.type2attr and
i['name'] in SysComputedAttributes.type2attr[_type.name]):
i['sys_computed'] = True
else:
i['sys_computed'] = False
if (_type.name in SysComputedAttributes.type2attr and
i['name'] in SysComputedAttributes.type2attr[_type.name]):
i['sys_computed'] = True
else:
i['sys_computed'] = False
return is_subscribed, result
@@ -601,54 +523,3 @@ class PreferenceManager(object):
db.session.rollback()
current_app.logger.error("upsert citype order failed: {}".format(e))
return abort(400, ErrFormat.unknown_error)
@staticmethod
def get_auto_subscription_config():
"""Get user's auto subscription configuration"""
config = PreferenceAutoSubscriptionConfig.get_by(
uid=current_user.uid, first=True, to_dict=True
)
return config
@staticmethod
def create_or_update_auto_subscription_config(base_strategy, group_ids=None, type_ids=None,
enabled=True, description=None):
"""Create or update user's auto subscription config"""
config = PreferenceAutoSubscriptionConfig.get_by(
uid=current_user.uid, first=True, to_dict=False
)
data = {
'base_strategy': base_strategy,
'group_ids': group_ids or [],
'type_ids': type_ids or [],
'enabled': enabled,
'description': description
}
if config:
return config.update(**data)
else:
data['uid'] = current_user.uid
return PreferenceAutoSubscriptionConfig.create(**data)
@staticmethod
def delete_auto_subscription_config():
"""Delete user's auto subscription configuration"""
config = PreferenceAutoSubscriptionConfig.get_by(
uid=current_user.uid, first=True, to_dict=False
)
if config:
config.soft_delete()
return True
@staticmethod
def toggle_auto_subscription_config(enabled):
"""Enable or disable user's auto subscription config"""
config = PreferenceAutoSubscriptionConfig.get_by(
uid=current_user.uid, first=True, to_dict=False
)
if not config:
return abort(404, "Auto subscription config not found")
return config.update(enabled=enabled)

View File

@@ -16,9 +16,8 @@ class ErrFormat(CommonErrFormat):
argument_file_not_found = _l("The file doesn't seem to be uploaded") # 文件似乎并未上传
attribute_not_found = _l("Attribute {} does not exist!") # 属性 {} 不存在!
# 该属性是模型的唯一标识,不能被删除!
attribute_is_unique_id = _l(
"This attribute is the unique identifier of the model and cannot be deleted!")
"This attribute is the unique identifier of the model and cannot be deleted!") # 该属性是模型的唯一标识,不能被删除!
attribute_is_ref_by_type = _l(
"This attribute is referenced by model {} and cannot be deleted!") # 该属性被模型 {} 引用, 不能删除!
attribute_value_type_cannot_change = _l(
@@ -130,8 +129,7 @@ class ErrFormat(CommonErrFormat):
adr_default_ref_once = _l("The default auto-discovery rule is already referenced by model {}!")
# unique_key方法必须返回非空字符串!
adr_unique_key_required = _l("The unique_key method must return a non-empty string!")
# attributes方法必须返回的是list
adr_plugin_attributes_list_required = _l("The attributes method must return a list")
adr_plugin_attributes_list_required = _l("The attributes method must return a list") # attributes方法必须返回的是list
# attributes方法返回的list不能为空!
adr_plugin_attributes_list_no_empty = _l("The list returned by the attributes method cannot be empty!")
# 只有管理员才可以定义执行机器为: 所有节点!

View File

@@ -107,12 +107,3 @@ FROM
WHERE c_value_index_datetime.value LIKE "{0}") AS {1}
GROUP BY {1}.ci_id
"""
QUERY_CI_BY_NO_ATTR_IN = """
SELECT *
FROM
(SELECT c_value_index_texts.ci_id
FROM c_value_index_texts
WHERE c_value_index_texts.value in ({0})) AS {1}
GROUP BY {1}.ci_id
"""

View File

@@ -6,7 +6,6 @@ from __future__ import unicode_literals
import copy
import six
import time
from flask import abort
from flask import current_app
from flask_login import current_user
from jinja2 import Template
@@ -28,7 +27,6 @@ from api.lib.cmdb.search.ci.db.query_sql import FACET_QUERY
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ATTR_NAME
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ID
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR_IN
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_TYPE
from api.lib.cmdb.search.ci.db.query_sql import QUERY_UNION_CI_ATTRIBUTE_IS_NULL
from api.lib.cmdb.utils import TableMap
@@ -143,7 +141,7 @@ class Search(object):
if str(ci_type.id) in self.type_id_list:
self.type_id_list.remove(str(ci_type.id))
type_id_list.remove(str(ci_type.id))
sub.extend([i for i in queries[1:] if isinstance(i, (six.string_types, list))])
sub.extend([i for i in queries[1:] if isinstance(i, six.string_types)])
sub.insert(0, "_type:{}".format(ci_type.id))
queries.append(dict(operator="|", queries=sub))
@@ -153,9 +151,7 @@ class Search(object):
if not self.fl:
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
else:
fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
not fl and abort(400, ErrFormat.ci_filter_perm_attr_no_permission.format(self.fl))
self.fl = fl
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
else:
self.fl = self.fl or {}
if not self.fl or isinstance(self.fl, dict):
@@ -437,14 +433,11 @@ class Search(object):
if not q.startswith("("):
raise SearchError(ErrFormat.ci_search_Parentheses_invalid)
if ":" not in q: # multi-line search
result.append(q[1:-1].split(';'))
else:
operator, q = self._operator_proc(q)
if q.endswith(")"):
result.append(dict(operator=operator, queries=[q[1:-1]]))
operator, q = self._operator_proc(q)
if q.endswith(")"):
result.append(dict(operator=operator, queries=[q[1:-1]]))
sub = dict(operator=operator, queries=[q[1:]])
sub = dict(operator=operator, queries=[q[1:]])
elif q.endswith(")") and sub:
sub['queries'].append(q[:-1])
result.append(copy.deepcopy(sub))
@@ -532,31 +525,22 @@ class Search(object):
query_sql = ""
for q in queries:
# current_app.logger.debug(q)
_query_sql = ""
if isinstance(q, dict):
if len(q['queries']) == 1 and ";" in q['queries'][0]:
values = q['queries'][0].split(";")
in_values = ",".join("'{0}'".format(v) for v in values)
_query_sql = QUERY_CI_BY_NO_ATTR_IN.format(in_values, alias)
operator = q['operator']
else:
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias,
is_sub=True)
operator = q['operator']
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias, is_sub=True)
# current_app.logger.info(_query_sql)
# current_app.logger.info((operator, is_first, alias))
operator = q['operator']
elif ":" in q and not q.startswith("*"):
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias, is_sub)
elif q == "*":
continue
elif q:
if not isinstance(q, list):
q = q.replace("'", "\\'")
q = q.replace('"', '\\"')
q = q.replace("*", "%").replace('\\n', '%')
_query_sql = QUERY_CI_BY_NO_ATTR.format(q, alias)
else:
_query_sql = QUERY_CI_BY_NO_ATTR_IN.format(",".join("'{0}'".format(v) for v in q), alias)
q = q.replace("'", "\\'")
q = q.replace('"', '\\"')
q = q.replace("*", "%").replace('\\n', '%')
_query_sql = QUERY_CI_BY_NO_ATTR.format(q, alias)
if is_first and _query_sql and not self.only_type_query:
query_sql = "SELECT * FROM ({0}) AS {1}".format(_query_sql, alias)

View File

@@ -64,7 +64,7 @@ class Search(object):
self.ancestor_ids = ancestor_ids
self.descendant_ids = descendant_ids
self.root_parent_path = root_parent_path or []
self.root_parent_path = root_parent_path
self.has_m2m = has_m2m or False
if not self.has_m2m:
if self.ancestor_ids:

View File

@@ -55,6 +55,7 @@ def str2datetime(x):
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
class ValueTypeMap(object):
deserialize = {
ValueTypeEnum.INT: string2int,

View File

@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import importlib.util
import imp
import copy
import jinja2
@@ -136,7 +136,7 @@ class AttributeValueManager(object):
if not re.compile(expr).match(str(value)):
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None, unique_name=None):
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
if not attr.is_reference:
ci = ci or {}
v = self._deserialize_value(attr.alias, attr.value_type, value)
@@ -146,7 +146,7 @@ class AttributeValueManager(object):
else:
v = value or None
(attr.is_unique or attr.name == unique_name) and self._check_is_unique(
attr.is_unique and self._check_is_unique(
value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
self._check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
if attr.is_reference:
@@ -180,15 +180,14 @@ class AttributeValueManager(object):
@staticmethod
def _compute_attr_value_from_expr(expr, ci_dict):
t = jinja2.Template(expr).render(ci_dict)
try:
result = jinja2.Template(expr).render(ci_dict)
return result
return eval(t)
except Exception as e:
current_app.logger.warning(
f"Expression evaluation error - Expression: '{expr}'"
f"Input parameters: {ci_dict}, Error type: {type(e).__name__}, Error message: {str(e)}"
)
return None
current_app.logger.warning(str(e))
return t
@staticmethod
def _compute_attr_value_from_script(script, ci_dict):
script = jinja2.Template(script).render(ci_dict)
@@ -199,11 +198,11 @@ class AttributeValueManager(object):
try:
path = script_f.name
name = os.path.basename(path)[:-3]
dir_name, name = os.path.dirname(path), os.path.basename(path)[:-3]
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
fp, path, desc = imp.find_module(name, [dir_name])
mod = imp.load_module(name, fp, path, desc)
if hasattr(mod, 'computed'):
return mod.computed()
@@ -238,10 +237,7 @@ class AttributeValueManager(object):
if computed_value is not None:
ci_dict[attr['name']] = computed_value
def valid_attr_value(self, ci_dict, type_id, ci_id, name2attr,
alias2attr=None,
ci_attr2type_attr=None,
unique_name=None):
def valid_attr_value(self, ci_dict, type_id, ci_id, name2attr, alias2attr=None, ci_attr2type_attr=None):
key2attr = dict()
alias2attr = alias2attr or {}
ci_attr2type_attr = ci_attr2type_attr or {}
@@ -272,8 +268,7 @@ class AttributeValueManager(object):
else:
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
type_attr=ci_attr2type_attr.get(attr.id),
unique_name=unique_name)
type_attr=ci_attr2type_attr.get(attr.id))
ci_dict[key] = value
except BadRequest as e:
raise

View File

@@ -1,6 +1,6 @@
import functools
from flask import abort, session, current_app
from flask import abort, session
from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.perm.acl.acl import is_app_admin
@@ -15,7 +15,6 @@ def perms_role_required(app_name, resource_type_name, resource_name, perm, role_
try:
has_perms = acl.role_has_perms(session["acl"]['rid'], resource_name, resource_type_name, perm)
except Exception as e:
current_app.logger.error(f"acl role_has_perms err: {e}")
# resource_type not exist, continue check role
if role_name:
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin(app_name):

View File

@@ -476,7 +476,7 @@ class EditDepartmentInACL(object):
for employee in e_list:
employee_acl_rid = employee.get('e_acl_rid')
if employee_acl_rid == 0:
result.append("employee_acl_rid == 0")
result.append(f"employee_acl_rid == 0")
continue
cls.remove_single_employee_from_old_department(acl, employee, result)
@@ -501,8 +501,8 @@ class EditDepartmentInACL(object):
acl.remove_user_from_role(employee.get('e_acl_rid'), payload)
current_app.logger.info(f"remove {employee.get('e_acl_rid')} from {d_acl_rid}")
except Exception as e:
err = f"remove_user_from_role e_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}"
result.append(err)
result.append(
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {e}")
return True
@@ -548,7 +548,7 @@ class EditDepartmentInACL(object):
for employee in e_list:
employee_acl_rid = employee.get('e_acl_rid')
if employee_acl_rid == 0:
result.append("employee_acl_rid == 0")
result.append(f"employee_acl_rid == 0")
continue
cls.remove_single_employee_from_old_department(acl, employee, result)

View File

@@ -4,7 +4,7 @@ import traceback
from datetime import datetime
import requests
from flask import abort, current_app
from flask import abort
from flask_login import current_user
from sqlalchemy import or_, literal_column, func, not_, and_
from werkzeug.datastructures import MultiDict
@@ -478,7 +478,7 @@ class EmployeeCRUD(object):
Employee.deleted == 0,
Employee.block == block,
]
if isinstance(department_id, list):
if type(department_id) == list:
if len(department_id) == 0:
return []
else:
@@ -702,7 +702,6 @@ class EmployeeCRUD(object):
try:
last_login = datetime.strptime(last_login, '%Y-%m-%d %H:%M:%S')
except Exception as e:
current_app.logger.error(f"strptime {last_login} err: {e}")
last_login = datetime.now()
else:
last_login = datetime.now()
@@ -713,7 +712,6 @@ class EmployeeCRUD(object):
)
return last_login
except Exception as e:
current_app.logger.error(f"update last_login err: {e}")
return

View File

@@ -2,7 +2,7 @@ import requests
from api.lib.common_setting.const import BotNameMap
from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import NoticeConfig
from api.models.common_setting import CompanyInfo, NoticeConfig
from wtforms import Form
from wtforms import StringField
from wtforms import validators

View File

@@ -48,9 +48,7 @@ class CMDBApp(BaseApp):
{"page": "Model_Relationships", "page_cn": "模型关系", "perms": ["read"]},
{"page": "Operation_Audit", "page_cn": "操作审计", "perms": ["read"]},
{"page": "Relationship_Types", "page_cn": "关系类型", "perms": ["read"]},
{"page": "Auto_Discovery", "page_cn": "自动发现",
"perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]
},
{"page": "Auto_Discovery", "page_cn": "自动发现", "perms": ["read", "create_plugin", "update_plugin", "delete_plugin"]},
{"page": "TopologyView", "page_cn": "拓扑视图",
"perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group",
"create_topology_view"],

View File

@@ -6,7 +6,7 @@ from functools import wraps
from flask import abort
from flask import request
from api.lib.perm.acl.cache import AppCache
from api.lib.perm.acl.cache import AppCache, AppAccessTokenCache
from api.lib.perm.acl.resp_format import ErrFormat

View File

@@ -138,14 +138,14 @@ class HasResourceRoleCache(object):
@classmethod
def add(cls, rid, app_id):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache', expire=10):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
c = cls.get(app_id)
c[rid] = 1
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
@classmethod
def remove(cls, rid, app_id):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache', expire=10):
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
c = cls.get(app_id)
c.pop(rid, None)
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)

View File

@@ -1,5 +1,8 @@
# -*- coding:utf-8 -*-
import time
import redis_lock
import six
from flask import abort
@@ -142,7 +145,7 @@ class RoleRelationCRUD(object):
@classmethod
def add(cls, role, parent_id, child_ids, app_id):
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD", expire=10):
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
db.session.commit()
result = []

View File

@@ -1,6 +1,7 @@
# -*- coding:utf-8 -*-
import base64
from typing import Set
import elasticsearch
import redis

View File

@@ -105,8 +105,8 @@ class User(CRUDModel, SoftDeleteMixin):
_password = db.Column("password", db.String(80))
key = db.Column(db.String(32), nullable=False)
secret = db.Column(db.String(32), nullable=False)
date_joined = db.Column(db.DateTime, default=datetime.now)
last_login = db.Column(db.DateTime, default=datetime.now)
date_joined = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime, default=datetime.utcnow)
block = db.Column(db.Boolean, default=False)
has_logined = db.Column(db.Boolean, default=False)
wx_id = db.Column(db.String(32))

View File

@@ -525,17 +525,6 @@ class PreferenceCITypeOrder(Model):
is_tree = db.Column(db.Boolean, default=False) # True is tree view, False is resource view
class PreferenceAutoSubscriptionConfig(Model):
__tablename__ = "c_pasc"
uid = db.Column(db.Integer, index=True, nullable=False, unique=True)
base_strategy = db.Column(db.Enum('all', 'none'), default='none', nullable=False)
group_ids = db.Column(db.JSON)
type_ids = db.Column(db.JSON)
enabled = db.Column(db.Boolean, default=True, nullable=False)
description = db.Column(db.Text)
# custom
class CustomDashboard(Model):
__tablename__ = "c_c_d"

View File

@@ -32,7 +32,7 @@ from api.models.acl import Trigger
def role_rebuild(rids, app_id):
rids = rids if isinstance(rids, list) else [rids]
for rid in rids:
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id), expire=10):
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
RoleRelationCache.rebuild(rid, app_id)
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))

View File

@@ -1,4 +1,4 @@
# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
import datetime
@@ -145,7 +145,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@flush_db
@reconnect_db
def ci_relation_cache(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id), expire=10):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
@@ -223,7 +223,7 @@ def ci_relation_add(parent_dict, child_id, uid):
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
@reconnect_db
def ci_relation_delete(parent_id, child_id, ancestor_ids):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id), expire=10):
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
@@ -374,48 +374,3 @@ def build_relations_for_ad_accept(adc, ci_id, ad_key2attr):
source=RelationSourceEnum.AUTO_DISCOVERY)
except:
pass
@celery.task(name="cmdb.add_net_device_ports", queue=CMDB_QUEUE)
@reconnect_db
def add_net_device_ports(ci_id, ports):
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.cache import CITypeCache
port_type = CITypeCache.get("net_port")
if not port_type:
current_app.logger.warning("CIType net port is not found")
return
for port in ports:
try:
port_id = CIManager.add(port_type.id, is_auto_discovery=True, _is_admin=True, **port)
CIRelationManager.add(ci_id, port_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except Exception as e:
current_app.logger.warning("add_net_device_ports failed: {}".format(e))
@celery.task(name="cmdb.dcim_calc_u_free_count", queue=CMDB_QUEUE)
@reconnect_db
def dcim_calc_u_free_count():
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.dcim.rack import RackManager
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
if not has_request_context():
current_app.test_request_context().push()
login_user(UserCache.get('worker'))
try:
rack_m = RackManager()
except Exception:
return
racks = CI.get_by(type_id=rack_m.type_id, to_dict=False)
for rack in racks:
payload = {RackBuiltinAttributes.FREE_U_COUNT: rack_m.calc_u_free_count(rack.id)}
CIManager().update(rack.id, **payload)

View File

@@ -30,7 +30,6 @@ from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search as ci_search
from api.lib.decorator import args_required
from api.lib.decorator import args_validate
from api.lib.exception import AbortException
from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.utils import AESCrypto
from api.lib.utils import get_page
@@ -297,10 +296,7 @@ class AutoDiscoveryRuleSyncView(APIView):
rules, last_update_at1 = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
try:
subnet_scan_rules, last_update_at2 = SubnetManager().scan_rules(oneagent_id, last_update_at)
except AbortException:
subnet_scan_rules, last_update_at2 = [], ""
subnet_scan_rules, last_update_at2 = SubnetManager().scan_rules(oneagent_id, last_update_at)
return self.jsonify(rules=rules,
subnet_scan_rules=subnet_scan_rules,

View File

@@ -2,14 +2,14 @@
import json
from io import BytesIO
from flask import abort
from flask import current_app
from flask import request
from io import BytesIO
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CITriggerManager
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeGroupManager
@@ -64,13 +64,9 @@ class CITypeView(APIView):
ci_type['unique_name'] = ci_type['unique_id'] and AttributeCache.get(ci_type['unique_id']).name
ci_types.append(ci_type)
elif type_name is not None:
ci_type = CITypeCache.get(type_name)
if ci_type is not None:
ci_type = ci_type.to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
ci_types = [ci_type]
else:
ci_types = []
ci_type = CITypeCache.get(type_name).to_dict()
ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])
ci_types = [ci_type]
else:
ci_types = CITypeManager().get_ci_types(q)
count = len(ci_types)
@@ -501,16 +497,6 @@ class CITypeTriggerView(APIView):
return self.jsonify(code=200)
class CITypeTriggerTestView(APIView):
url_prefix = ("/ci_types/<int:type_id>/triggers/<int:_id>/test_notify",)
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def post(self, type_id, _id):
CITriggerManager().trigger_notify_test(type_id, _id)
return self.jsonify(code=200)
class CITypeGrantView(APIView):
url_prefix = "/ci_types/<int:type_id>/roles/<int:rid>/grant"

View File

@@ -2,14 +2,12 @@
from flask import request
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
from api.lib.cmdb.dcim.rack import RackManager
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required
from api.resource import APIView
from api.tasks.cmdb import dcim_calc_u_free_count
app_cli = CMDBApp()
@@ -89,14 +87,3 @@ class RackDeviceMigrateView(APIView):
device_id=device_id,
to_u_start=to_u_start,
to_rack_id=to_rack_id)
class RackCalcUFreeCountView(APIView):
url_prefix = ("/dcim/rack/calc_u_free_count",)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
app_cli.op.read, app_cli.admin_name)
def post(self):
dcim_calc_u_free_count.apply_async(queue=CMDB_QUEUE)
return self.jsonify(code=200)

View File

@@ -211,59 +211,3 @@ class PreferenceCITypeOrderView(APIView):
PreferenceManager.upsert_ci_type_order(type_ids, is_tree)
return self.jsonify(type_ids=type_ids, is_tree=is_tree)
class PreferenceAutoSubscriptionView(APIView):
url_prefix = "/preference/auto_subscription"
def get(self):
config = PreferenceManager.get_auto_subscription_config()
return self.jsonify(config or {})
@args_required("base_strategy")
def put(self):
base_strategy = request.values.get("base_strategy")
group_ids = request.values.get("group_ids")
type_ids = request.values.get("type_ids")
enabled = request.values.get("enabled", 1) in current_app.config.get('BOOL_TRUE')
description = request.values.get("description")
if base_strategy not in ['all', 'none']:
return abort(400, "base_strategy must be 'all' or 'none'")
if group_ids:
try:
group_ids = [int(x) for x in group_ids.split(',') if x.strip()]
except ValueError:
return abort(400, "Invalid group_ids format")
if type_ids:
try:
type_ids = [int(x) for x in type_ids.split(',') if x.strip()]
except ValueError:
return abort(400, "Invalid type_ids format")
result = PreferenceManager.create_or_update_auto_subscription_config(
base_strategy=base_strategy,
group_ids=group_ids,
type_ids=type_ids,
enabled=enabled,
description=description
)
return self.jsonify(result.to_dict())
def delete(self):
PreferenceManager.delete_auto_subscription_config()
return self.jsonify(message="Auto subscription config deleted")
class PreferenceAutoSubscriptionToggleView(APIView):
url_prefix = "/preference/auto_subscription/toggle"
@args_required("enabled")
def patch(self):
enabled = request.values.get("enabled") in current_app.config.get('BOOL_TRUE')
result = PreferenceManager.toggle_auto_subscription_config(enabled)
return self.jsonify(result.to_dict())

View File

@@ -131,7 +131,7 @@ class EmployeeChangePasswordWithACLID(APIView):
if not password:
abort(400, ErrFormat.password_is_required)
EmployeeCRUD.change_password_by_uid(_uid, password)
data = EmployeeCRUD.change_password_by_uid(_uid, password)
return self.jsonify(200)

View File

@@ -6,7 +6,7 @@ import magic
from api.lib.common_setting.const import MIMEExtMap
from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.upload_file import generate_new_file_name, CommonFileCRUD
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD
from api.resource import APIView
prefix = '/file'

View File

@@ -1,37 +0,0 @@
import os
from api.resource import APIView
from api.lib.perm.auth import auth_abandoned
prefix = "/system"
class SystemLanguageView(APIView):
url_prefix = (f"{prefix}/language",)
method_decorators = []
@auth_abandoned
def get(self):
"""Get system default language
Read from environment variable SYSTEM_DEFAULT_LANGUAGE, default to Chinese if not set
"""
default_language = os.environ.get("SYSTEM_DEFAULT_LANGUAGE", "")
return self.jsonify(
{
"language": default_language,
"language_name": self._get_language_name(default_language),
}
)
def _get_language_name(self, language_code):
"""Return language name based on language code"""
language_mapping = {
"zh-CN": "中文(简体)",
"zh-TW": "中文(繁体)",
"en-US": "English",
"ja-JP": "日本語",
"ko-KR": "한국어",
}
return language_mapping.get(language_code, "")

View File

@@ -58,4 +58,3 @@ python-magic==0.4.27
jsonpath==0.82.2
networkx>=3.1
ipaddress>=1.0.23
ruff==0.8.3

View File

@@ -5,6 +5,14 @@ if (IS_PROD) {
plugins.push('transform-remove-console')
}
// lazy load ant-design-vue
// if your use import on Demand, Use this code
// plugins.push(['import', {
// 'libraryName': 'ant-design-vue',
// 'libraryDirectory': 'es',
// 'style': true // `style: true` 会加载 less 文件
// }])
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',

View File

@@ -2,7 +2,7 @@ const ThemeColorReplacer = require('webpack-theme-color-replacer')
const generate = require('@ant-design/colors/lib/generate').default
const getAntdSerials = (color) => {
// Lighten (similar to less's tint)
// 淡化(即lesstint
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
})
@@ -13,8 +13,8 @@ const getAntdSerials = (color) => {
const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#2f54eb'), // primary color series
// change style selectors to solve style override issues
matchColors: getAntdSerials('#2f54eb'), // 主色系列
// 改变样式选择器,解决样式覆盖问题
changeSelector (selector) {
switch (selector) {
case '.ant-calendar-today .ant-calendar-date':

View File

@@ -54,228 +54,6 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xea28;</span>
<div class="name">auto</div>
<div class="code-name">&amp;#xea28;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea26;</span>
<div class="name">oneterm-http</div>
<div class="code-name">&amp;#xea26;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea27;</span>
<div class="name">oneterm-https</div>
<div class="code-name">&amp;#xea27;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea25;</span>
<div class="name">access_period</div>
<div class="code-name">&amp;#xea25;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea24;</span>
<div class="name">authorization</div>
<div class="code-name">&amp;#xea24;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea23;</span>
<div class="name">onterm-symbolic_link</div>
<div class="code-name">&amp;#xea23;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea20;</span>
<div class="name">oneterm-batch_execution</div>
<div class="code-name">&amp;#xea20;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea21;</span>
<div class="name">oneterm-file_log-selected</div>
<div class="code-name">&amp;#xea21;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea22;</span>
<div class="name">oneterm-file_log</div>
<div class="code-name">&amp;#xea22;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1f;</span>
<div class="name">file</div>
<div class="code-name">&amp;#xea1f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1e;</span>
<div class="name">folder</div>
<div class="code-name">&amp;#xea1e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1b;</span>
<div class="name">mongoDB (1)</div>
<div class="code-name">&amp;#xea1b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1c;</span>
<div class="name">postgreSQL (1)</div>
<div class="code-name">&amp;#xea1c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1d;</span>
<div class="name">telnet (1)</div>
<div class="code-name">&amp;#xea1d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea17;</span>
<div class="name">command_interception (1)</div>
<div class="code-name">&amp;#xea17;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea18;</span>
<div class="name">quick_commands</div>
<div class="code-name">&amp;#xea18;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea19;</span>
<div class="name">terminal_settings</div>
<div class="code-name">&amp;#xea19;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea1a;</span>
<div class="name">basic_settings</div>
<div class="code-name">&amp;#xea1a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea16;</span>
<div class="name">asset_management</div>
<div class="code-name">&amp;#xea16;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea15;</span>
<div class="name">ai-seek</div>
<div class="code-name">&amp;#xea15;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea13;</span>
<div class="name">ai-hate1</div>
<div class="code-name">&amp;#xea13;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea14;</span>
<div class="name">ai-like1</div>
<div class="code-name">&amp;#xea14;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea11;</span>
<div class="name">ai-like2</div>
<div class="code-name">&amp;#xea11;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea12;</span>
<div class="name">ai-hate2</div>
<div class="code-name">&amp;#xea12;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea10;</span>
<div class="name">ai-top_up</div>
<div class="code-name">&amp;#xea10;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0f;</span>
<div class="name">ai-top_down</div>
<div class="code-name">&amp;#xea0f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0d;</span>
<div class="name">autoflow-script</div>
<div class="code-name">&amp;#xea0d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0e;</span>
<div class="name">autoflow-dag</div>
<div class="code-name">&amp;#xea0e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0c;</span>
<div class="name">itsm-default_line</div>
<div class="code-name">&amp;#xea0c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0b;</span>
<div class="name">veops-servicetree</div>
<div class="code-name">&amp;#xea0b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea0a;</span>
<div class="name">veops-switch (1)</div>
<div class="code-name">&amp;#xea0a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea09;</span>
<div class="name">veops-label</div>
<div class="code-name">&amp;#xea09;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea08;</span>
<div class="name">top_acl</div>
<div class="code-name">&amp;#xea08;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea06;</span>
<div class="name">top_ticket</div>
<div class="code-name">&amp;#xea06;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea07;</span>
<div class="name">top_agent</div>
<div class="code-name">&amp;#xea07;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea05;</span>
<div class="name">itsm-table_download</div>
<div class="code-name">&amp;#xea05;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea04;</span>
<div class="name">itsm-image_download</div>
<div class="code-name">&amp;#xea04;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea02;</span>
<div class="name">veops-rear</div>
@@ -6384,9 +6162,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1755240492206') format('woff2'),
url('iconfont.woff?t=1755240492206') format('woff'),
url('iconfont.ttf?t=1755240492206') format('truetype');
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
url('iconfont.woff?t=1732673294759') format('woff'),
url('iconfont.ttf?t=1732673294759') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -6412,339 +6190,6 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont auto"></span>
<div class="name">
auto
</div>
<div class="code-name">.auto
</div>
</li>
<li class="dib">
<span class="icon iconfont oneterm-http"></span>
<div class="name">
oneterm-http
</div>
<div class="code-name">.oneterm-http
</div>
</li>
<li class="dib">
<span class="icon iconfont oneterm-https"></span>
<div class="name">
oneterm-https
</div>
<div class="code-name">.oneterm-https
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-access_period"></span>
<div class="name">
access_period
</div>
<div class="code-name">.ops-oneterm-access_period
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-authorization"></span>
<div class="name">
authorization
</div>
<div class="code-name">.ops-oneterm-authorization
</div>
</li>
<li class="dib">
<span class="icon iconfont onterm-symbolic_link"></span>
<div class="name">
onterm-symbolic_link
</div>
<div class="code-name">.onterm-symbolic_link
</div>
</li>
<li class="dib">
<span class="icon iconfont oneterm-batch_execution"></span>
<div class="name">
oneterm-batch_execution
</div>
<div class="code-name">.oneterm-batch_execution
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-file_log-selected"></span>
<div class="name">
oneterm-file_log-selected
</div>
<div class="code-name">.ops-oneterm-file_log-selected
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-file_log"></span>
<div class="name">
oneterm-file_log
</div>
<div class="code-name">.ops-oneterm-file_log
</div>
</li>
<li class="dib">
<span class="icon iconfont file"></span>
<div class="name">
file
</div>
<div class="code-name">.file
</div>
</li>
<li class="dib">
<span class="icon iconfont folder1"></span>
<div class="name">
folder
</div>
<div class="code-name">.folder1
</div>
</li>
<li class="dib">
<span class="icon iconfont a-mongoDB1"></span>
<div class="name">
mongoDB (1)
</div>
<div class="code-name">.a-mongoDB1
</div>
</li>
<li class="dib">
<span class="icon iconfont a-postgreSQL1"></span>
<div class="name">
postgreSQL (1)
</div>
<div class="code-name">.a-postgreSQL1
</div>
</li>
<li class="dib">
<span class="icon iconfont a-telnet1"></span>
<div class="name">
telnet (1)
</div>
<div class="code-name">.a-telnet1
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-command_interception1"></span>
<div class="name">
command_interception (1)
</div>
<div class="code-name">.ops-oneterm-command_interception1
</div>
</li>
<li class="dib">
<span class="icon iconfont quick_commands"></span>
<div class="name">
quick_commands
</div>
<div class="code-name">.quick_commands
</div>
</li>
<li class="dib">
<span class="icon iconfont terminal_settings"></span>
<div class="name">
terminal_settings
</div>
<div class="code-name">.terminal_settings
</div>
</li>
<li class="dib">
<span class="icon iconfont basic_settings"></span>
<div class="name">
basic_settings
</div>
<div class="code-name">.basic_settings
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-oneterm-asset-management"></span>
<div class="name">
asset_management
</div>
<div class="code-name">.ops-oneterm-asset-management
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-seek"></span>
<div class="name">
ai-seek
</div>
<div class="code-name">.ai-seek
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-hate1"></span>
<div class="name">
ai-hate1
</div>
<div class="code-name">.ai-hate1
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-like1"></span>
<div class="name">
ai-like1
</div>
<div class="code-name">.ai-like1
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-like2"></span>
<div class="name">
ai-like2
</div>
<div class="code-name">.ai-like2
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-hate2"></span>
<div class="name">
ai-hate2
</div>
<div class="code-name">.ai-hate2
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-top_up"></span>
<div class="name">
ai-top_up
</div>
<div class="code-name">.ai-top_up
</div>
</li>
<li class="dib">
<span class="icon iconfont ai-top_down"></span>
<div class="name">
ai-top_down
</div>
<div class="code-name">.ai-top_down
</div>
</li>
<li class="dib">
<span class="icon iconfont autoflow-script"></span>
<div class="name">
autoflow-script
</div>
<div class="code-name">.autoflow-script
</div>
</li>
<li class="dib">
<span class="icon iconfont autoflow-dag"></span>
<div class="name">
autoflow-dag
</div>
<div class="code-name">.autoflow-dag
</div>
</li>
<li class="dib">
<span class="icon iconfont itsm-default_line"></span>
<div class="name">
itsm-default_line
</div>
<div class="code-name">.itsm-default_line
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-servicetree"></span>
<div class="name">
veops-servicetree
</div>
<div class="code-name">.veops-servicetree
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-switch1"></span>
<div class="name">
veops-switch (1)
</div>
<div class="code-name">.veops-switch1
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-label"></span>
<div class="name">
veops-label
</div>
<div class="code-name">.veops-label
</div>
</li>
<li class="dib">
<span class="icon iconfont top_acl"></span>
<div class="name">
top_acl
</div>
<div class="code-name">.top_acl
</div>
</li>
<li class="dib">
<span class="icon iconfont top_ticket"></span>
<div class="name">
top_ticket
</div>
<div class="code-name">.top_ticket
</div>
</li>
<li class="dib">
<span class="icon iconfont top_agent"></span>
<div class="name">
top_agent
</div>
<div class="code-name">.top_agent
</div>
</li>
<li class="dib">
<span class="icon iconfont itsm-table_download"></span>
<div class="name">
itsm-table_download
</div>
<div class="code-name">.itsm-table_download
</div>
</li>
<li class="dib">
<span class="icon iconfont itsm-image_download"></span>
<div class="name">
itsm-image_download
</div>
<div class="code-name">.itsm-image_download
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-rear"></span>
<div class="name">
@@ -15907,302 +15352,6 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#auto"></use>
</svg>
<div class="name">auto</div>
<div class="code-name">#auto</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#oneterm-http"></use>
</svg>
<div class="name">oneterm-http</div>
<div class="code-name">#oneterm-http</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#oneterm-https"></use>
</svg>
<div class="name">oneterm-https</div>
<div class="code-name">#oneterm-https</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-access_period"></use>
</svg>
<div class="name">access_period</div>
<div class="code-name">#ops-oneterm-access_period</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-authorization"></use>
</svg>
<div class="name">authorization</div>
<div class="code-name">#ops-oneterm-authorization</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#onterm-symbolic_link"></use>
</svg>
<div class="name">onterm-symbolic_link</div>
<div class="code-name">#onterm-symbolic_link</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#oneterm-batch_execution"></use>
</svg>
<div class="name">oneterm-batch_execution</div>
<div class="code-name">#oneterm-batch_execution</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-file_log-selected"></use>
</svg>
<div class="name">oneterm-file_log-selected</div>
<div class="code-name">#ops-oneterm-file_log-selected</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-file_log"></use>
</svg>
<div class="name">oneterm-file_log</div>
<div class="code-name">#ops-oneterm-file_log</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#file"></use>
</svg>
<div class="name">file</div>
<div class="code-name">#file</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#folder1"></use>
</svg>
<div class="name">folder</div>
<div class="code-name">#folder1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-mongoDB1"></use>
</svg>
<div class="name">mongoDB (1)</div>
<div class="code-name">#a-mongoDB1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-postgreSQL1"></use>
</svg>
<div class="name">postgreSQL (1)</div>
<div class="code-name">#a-postgreSQL1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-telnet1"></use>
</svg>
<div class="name">telnet (1)</div>
<div class="code-name">#a-telnet1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-command_interception1"></use>
</svg>
<div class="name">command_interception (1)</div>
<div class="code-name">#ops-oneterm-command_interception1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#quick_commands"></use>
</svg>
<div class="name">quick_commands</div>
<div class="code-name">#quick_commands</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#terminal_settings"></use>
</svg>
<div class="name">terminal_settings</div>
<div class="code-name">#terminal_settings</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#basic_settings"></use>
</svg>
<div class="name">basic_settings</div>
<div class="code-name">#basic_settings</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-oneterm-asset-management"></use>
</svg>
<div class="name">asset_management</div>
<div class="code-name">#ops-oneterm-asset-management</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-seek"></use>
</svg>
<div class="name">ai-seek</div>
<div class="code-name">#ai-seek</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-hate1"></use>
</svg>
<div class="name">ai-hate1</div>
<div class="code-name">#ai-hate1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-like1"></use>
</svg>
<div class="name">ai-like1</div>
<div class="code-name">#ai-like1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-like2"></use>
</svg>
<div class="name">ai-like2</div>
<div class="code-name">#ai-like2</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-hate2"></use>
</svg>
<div class="name">ai-hate2</div>
<div class="code-name">#ai-hate2</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-top_up"></use>
</svg>
<div class="name">ai-top_up</div>
<div class="code-name">#ai-top_up</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ai-top_down"></use>
</svg>
<div class="name">ai-top_down</div>
<div class="code-name">#ai-top_down</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#autoflow-script"></use>
</svg>
<div class="name">autoflow-script</div>
<div class="code-name">#autoflow-script</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#autoflow-dag"></use>
</svg>
<div class="name">autoflow-dag</div>
<div class="code-name">#autoflow-dag</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#itsm-default_line"></use>
</svg>
<div class="name">itsm-default_line</div>
<div class="code-name">#itsm-default_line</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-servicetree"></use>
</svg>
<div class="name">veops-servicetree</div>
<div class="code-name">#veops-servicetree</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-switch1"></use>
</svg>
<div class="name">veops-switch (1)</div>
<div class="code-name">#veops-switch1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-label"></use>
</svg>
<div class="name">veops-label</div>
<div class="code-name">#veops-label</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#top_acl"></use>
</svg>
<div class="name">top_acl</div>
<div class="code-name">#top_acl</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#top_ticket"></use>
</svg>
<div class="name">top_ticket</div>
<div class="code-name">#top_ticket</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#top_agent"></use>
</svg>
<div class="name">top_agent</div>
<div class="code-name">#top_agent</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#itsm-table_download"></use>
</svg>
<div class="name">itsm-table_download</div>
<div class="code-name">#itsm-table_download</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#itsm-image_download"></use>
</svg>
<div class="name">itsm-image_download</div>
<div class="code-name">#itsm-image_download</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-rear"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1755240492206') format('woff2'),
url('iconfont.woff?t=1755240492206') format('woff'),
url('iconfont.ttf?t=1755240492206') format('truetype');
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
url('iconfont.woff?t=1732673294759') format('woff'),
url('iconfont.ttf?t=1732673294759') format('truetype');
}
.iconfont {
@@ -13,154 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.auto:before {
content: "\ea28";
}
.oneterm-http:before {
content: "\ea26";
}
.oneterm-https:before {
content: "\ea27";
}
.ops-oneterm-access_period:before {
content: "\ea25";
}
.ops-oneterm-authorization:before {
content: "\ea24";
}
.onterm-symbolic_link:before {
content: "\ea23";
}
.oneterm-batch_execution:before {
content: "\ea20";
}
.ops-oneterm-file_log-selected:before {
content: "\ea21";
}
.ops-oneterm-file_log:before {
content: "\ea22";
}
.file:before {
content: "\ea1f";
}
.folder1:before {
content: "\ea1e";
}
.a-mongoDB1:before {
content: "\ea1b";
}
.a-postgreSQL1:before {
content: "\ea1c";
}
.a-telnet1:before {
content: "\ea1d";
}
.ops-oneterm-command_interception1:before {
content: "\ea17";
}
.quick_commands:before {
content: "\ea18";
}
.terminal_settings:before {
content: "\ea19";
}
.basic_settings:before {
content: "\ea1a";
}
.ops-oneterm-asset-management:before {
content: "\ea16";
}
.ai-seek:before {
content: "\ea15";
}
.ai-hate1:before {
content: "\ea13";
}
.ai-like1:before {
content: "\ea14";
}
.ai-like2:before {
content: "\ea11";
}
.ai-hate2:before {
content: "\ea12";
}
.ai-top_up:before {
content: "\ea10";
}
.ai-top_down:before {
content: "\ea0f";
}
.autoflow-script:before {
content: "\ea0d";
}
.autoflow-dag:before {
content: "\ea0e";
}
.itsm-default_line:before {
content: "\ea0c";
}
.veops-servicetree:before {
content: "\ea0b";
}
.veops-switch1:before {
content: "\ea0a";
}
.veops-label:before {
content: "\ea09";
}
.top_acl:before {
content: "\ea08";
}
.top_ticket:before {
content: "\ea06";
}
.top_agent:before {
content: "\ea07";
}
.itsm-table_download:before {
content: "\ea05";
}
.itsm-image_download:before {
content: "\ea04";
}
.veops-rear:before {
content: "\ea02";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,265 +5,6 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "45254419",
"name": "auto",
"font_class": "auto",
"unicode": "ea28",
"unicode_decimal": 59944
},
{
"icon_id": "45069619",
"name": "oneterm-http",
"font_class": "oneterm-http",
"unicode": "ea26",
"unicode_decimal": 59942
},
{
"icon_id": "45069616",
"name": "oneterm-https",
"font_class": "oneterm-https",
"unicode": "ea27",
"unicode_decimal": 59943
},
{
"icon_id": "44939355",
"name": "access_period",
"font_class": "ops-oneterm-access_period",
"unicode": "ea25",
"unicode_decimal": 59941
},
{
"icon_id": "44939354",
"name": "authorization",
"font_class": "ops-oneterm-authorization",
"unicode": "ea24",
"unicode_decimal": 59940
},
{
"icon_id": "44501032",
"name": "onterm-symbolic_link",
"font_class": "onterm-symbolic_link",
"unicode": "ea23",
"unicode_decimal": 59939
},
{
"icon_id": "44497221",
"name": "oneterm-batch_execution",
"font_class": "oneterm-batch_execution",
"unicode": "ea20",
"unicode_decimal": 59936
},
{
"icon_id": "44497220",
"name": "oneterm-file_log-selected",
"font_class": "ops-oneterm-file_log-selected",
"unicode": "ea21",
"unicode_decimal": 59937
},
{
"icon_id": "44497219",
"name": "oneterm-file_log",
"font_class": "ops-oneterm-file_log",
"unicode": "ea22",
"unicode_decimal": 59938
},
{
"icon_id": "44455092",
"name": "file",
"font_class": "file",
"unicode": "ea1f",
"unicode_decimal": 59935
},
{
"icon_id": "44455100",
"name": "folder",
"font_class": "folder1",
"unicode": "ea1e",
"unicode_decimal": 59934
},
{
"icon_id": "44315758",
"name": "mongoDB (1)",
"font_class": "a-mongoDB1",
"unicode": "ea1b",
"unicode_decimal": 59931
},
{
"icon_id": "44315757",
"name": "postgreSQL (1)",
"font_class": "a-postgreSQL1",
"unicode": "ea1c",
"unicode_decimal": 59932
},
{
"icon_id": "44315755",
"name": "telnet (1)",
"font_class": "a-telnet1",
"unicode": "ea1d",
"unicode_decimal": 59933
},
{
"icon_id": "44276353",
"name": "command_interception (1)",
"font_class": "ops-oneterm-command_interception1",
"unicode": "ea17",
"unicode_decimal": 59927
},
{
"icon_id": "44276352",
"name": "quick_commands",
"font_class": "quick_commands",
"unicode": "ea18",
"unicode_decimal": 59928
},
{
"icon_id": "44276351",
"name": "terminal_settings",
"font_class": "terminal_settings",
"unicode": "ea19",
"unicode_decimal": 59929
},
{
"icon_id": "44276350",
"name": "basic_settings",
"font_class": "basic_settings",
"unicode": "ea1a",
"unicode_decimal": 59930
},
{
"icon_id": "44276278",
"name": "asset_management",
"font_class": "ops-oneterm-asset-management",
"unicode": "ea16",
"unicode_decimal": 59926
},
{
"icon_id": "43267802",
"name": "ai-seek",
"font_class": "ai-seek",
"unicode": "ea15",
"unicode_decimal": 59925
},
{
"icon_id": "43213714",
"name": "ai-hate1",
"font_class": "ai-hate1",
"unicode": "ea13",
"unicode_decimal": 59923
},
{
"icon_id": "43213712",
"name": "ai-like1",
"font_class": "ai-like1",
"unicode": "ea14",
"unicode_decimal": 59924
},
{
"icon_id": "43213717",
"name": "ai-like2",
"font_class": "ai-like2",
"unicode": "ea11",
"unicode_decimal": 59921
},
{
"icon_id": "43213716",
"name": "ai-hate2",
"font_class": "ai-hate2",
"unicode": "ea12",
"unicode_decimal": 59922
},
{
"icon_id": "43139007",
"name": "ai-top_up",
"font_class": "ai-top_up",
"unicode": "ea10",
"unicode_decimal": 59920
},
{
"icon_id": "43139017",
"name": "ai-top_down",
"font_class": "ai-top_down",
"unicode": "ea0f",
"unicode_decimal": 59919
},
{
"icon_id": "43029539",
"name": "autoflow-script",
"font_class": "autoflow-script",
"unicode": "ea0d",
"unicode_decimal": 59917
},
{
"icon_id": "43029538",
"name": "autoflow-dag",
"font_class": "autoflow-dag",
"unicode": "ea0e",
"unicode_decimal": 59918
},
{
"icon_id": "42960865",
"name": "itsm-default_line",
"font_class": "itsm-default_line",
"unicode": "ea0c",
"unicode_decimal": 59916
},
{
"icon_id": "42930714",
"name": "veops-servicetree",
"font_class": "veops-servicetree",
"unicode": "ea0b",
"unicode_decimal": 59915
},
{
"icon_id": "42921461",
"name": "veops-switch (1)",
"font_class": "veops-switch1",
"unicode": "ea0a",
"unicode_decimal": 59914
},
{
"icon_id": "42857659",
"name": "veops-label",
"font_class": "veops-label",
"unicode": "ea09",
"unicode_decimal": 59913
},
{
"icon_id": "42790685",
"name": "top_acl",
"font_class": "top_acl",
"unicode": "ea08",
"unicode_decimal": 59912
},
{
"icon_id": "42790687",
"name": "top_ticket",
"font_class": "top_ticket",
"unicode": "ea06",
"unicode_decimal": 59910
},
{
"icon_id": "42790686",
"name": "top_agent",
"font_class": "top_agent",
"unicode": "ea07",
"unicode_decimal": 59911
},
{
"icon_id": "42732510",
"name": "itsm-table_download",
"font_class": "itsm-table_download",
"unicode": "ea05",
"unicode_decimal": 59909
},
{
"icon_id": "42732515",
"name": "itsm-image_download",
"font_class": "itsm-image_download",
"unicode": "ea04",
"unicode_decimal": 59908
},
{
"icon_id": "42510712",
"name": "veops-rear",

Binary file not shown.

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
@@ -11,19 +12,17 @@
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<script>
const userAgent = navigator.userAgent
<script>
  const userAgent = navigator.userAgent
const isEdge = userAgent.indexOf("Edge") > -1
const isChrome = userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1 && !isEdge
const lang = (navigator.language || navigator.userLanguage || '').toLowerCase()
if (!isChrome) {
if (lang.startsWith('zh')) {
alert("推荐使用Chrome浏览器其他环境下未做严格测试");
} else {
alert("It is recommended to use Chrome browser. Other environments are not strictly tested!");
}
  const isChrome = userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1 && !isEdge
  if (!isChrome) {
alert("推荐使用Chrome浏览器 其他环境下未做严格测试!")
}
</script>
</head>
<body>
<noscript>
@@ -31,10 +30,10 @@
</noscript>
<div id="app">
<div id="loading-mask">
<div class="loading-wrapper">
<span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span>
<div class="loading-wrapper">
<span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
</div>
</div>
</div>
<!-- require cdn assets js -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>

View File

@@ -12,7 +12,6 @@ import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import enUS from 'ant-design-vue/lib/locale-provider/en_US'
import { AppDeviceEnquire } from '@/utils/mixin'
import { debounce } from './utils/util'
import { getSystemLanguage } from '@/api/system.js'
import { h } from 'snabbdom'
import { DomEditor, Boot } from '@wangeditor/editor'
@@ -46,7 +45,8 @@ export default {
},
},
created() {
this.initLanguage()
this.SET_LOCALE(localStorage.getItem('ops_locale') || 'zh')
this.$i18n.locale = localStorage.getItem('ops_locale') || 'zh'
this.timer = setInterval(() => {
this.setTime(new Date().getTime())
}, 1000)
@@ -60,7 +60,133 @@ export default {
})
)
this.handleEditor()
// 注册富文本自定义元素
// const resume = {
// type: 'attachment',
// attachmentLabel: '',
// attachmentValue: '',
// children: [{ text: '' }], // void 元素必须有一个 children 其中只有一个空字符串重要
// }
function withAttachment(editor) {
// JS 语法
const { isInline, isVoid } = editor
const newEditor = editor
newEditor.isInline = (elem) => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // 针对 type: attachment 设置为 inline
return isInline(elem)
}
newEditor.isVoid = (elem) => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // 针对 type: attachment 设置为 void
return isVoid(elem)
}
return newEditor // 返回 newEditor 重要
}
Boot.registerPlugin(withAttachment)
/**
* 渲染附件元素到编辑器
* @param elem 附件元素即上文的 myResume
* @param children 元素子节点void 元素可忽略
* @param editor 编辑器实例
* @returns vnode 节点通过 snabbdom.js h 函数生成
*/
function renderAttachment(elem, children, editor) {
// JS 语法
// 获取附件的数据参考上文 myResume 数据结构
const { attachmentLabel = '', attachmentValue = '' } = elem
// 附件元素 vnode
const attachVnode = h(
// HTML tag
'span',
// HTML 属性样式事件
{
props: { contentEditable: false }, // HTML 属性驼峰式写法
style: {
display: 'inline-block',
margin: '0 3px',
padding: '0 3px',
backgroundColor: '#e6f7ff',
border: '1px solid #91d5ff',
borderRadius: '2px',
color: '#1890ff',
}, // style 驼峰式写法
on: {
click() {
console.log('clicked', attachmentValue)
} /* 其他... */,
},
},
// 子节点
[attachmentLabel]
)
return attachVnode
}
const renderElemConf = {
type: 'attachment', // 新元素 type 重要
renderElem: renderAttachment,
}
Boot.registerRenderElem(renderElemConf)
/**
* 生成附件元素的 HTML
* @param elem 附件元素即上文的 myResume
* @param childrenHtml 子节点的 HTML 代码void 元素可忽略
* @returns 附件元素的 HTML 字符串
*/
function attachmentToHtml(elem, childrenHtml) {
// JS 语法
// 获取附件元素的数据
const { attachmentValue = '', attachmentLabel = '' } = elem
// 生成 HTML 代码
const html = `<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline data-attachmentValue="${attachmentValue}" data-attachmentLabel="${attachmentLabel}">${attachmentLabel}</span>`
return html
}
const elemToHtmlConf = {
type: 'attachment', // 新元素的 type 重要
elemToHtml: attachmentToHtml,
}
Boot.registerElemToHtml(elemToHtmlConf)
/**
* 解析 HTML 字符串生成附件元素
* @param domElem HTML 对应的 DOM Element
* @param children 子节点
* @param editor editor 实例
* @returns 附件元素如上文的 myResume
*/
function parseAttachmentHtml(domElem, children, editor) {
// JS 语法
// DOM element 中获取附件的信息
const attachmentValue = domElem.getAttribute('data-attachmentValue') || ''
const attachmentLabel = domElem.getAttribute('data-attachmentLabel') || ''
// 生成附件元素按照此前约定的数据结构
const myResume = {
type: 'attachment',
attachmentValue,
attachmentLabel,
children: [{ text: '' }], // void node 必须有 children 其中有一个空字符串重要
}
return myResume
}
const parseHtmlConf = {
selector: 'span[data-w-e-type="attachment"]', // CSS 选择器匹配特定的 HTML 标签
parseElemHtml: parseAttachmentHtml,
}
Boot.registerParseElemHtml(parseHtmlConf)
},
beforeDestroy() {
clearInterval(this.timer)
@@ -74,141 +200,6 @@ export default {
this.alive = true
})
},
async initLanguage() {
let saveLocale = localStorage.getItem('ops_locale')
if (!saveLocale) {
let requestLanguage = ''
try {
const languageRes = await getSystemLanguage()
requestLanguage = languageRes?.language || ''
} catch (e) {
console.error('getSystemLanguage error:', e)
}
// request language variable || user local system language
const userLanguage = requestLanguage || navigator.language || navigator.userLanguage
if (userLanguage.includes('zh')) {
saveLocale = 'zh'
} else {
saveLocale = 'en'
}
}
this.SET_LOCALE(saveLocale)
this.$i18n.locale = saveLocale
},
handleEditor() {
// register custom rich text element: attachment
function withAttachment(editor) {
const { isInline, isVoid } = editor
const newEditor = editor
newEditor.isInline = (elem) => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // For type: attachment, set to inline
return isInline(elem)
}
newEditor.isVoid = (elem) => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // For type: attachment set to void
return isVoid(elem)
}
return newEditor // Must return, important!!!
}
Boot.registerPlugin(withAttachment)
/**
* Render "attachment" element in editor
* @param elem Attachment element
* @param children Child nodes (ignored for void elements)
* @param editor Editor instance
* @returns vnode (generated by snabbdom's h function)
*/
function renderAttachment(elem, children, editor) {
const { attachmentLabel = '', attachmentValue = '' } = elem
const attachVnode = h(
// HTML tag
'span',
// HTML attr, style, event
{
props: { contentEditable: false },
style: {
display: 'inline-block',
margin: '0 3px',
padding: '0 3px',
backgroundColor: '#e6f7ff',
border: '1px solid #91d5ff',
borderRadius: '2px',
color: '#1890ff',
},
on: {
click() {
console.log('clicked', attachmentValue)
}
},
},
// child node
[attachmentLabel]
)
return attachVnode
}
const renderElemConf = {
type: 'attachment',
renderElem: renderAttachment,
}
Boot.registerRenderElem(renderElemConf)
/**
* Generate HTML for "attachment" element
* @param elem Attachment element
* @param childrenHtml Child HTML (ignored for void elements)
* @returns HTML string
*/
function attachmentToHtml(elem, childrenHtml) {
// Getting data for attached elements
const { attachmentValue = '', attachmentLabel = '' } = elem
// generate HTML
const html = `<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline data-attachmentValue="${attachmentValue}" data-attachmentLabel="${attachmentLabel}">${attachmentLabel}</span>`
return html
}
const elemToHtmlConf = {
type: 'attachment',
elemToHtml: attachmentToHtml,
}
Boot.registerElemToHtml(elemToHtmlConf)
/**
* Parse HTML to generate "attachment" element
* @param domElem DOM element
* @param children Children
* @param editor Editor instance
* @returns Attachment element
*/
function parseAttachmentHtml(domElem, children, editor) {
// Getting attachment information from DOM element
const attachmentValue = domElem.getAttribute('data-attachmentValue') || ''
const attachmentLabel = domElem.getAttribute('data-attachmentLabel') || ''
const myResume = {
type: 'attachment',
attachmentValue,
attachmentLabel,
children: [{ text: '' }], // The void node must have children with an empty string in it, important!!!!
}
return myResume
}
const parseHtmlConf = {
selector: 'span[data-w-e-type="attachment"]', // CSS selector to match specific HTML tags
parseElemHtml: parseAttachmentHtml,
}
Boot.registerParseElemHtml(parseHtmlConf)
}
},
}
</script>

View File

@@ -1,8 +0,0 @@
import { axios } from '@/utils/request'
export function getSystemLanguage() {
return axios({
url: '/common-setting/v1/system/language',
method: 'get',
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -1,40 +1,41 @@
import i18n from '@/lang'
export const ruleTypeList = () => {
return [
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
]
}
export const expList = () => {
return [
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') },
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
]
}
export const advancedExpList = () => {
return [
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
]
}
export const compareTypeList = [
{ value: '1', label: '>' },
{ value: '2', label: '>=' },
{ value: '3', label: '<' },
{ value: '4', label: '<=' },
]
import i18n from '@/lang'
export const ruleTypeList = () => {
return [
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
// { value: 'not', label: '非' },
]
}
export const expList = () => {
return [
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
]
}
export const advancedExpList = () => {
return [
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
]
}
export const compareTypeList = [
{ value: '1', label: '>' },
{ value: '2', label: '>=' },
{ value: '3', label: '<' },
{ value: '4', label: '<=' },
]

View File

@@ -106,7 +106,7 @@
<CIReferenceAttr
v-if="getAttr(item.property).is_reference && (item.exp === 'is' || item.exp === '~is')"
:style="{ width: '175px' }"
class="select-filter-component ops-select-bg"
class="select-filter-component"
:referenceTypeId="getAttr(item.property).reference_type_id"
:disabled="disabled"
v-model="item.value"
@@ -114,7 +114,7 @@
<a-select
v-else-if="getAttr(item.property).is_bool && (item.exp === 'is' || item.exp === '~is')"
v-model="item.value"
class="select-filter-component ops-select-bg"
class="select-filter-component"
:style="{ width: '175px' }"
:disabled="disabled"
:placeholder="$t('placeholder2')"
@@ -301,7 +301,7 @@ export default {
return [
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
{ value: '~value', label: this.$t('cmdbFilterComp.~value') },
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
]
}
@@ -398,6 +398,7 @@ export default {
/deep/ .ant-select-selection {
height: 24px;
background: #f7f8fa;
line-height: 24px;
border: none;

View File

@@ -1,304 +1,302 @@
<template>
<div>
<a-popover
v-if="isDropdown"
v-model="visible"
trigger="click"
:placement="placement"
overlayClassName="table-filter"
@visibleChange="visibleChange"
>
<slot name="popover_item">
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
</slot>
<template slot="content">
<Expression
:needAddHere="needAddHere"
v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="disabled"
/>
<a-divider :style="{ margin: '10px 0' }" />
<div style="width:554px">
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
</a-space>
</div>
</template>
</a-popover>
<Expression
:needAddHere="needAddHere"
v-else
v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="disabled"
/>
</div>
</template>
<script>
import { v4 as uuidv4 } from 'uuid'
import Expression from './expression.vue'
import { advancedExpList, compareTypeList } from './constants'
export default {
name: 'FilterComp',
components: { Expression },
props: {
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
expression: {
type: String,
default: '',
},
regQ: {
type: String,
default: '(?<=q=).+(?=&)|(?<=q=).+$',
},
placement: {
type: String,
default: 'bottomRight',
},
isDropdown: {
type: Boolean,
default: true,
},
needAddHere: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
advancedExpList,
compareTypeList,
visible: false,
ruleList: [],
filterExp: '',
}
},
methods: {
/**
* @param isInitOne When the initialization exp is null, does the ruleList default to giving one
*/
visibleChange(open, isInitOne = true) {
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
: null
if (open && exp) {
const expArray = exp.split(',').map((item) => {
let has_not = ''
const key = item.split(':')[0]
const val = item
.split(':')
.slice(1)
.join(':')
let type, property, exp, value, min, max, compareType
if (key.includes('-')) {
type = 'or'
if (key.includes('~')) {
property = key.substring(2)
has_not = '~'
} else {
property = key.substring(1)
}
} else {
type = 'and'
if (key.includes('~')) {
property = key.substring(1)
has_not = '~'
} else {
property = key
}
}
const in_reg = /(?<=\().+(?=\))/g
const range_reg = /(?<=\[).+(?=\])/g
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
if (val === '*') {
exp = has_not + 'value'
value = ''
} else if (in_reg.test(val)) {
exp = has_not + 'in'
value = val.match(in_reg)[0]
} else if (range_reg.test(val)) {
exp = has_not + 'range'
value = val.match(range_reg)[0]
min = value.split('_TO_')[0]
max = value.split('_TO_')[1]
} else if (compare_reg.test(val)) {
exp = has_not + 'compare'
value = val.match(compare_reg)[0]
const _compareType = val.substring(0, val.match(compare_reg)['index'])
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
compareType = compareTypeList[idx].value
} else if (!val.includes('*')) {
exp = has_not + 'is'
value = val
} else {
const resList = [
['contain', /(?<=\*).*(?=\*)/g],
['end_with', /(?<=\*).+/g],
['start_with', /.+(?=\*)/g],
]
for (let i = 0; i < 3; i++) {
const reg = resList[i]
if (reg[1].test(val)) {
exp = has_not + reg[0]
value = val.match(reg[1])[0]
break
}
}
}
return {
id: uuidv4(),
type,
property,
exp,
value,
min,
max,
compareType,
}
})
this.ruleList = [...expArray]
} else if (open) {
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
this.ruleList = isInitOne
? [
{
id: uuidv4(),
type: 'and',
property:
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
? _canSearchPreferenceAttrList[0].name
: undefined,
exp: 'is',
value: null,
},
]
: []
}
},
handleClear() {
this.ruleList = [
{
id: uuidv4(),
type: 'and',
property: this.canSearchPreferenceAttrList[0].name,
exp: 'is',
value: null,
},
]
this.filterExp = ''
this.visible = false
this.$emit('setExpFromFilter', this.filterExp)
},
handleSubmit() {
if (this.ruleList && this.ruleList.length) {
this.ruleList[0].type = 'and' // after add/delete, just in case the first one is not 'and'
this.filterExp = ''
const expList = this.ruleList.map((rule) => {
let singleRuleExp = ''
let _exp = rule.exp
if (rule.type === 'or') {
singleRuleExp += '-'
}
if (rule.exp.includes('~')) {
singleRuleExp += '~'
_exp = rule.exp.split('~')[1]
}
singleRuleExp += `${rule.property}:`
if (_exp === 'is') {
singleRuleExp += `${rule.value ?? ''}`
}
if (_exp === 'contain') {
singleRuleExp += `*${rule.value ?? ''}*`
}
if (_exp === 'start_with') {
singleRuleExp += `${rule.value ?? ''}*`
}
if (_exp === 'end_with') {
singleRuleExp += `*${rule.value ?? ''}`
}
if (_exp === 'value') {
singleRuleExp += `*`
}
if (_exp === 'in') {
singleRuleExp += `(${rule.value ?? ''})`
}
if (_exp === 'range') {
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
}
if (_exp === 'compare') {
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
}
return singleRuleExp
})
this.filterExp = expList.join(',')
this.$emit('setExpFromFilter', this.filterExp)
} else {
this.$emit('setExpFromFilter', '')
}
this.visible = false
},
},
}
</script>
<style lang="less" scoped>
.table-filter {
.table-filter-add {
margin-top: 10px;
& > a {
padding: 2px 8px;
&:hover {
background-color: @primary-color_5;
border-radius: 5px;
}
}
}
.table-filter-extra-icon {
padding: 0px 2px;
&:hover {
display: inline-block;
border-radius: 5px;
background-color: #f0faff;
}
}
}
</style>
<style lang="less">
.table-filter-extra-operation {
.ant-popover-inner-content {
padding: 3px 4px;
.operation {
cursor: pointer;
width: 90px;
height: 30px;
line-height: 30px;
padding: 3px 4px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background-color: #f0faff;
}
> .anticon {
margin-right: 10px;
}
}
}
}
</style>
<template>
<div>
<a-popover
v-if="isDropdown"
v-model="visible"
trigger="click"
:placement="placement"
overlayClassName="table-filter"
@visibleChange="visibleChange"
>
<slot name="popover_item">
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
</slot>
<template slot="content">
<Expression
:needAddHere="needAddHere"
v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="disabled"
/>
<a-divider :style="{ margin: '10px 0' }" />
<div style="width:554px">
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
</a-space>
</div>
</template>
</a-popover>
<Expression
:needAddHere="needAddHere"
v-else
v-model="ruleList"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
:disabled="disabled"
/>
</div>
</template>
<script>
import { v4 as uuidv4 } from 'uuid'
import Expression from './expression.vue'
import { advancedExpList, compareTypeList } from './constants'
export default {
name: 'FilterComp',
components: { Expression },
props: {
canSearchPreferenceAttrList: {
type: Array,
required: true,
default: () => [],
},
expression: {
type: String,
default: '',
},
regQ: {
type: String,
default: '(?<=q=).+(?=&)|(?<=q=).+$',
},
placement: {
type: String,
default: 'bottomRight',
},
isDropdown: {
type: Boolean,
default: true,
},
needAddHere: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
advancedExpList,
compareTypeList,
visible: false,
ruleList: [],
filterExp: '',
}
},
methods: {
visibleChange(open, isInitOne = true) {
// isInitOne 初始化exp为空时ruleList是否默认给一条
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
: null
if (open && exp) {
const expArray = exp.split(',').map((item) => {
let has_not = ''
const key = item.split(':')[0]
const val = item
.split(':')
.slice(1)
.join(':')
let type, property, exp, value, min, max, compareType
if (key.includes('-')) {
type = 'or'
if (key.includes('~')) {
property = key.substring(2)
has_not = '~'
} else {
property = key.substring(1)
}
} else {
type = 'and'
if (key.includes('~')) {
property = key.substring(1)
has_not = '~'
} else {
property = key
}
}
const in_reg = /(?<=\().+(?=\))/g
const range_reg = /(?<=\[).+(?=\])/g
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
if (val === '*') {
exp = has_not + 'value'
value = ''
} else if (in_reg.test(val)) {
exp = has_not + 'in'
value = val.match(in_reg)[0]
} else if (range_reg.test(val)) {
exp = has_not + 'range'
value = val.match(range_reg)[0]
min = value.split('_TO_')[0]
max = value.split('_TO_')[1]
} else if (compare_reg.test(val)) {
exp = has_not + 'compare'
value = val.match(compare_reg)[0]
const _compareType = val.substring(0, val.match(compare_reg)['index'])
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
compareType = compareTypeList[idx].value
} else if (!val.includes('*')) {
exp = has_not + 'is'
value = val
} else {
const resList = [
['contain', /(?<=\*).*(?=\*)/g],
['end_with', /(?<=\*).+/g],
['start_with', /.+(?=\*)/g],
]
for (let i = 0; i < 3; i++) {
const reg = resList[i]
if (reg[1].test(val)) {
exp = has_not + reg[0]
value = val.match(reg[1])[0]
break
}
}
}
return {
id: uuidv4(),
type,
property,
exp,
value,
min,
max,
compareType,
}
})
this.ruleList = [...expArray]
} else if (open) {
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
this.ruleList = isInitOne
? [
{
id: uuidv4(),
type: 'and',
property:
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
? _canSearchPreferenceAttrList[0].name
: undefined,
exp: 'is',
value: null,
},
]
: []
}
},
handleClear() {
this.ruleList = [
{
id: uuidv4(),
type: 'and',
property: this.canSearchPreferenceAttrList[0].name,
exp: 'is',
value: null,
},
]
this.filterExp = ''
this.visible = false
this.$emit('setExpFromFilter', this.filterExp)
},
handleSubmit() {
if (this.ruleList && this.ruleList.length) {
this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and
this.filterExp = ''
const expList = this.ruleList.map((rule) => {
let singleRuleExp = ''
let _exp = rule.exp
if (rule.type === 'or') {
singleRuleExp += '-'
}
if (rule.exp.includes('~')) {
singleRuleExp += '~'
_exp = rule.exp.split('~')[1]
}
singleRuleExp += `${rule.property}:`
if (_exp === 'is') {
singleRuleExp += `${rule.value ?? ''}`
}
if (_exp === 'contain') {
singleRuleExp += `*${rule.value ?? ''}*`
}
if (_exp === 'start_with') {
singleRuleExp += `${rule.value ?? ''}*`
}
if (_exp === 'end_with') {
singleRuleExp += `*${rule.value ?? ''}`
}
if (_exp === 'value') {
singleRuleExp += `*`
}
if (_exp === 'in') {
singleRuleExp += `(${rule.value ?? ''})`
}
if (_exp === 'range') {
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
}
if (_exp === 'compare') {
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
}
return singleRuleExp
})
this.filterExp = expList.join(',')
this.$emit('setExpFromFilter', this.filterExp)
} else {
this.$emit('setExpFromFilter', '')
}
this.visible = false
},
},
}
</script>
<style lang="less" scoped>
.table-filter {
.table-filter-add {
margin-top: 10px;
& > a {
padding: 2px 8px;
&:hover {
background-color: #f0faff;
border-radius: 5px;
}
}
}
.table-filter-extra-icon {
padding: 0px 2px;
&:hover {
display: inline-block;
border-radius: 5px;
background-color: #f0faff;
}
}
}
</style>
<style lang="less">
.table-filter-extra-operation {
.ant-popover-inner-content {
padding: 3px 4px;
.operation {
cursor: pointer;
width: 90px;
height: 30px;
line-height: 30px;
padding: 3px 4px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background-color: #f0faff;
}
> .anticon {
margin-right: 10px;
}
}
}
}
</style>

View File

@@ -14,7 +14,7 @@
<script>
/**
* Collapse transition effect for elements
* 元素折叠过度效果
*/
export default {
name: 'CollapseTransition',
@@ -33,17 +33,20 @@ export default {
},
methods: {
collapseBeforeEnter(el) {
// console.log('11, collapseBeforeEnter');
this.oldPaddingBottom = el.style.paddingBottom
this.oldPaddingTop = el.style.paddingTop
// set the element's maxHeight to 0 before the transition effect starts so that the element's maxHeight has an initial value
// 过渡效果开始前设置元素的maxHeight为0让元素maxHeight有一个初始值
el.style.paddingTop = '0'
el.style.paddingBottom = '0'
el.style.maxHeight = '0'
},
collapseEnter(el, done) {
// console.log('22, collapseEnter');
//
this.oldOverflow = el.style.overflow
const elHeight = el.scrollHeight
// After entering, set maxHeight to the element's height; setting maxHeight to auto will not have a transition effect
// 过渡效果进入后将元素的maxHeight设置为元素本身的高度将元素maxHeight设置为auto不会有过渡效果
if (elHeight > 0) {
el.style.maxHeight = elHeight + 'px'
} else {
@@ -56,20 +59,24 @@ export default {
// done();
const onTransitionDone = function() {
done()
// console.log('enter onTransitionDone');
el.removeEventListener('transitionend', onTransitionDone, false)
el.removeEventListener('transitioncancel', onTransitionDone, false)
}
// Bind transition end event to finish Vue's transition immediately after the CSS transition
// 绑定元素的transition完成事件在transition完成后立即完成vue的过度动效
el.addEventListener('transitionend', onTransitionDone, false)
el.addEventListener('transitioncancel', onTransitionDone, false)
},
collapseAfterEnter(el) {
// Restore maxHeight after transition is complete
// console.log('33, collapseAfterEnter');
// 过渡效果完成后恢复元素的maxHeight
el.style.maxHeight = ''
el.style.overflow = this.oldOverflow
},
collapseBeforeLeave(el) {
// console.log('44, collapseBeforeLeave', el.scrollHeight);
this.oldPaddingBottom = el.style.paddingBottom
this.oldPaddingTop = el.style.paddingTop
this.oldOverflow = el.style.overflow
@@ -78,6 +85,8 @@ export default {
el.style.overflow = 'hidden'
},
collapseLeave(el, done) {
// console.log('55, collapseLeave', el.scrollHeight);
if (el.scrollHeight !== 0) {
el.style.maxHeight = '0'
el.style.paddingBottom = '0'
@@ -86,14 +95,16 @@ export default {
// done();
const onTransitionDone = function() {
done()
// console.log('leave onTransitionDone');
el.removeEventListener('transitionend', onTransitionDone, false)
el.removeEventListener('transitioncancel', onTransitionDone, false)
}
// Bind transition end event to finish Vue's transition immediately after the CSS transition
// 绑定元素的transition完成事件在transition完成后立即完成vue的过度动效
el.addEventListener('transitionend', onTransitionDone, false)
el.addEventListener('transitioncancel', onTransitionDone, false)
},
collapseAfterLeave(el) {
// console.log('66, collapseAfterLeave');
el.style.maxHeight = ''
el.style.overflow = this.oldOverflow
el.style.paddingBottom = this.oldPaddingBottom

View File

@@ -341,7 +341,7 @@ export default {
},
}
</script>
<style lang="less" scoped>
<style scoped>
.pop_btn {
text-align: right;
margin-top: 24px;

View File

@@ -1,6 +1,9 @@
/* eslint-disable */
/*
cron表达式验证
!!!!!!!
以下为凶残的cron表达式验证胆小肾虚及心脏病者慎入!!!
不听劝告者后果自负T T
!!!!!!!
cron表达式为秒
判断正误方法错误的话返回错误信息正确的话返回true
*/

View File

@@ -47,7 +47,7 @@ export const commonIconList = ['changyong-ubuntu',
export const linearIconList = [
{
value: 'database',
label: 'components.database',
label: '数据库',
list: [{
value: 'icon-xianxing-DB2',
label: 'DB2'
@@ -81,7 +81,7 @@ export const linearIconList = [
}]
}, {
value: 'system',
label: 'components.system',
label: '操作系统',
list: [{
value: 'icon-xianxing-Windows',
label: 'Windows'
@@ -106,7 +106,7 @@ export const linearIconList = [
}]
}, {
value: 'language',
label: 'components.language',
label: '语言',
list: [{
value: 'icon-xianxing-python',
label: 'python'
@@ -137,7 +137,7 @@ export const linearIconList = [
}]
}, {
value: 'status',
label: 'components.status',
label: '状态',
list: [{
value: 'icon-xianxing-yiwen',
label: '疑问'
@@ -177,7 +177,7 @@ export const linearIconList = [
}]
}, {
value: 'icon-xianxing-application',
label: 'components.commonComponent',
label: '常用组件',
list: [{
value: 'icon-xianxing-yilianjie',
label: '已连接'
@@ -310,7 +310,7 @@ export const linearIconList = [
}]
}, {
value: 'data',
label: 'components.data',
label: '数据',
list: [{
value: 'icon-xianxing-bingzhuangtu',
label: '饼状图'
@@ -387,7 +387,7 @@ export const linearIconList = [
export const fillIconList = [
{
value: 'database',
label: 'components.database',
label: '数据库',
list: [{
value: 'icon-shidi-DB2',
label: 'DB2'
@@ -421,7 +421,7 @@ export const fillIconList = [
}]
}, {
value: 'system',
label: 'components.system',
label: '操作系统',
list: [{
value: 'icon-shidi-Windows',
label: 'Windows'
@@ -446,7 +446,7 @@ export const fillIconList = [
}]
}, {
value: 'language',
label: 'components.language',
label: '语言',
list: [{
value: 'icon-shidi-python',
label: 'python'
@@ -477,7 +477,7 @@ export const fillIconList = [
}]
}, {
value: 'status',
label: 'components.status',
label: '状态',
list: [{
value: 'icon-shidi-yiwen',
label: '疑问'
@@ -517,7 +517,7 @@ export const fillIconList = [
}]
}, {
value: 'icon-shidi-application',
label: 'components.commonComponent',
label: '常用组件',
list: [{
value: 'icon-shidi-yilianjie',
label: '已连接'
@@ -650,7 +650,7 @@ export const fillIconList = [
}]
}, {
value: 'data',
label: 'components.data',
label: '数据',
list: [{
value: 'icon-shidi-bingzhuangtu',
label: '饼状图'
@@ -727,7 +727,7 @@ export const fillIconList = [
export const multicolorIconList = [
{
value: 'database',
label: 'components.database',
label: '数据库',
list: [{
value: 'caise-TIDB',
label: 'TIDB'
@@ -773,7 +773,7 @@ export const multicolorIconList = [
}]
}, {
value: 'cloud',
label: 'components.cloud',
label: '',
list: [{
value: 'AWS',
label: 'AWS'
@@ -819,7 +819,7 @@ export const multicolorIconList = [
}]
}, {
value: 'system',
label: 'components.system',
label: '操作系统',
list: [{
value: 'ciase-aix',
label: 'aix'
@@ -847,7 +847,7 @@ export const multicolorIconList = [
}]
}, {
value: 'language',
label: 'components.language',
label: '语言',
list: [{
value: 'caise-python',
label: 'python'
@@ -878,7 +878,7 @@ export const multicolorIconList = [
}]
}, {
value: 'status',
label: 'components.status',
label: '状态',
list: [{
value: 'caise-yiwen',
label: '疑问'
@@ -918,7 +918,7 @@ export const multicolorIconList = [
}]
}, {
value: 'caise-application',
label: 'components.commonComponent',
label: '常用组件',
list: [{
value: 'caise-websphere',
label: 'WebSphere'
@@ -1180,7 +1180,7 @@ export const multicolorIconList = [
}]
}, {
value: 'data',
label: 'components.data',
label: '数据',
list: [{
value: 'caise-bingzhuangtu',
label: '饼状图'

View File

@@ -33,7 +33,7 @@
<template v-if="iconList && iconList.length">
<template v-if="currentIconType !== '4'">
<div v-for="category in iconList" :key="category.value">
<h4 class="category">{{ $t(category.label) }}</h4>
<h4 class="category">{{ category.label }}</h4>
<div class="custom-icon-select-popover-content-wrapper">
<div
v-for="name in category.list"

View File

@@ -1,60 +0,0 @@
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
/*
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
*/
export default {
name: 'Ellipsis',
components: {
Tooltip,
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis',
},
tooltip: {
type: Boolean,
},
length: {
type: Number,
required: true,
},
lines: {
type: Number,
default: 1,
},
fullWidthRecognition: {
type: Boolean,
default: false,
},
},
methods: {
getStrDom(str, fullLength) {
return <span>{cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '')}</span>
},
getTooltip(fullStr, fullLength) {
return (
<Tooltip overlayStyle={{ maxWidth: '700px' }}>
<template slot="title">{fullStr}</template>
{this.getStrDom(fullStr, fullLength)}
</Tooltip>
)
},
},
render() {
const { tooltip, length } = this.$props
const str = this.$slots.default.map((vNode) => vNode.text).join('')
const fullLength = getStrFullLength(str)
const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength)
return strDom
},
}
</script>

View File

@@ -1,3 +0,0 @@
import Ellipsis from './Ellipsis'
export default Ellipsis

View File

@@ -1,38 +0,0 @@
# Ellipsis 文本自动省略号
文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
引用方式:
```javascript
import Ellipsis from '@/components/Ellipsis'
export default {
components: {
Ellipsis
}
}
```
## 代码演示 [demo](https://pro.loacg.com/test/home)
```html
<ellipsis :length="100" tooltip>
There were injuries alleged in three cases in 2015, and a
fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.
</ellipsis>
```
## API
参数 | 说明 | 类型 | 默认值
----|------|-----|------
tooltip | 移动到文本展示完整内容的提示 | boolean | -
length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -

View File

@@ -6,9 +6,9 @@
</div>
<div class="content">
<h1>{{ config[type].title }}</h1>
<div class="desc">{{ $t(config[type].desc) }}</div>
<div class="desc">{{ config[type].desc }}</div>
<div class="actions">
<a-button type="primary" @click="handleToHome">{{ $t('exception.backToHome') }}</a-button>
<a-button type="primary" @click="handleToHome">返回首页</a-button>
</div>
</div>
</div>

View File

@@ -2,17 +2,17 @@ const types = {
403: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
title: '403',
desc: 'exception.desc1'
desc: '抱歉,你无权访问该页面'
},
404: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
title: '404',
desc: 'exception.desc2'
desc: '抱歉,你访问的页面不存在或仍在开发中'
},
500: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
title: '500',
desc: 'exception.desc3'
desc: '抱歉,服务器出错了'
}
}

View File

@@ -0,0 +1,58 @@
<template>
<div class="footer">
<div class="links">
<a
href="https://veops.cn/"
target="_blank"
>维易科技</a>
<a
href="https://github.com/sendya/ant-design-pro-vue"
target="_blank"
>
<a-icon type="github" />
</a>
</div>
<div class="copyright">
Copyright
<a-icon type="copyright" /> 2021-2023 <span>@维易科技</span>
</div>
</div>
</template>
<script>
export default {
name: 'GlobalFooter',
data () {
return {}
}
}
</script>
<style lang="less" scoped>
.footer {
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
.links {
margin-bottom: 8px;
a {
color: rgba(0, 0, 0, 0.45);
&:hover {
color: rgba(0, 0, 0, 0.65);
}
&:not(:last-child) {
margin-right: 40px;
}
}
}
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,2 @@
import GlobalFooter from './GlobalFooter'
export default GlobalFooter

View File

@@ -11,19 +11,10 @@
.ant-input {
box-shadow: none;
border: none;
background-color: #F7F8FA;
height: 30px;
line-height: 30px;
border-radius: 30px;
&:focus {
border: solid 1px #B1C9FF;
}
}
.cmdb-side-menu-search-focused {
.ant-input {
border: solid 1px #B1C9FF;
}
}
.ant-input-suffix {

View File

@@ -1,6 +1,14 @@
import router, { resetRouter } from '@/router'
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
import store from '@/store'
import {
subscribeCIType,
subscribeTreeView,
} from '@/modules/cmdb/api/preference'
import { searchResourceType } from '@/modules/acl/api/resource'
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
import styles from './index.module.less'
import { mapActions } from 'vuex'
@@ -79,6 +87,40 @@ export default {
inject: ['reload'],
methods: {
...mapActions(['UpdateCMDBSEarchValue']),
cancelAttributes(e, menu) {
const that = this
e.preventDefault()
e.stopPropagation()
this.$confirm({
title: this.$t('alert'),
content: this.$t('cmdb.preference.confirmcancelSub2', { name: menu.meta.title }),
onOk() {
const citypeId = menu.meta.typeId
const unsubCIType = subscribeCIType(citypeId, '')
const unsubTree = subscribeTreeView(citypeId, '')
Promise.all([unsubCIType, unsubTree]).then(() => {
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
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('/')
if (Number(hrefSplit[hrefSplit.length - 1]) === Number(citypeId)) {
that.$router.push('/cmdb/preference')
}
const roles = store.getters.roles
resetRouter()
store.dispatch('GenerateRoutes', { roles }, { root: true }).then(() => {
router.addRoutes(store.getters.appRoutes)
})
if (hrefSplit[hrefSplit.length - 1] === 'preference') {
that.reload()
}
})
},
})
},
// select menu item
onOpenChange(openKeys) {
if (this.mode === 'horizontal') {
@@ -128,6 +170,8 @@ export default {
return this.$t(`${title}`)
},
renderMenuItem(menu) {
const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/'
const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/'
const target = menu.meta.target || null
const tag = target && 'a' || 'router-link'
const props = { to: { name: menu.name } }
@@ -144,11 +188,28 @@ export default {
<tag {...{ props, attrs }}>
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
<span>
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>
{this.renderI18n(menu.meta.title)}
</span>
<span style={menu.meta.style} class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
{isShowDot && !menu.meta.disabled &&
<a-popover
overlayClassName="custom-menu-extra-submenu"
placement="rightTop"
arrowPointAtCenter
autoAdjustOverflow={false}
getPopupContainer={(trigger) => trigger}
content={() =>
<div>
<div onClick={e => this.handlePerm(e, menu, 'CIType')} class="custom-menu-extra-submenu-item"><a-icon type="user-add" />{ this.renderI18n('grant') }</div>
<div onClick={e => this.cancelAttributes(e, menu)} class="custom-menu-extra-submenu-item"><a-icon type="star" />{ this.renderI18n('cmdb.preference.cancelSub') }</div>
</div>}
>
<a-icon type="menu" ref="extraEllipsis" class="custom-menu-extra-ellipsis"></a-icon>
</a-popover>
}
{isShowGrant && <a-icon class="custom-menu-extra-ellipsis" onClick={e => this.handlePerm(e, menu, 'RelationView')} type="user-add" />}
</span>
</tag>
{isShowDot && <CMDBGrant ref="cmdbGrantCIType" resourceType="CIType" app_id="cmdb" />}
{isShowGrant && <CMDBGrant ref="cmdbGrantRelationView" resourceType="RelationView" app_id="cmdb" />}
</Item>
)
},
@@ -211,6 +272,27 @@ export default {
)
}
},
handlePerm(e, menu, resource_type_name) {
e.stopPropagation()
e.preventDefault()
roleHasPermissionToGrant({
app_id: 'cmdb',
resource_type_name,
perm: 'grant',
resource_name: menu.meta.name,
}).then(res => {
if (res.result) {
console.log(menu)
if (resource_type_name === 'CIType') {
this.$refs.cmdbGrantCIType.open({ name: menu.meta.name, cmdbGrantType: 'ci', CITypeId: menu.meta?.typeId })
} else {
this.$refs.cmdbGrantRelationView.open({ name: menu.meta.name, cmdbGrantType: 'relation_view' })
}
} else {
this.$message.error(this.$t('noPermission'))
}
})
},
jumpCMDBSearch(value) {
this.UpdateCMDBSEarchValue(value)
@@ -231,7 +313,10 @@ export default {
<Item class={styles['cmdb-side-menu-search']}>
<a-input
ref="cmdbSideMenuSearchInputRef"
class={`ops-input ${this.$route.name === 'cmdb_resource_search' ? 'cmdb-side-menu-search-focused' : ''}`}
class={styles['cmdb-side-menu-search-input']}
style={{
border: this.$route.name === 'cmdb_resource_search' ? 'solid 1px #B1C9FF' : ''
}}
placeholder={this.$t('cmdbSearch')}
onPressEnter={(e) => {
this.jumpCMDBSearch(e.target.value)

View File

@@ -1,121 +1,121 @@
<template>
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
<slot></slot>
<template #empty>
<slot name="empty">
<div :style="{ paddingTop: '10px' }">
<img :style="{ width: '140px', height: '120px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</slot>
</template>
<template #loading>
<slot name="loading"></slot>
</template>
</vxe-table>
</template>
<script>
import _ from 'lodash'
// 该组件使用方法与vxe-table一致但调用它的方法时需先调用getVxetableRef()获取到vxe-table实体
export default {
name: 'OpsTable',
data() {
return {
// isShifting: false,
// lastIndex: -1,
lastSelected: [],
currentSelected: [],
}
},
computed: {
new$listeners() {
if (!Object.keys(this.$listeners).length) {
return this.$listeners
}
return Object.assign(this.$listeners, {
// 在这里覆盖原有的change事件
// 'checkbox-change': this.selectChangeEvent,
'checkbox-range-change': this.checkboxRangeChange,
'checkbox-range-start': this.checkboxRangeStart,
'checkbox-range-end': this.checkboxRangeEnd,
})
},
},
mounted() {
// window.onkeydown = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = true
// }
// }
// window.onkeyup = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = false
// this.lastIndex = -1
// }
// }
},
beforeDestroy() {
// window.onkeydown = ''
// window.onkeyup = ''
},
methods: {
getVxetableRef() {
return this.$refs.xTable
},
// selectChangeEvent(e) {
// const xTable = this.$refs.xTable
// const { lastIndex } = this
// const currentIndex = e.rowIndex
// const { tableData } = xTable.getTableData()
// if (lastIndex > -1 && this.isShifting) {
// let start = lastIndex
// let end = currentIndex
// if (lastIndex > currentIndex) {
// start = currentIndex
// end = lastIndex
// }
// const rangeData = tableData.slice(start, end + 1)
// xTable.setCheckboxRow(rangeData, true)
// }
// this.lastIndex = currentIndex
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
// },
checkboxRangeStart(e) {
const xTable = this.$refs.xTable
const lastSelected = xTable.getCheckboxRecords()
const selectedReserve = xTable.getCheckboxReserveRecords()
this.lastSelected = [...lastSelected, ...selectedReserve]
this.$emit('checkbox-range-start', e)
},
checkboxRangeChange(e) {
const xTable = this.$refs.xTable
xTable.setCheckboxRow(this.lastSelected, true)
this.currentSelected = e.records
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
this.$emit('checkbox-range-change', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
},
checkboxRangeEnd(e) {
const xTable = this.$refs.xTable
const isAllSelected = this.currentSelected.every((item) => {
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
return _idx > -1
})
if (isAllSelected) {
xTable.setCheckboxRow(this.currentSelected, false)
}
this.currentSelected = []
this.lastSelected = []
this.$emit('checkbox-range-end', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
},
},
}
</script>
<style lang="less"></style>
<template>
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
<slot></slot>
<template #empty>
<slot name="empty">
<div :style="{ paddingTop: '10px' }">
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</slot>
</template>
<template #loading>
<slot name="loading"></slot>
</template>
</vxe-table>
</template>
<script>
import _ from 'lodash'
// 该组件使用方法与vxe-table一致但调用它的方法时需先调用getVxetableRef()获取到vxe-table实体
export default {
name: 'OpsTable',
data() {
return {
// isShifting: false,
// lastIndex: -1,
lastSelected: [],
currentSelected: [],
}
},
computed: {
new$listeners() {
if (!Object.keys(this.$listeners).length) {
return this.$listeners
}
return Object.assign(this.$listeners, {
// 在这里覆盖原有的change事件
// 'checkbox-change': this.selectChangeEvent,
'checkbox-range-change': this.checkboxRangeChange,
'checkbox-range-start': this.checkboxRangeStart,
'checkbox-range-end': this.checkboxRangeEnd,
})
},
},
mounted() {
// window.onkeydown = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = true
// }
// }
// window.onkeyup = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = false
// this.lastIndex = -1
// }
// }
},
beforeDestroy() {
// window.onkeydown = ''
// window.onkeyup = ''
},
methods: {
getVxetableRef() {
return this.$refs.xTable
},
// selectChangeEvent(e) {
// const xTable = this.$refs.xTable
// const { lastIndex } = this
// const currentIndex = e.rowIndex
// const { tableData } = xTable.getTableData()
// if (lastIndex > -1 && this.isShifting) {
// let start = lastIndex
// let end = currentIndex
// if (lastIndex > currentIndex) {
// start = currentIndex
// end = lastIndex
// }
// const rangeData = tableData.slice(start, end + 1)
// xTable.setCheckboxRow(rangeData, true)
// }
// this.lastIndex = currentIndex
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
// },
checkboxRangeStart(e) {
const xTable = this.$refs.xTable
const lastSelected = xTable.getCheckboxRecords()
const selectedReserve = xTable.getCheckboxReserveRecords()
this.lastSelected = [...lastSelected, ...selectedReserve]
this.$emit('checkbox-range-start', e)
},
checkboxRangeChange(e) {
const xTable = this.$refs.xTable
xTable.setCheckboxRow(this.lastSelected, true)
this.currentSelected = e.records
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
this.$emit('checkbox-range-change', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
},
checkboxRangeEnd(e) {
const xTable = this.$refs.xTable
const isAllSelected = this.currentSelected.every((item) => {
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
return _idx > -1
})
if (isAllSelected) {
xTable.setCheckboxRow(this.currentSelected, false)
}
this.currentSelected = []
this.lastSelected = []
this.$emit('checkbox-range-end', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
},
},
}
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,10 @@
import { Spin } from 'ant-design-vue'
export default {
name: 'PageLoading',
render () {
return (<div style={{ paddingTop: 100, textAlign: 'center' }}>
<Spin size="large" />
</div>)
}
}

View File

@@ -0,0 +1,352 @@
<template>
<div class="setting-drawer" ref="settingDrawer">
<a-drawer
width="300"
placement="right"
@close="onClose"
:closable="false"
:visible="visible"
>
<div class="setting-drawer-index-content">
<div :style="{ marginBottom: '24px' }">
<h3 class="setting-drawer-index-title">整体风格设置</h3>
<div class="setting-drawer-index-blockChecbox">
<a-tooltip>
<template slot="title">
暗色菜单风格
</template>
<div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
<img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark">
<div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
<a-icon type="check"/>
</div>
</div>
</a-tooltip>
<a-tooltip>
<template slot="title">
亮色菜单风格
</template>
<div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
<img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light">
<div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
<a-icon type="check"/>
</div>
</div>
</a-tooltip>
</div>
</div>
<div :style="{ marginBottom: '24px' }">
<h3 class="setting-drawer-index-title">主题色</h3>
<div style="height: 20px">
<a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
<template slot="title">
{{ item.key }}
</template>
<a-tag :color="item.color" @click="changeColor(item.color)">
<a-icon type="check" v-if="item.color === primaryColor"></a-icon>
</a-tag>
</a-tooltip>
</div>
</div>
<a-divider />
<div :style="{ marginBottom: '24px' }">
<h3 class="setting-drawer-index-title">导航模式</h3>
<div class="setting-drawer-index-blockChecbox">
<a-tooltip>
<template slot="title">
侧边栏导航
</template>
<div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
<img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu">
<div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
<a-icon type="check"/>
</div>
</div>
</a-tooltip>
<a-tooltip>
<template slot="title">
顶部栏导航
</template>
<div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
<img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu">
<div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
<a-icon type="check"/>
</div>
</div>
</a-tooltip>
</div>
<div :style="{ marginTop: '24px' }">
<a-list :split="false">
<a-list-item>
<a-tooltip slot="actions">
<template slot="title">
该设定仅 [顶部栏导航] 时有效
</template>
<a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
<a-select-option value="Fixed">固定</a-select-option>
<a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
</a-select>
</a-tooltip>
<a-list-item-meta>
<div slot="title">内容区域宽度</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
<a-list-item-meta>
<div slot="title">固定 Header</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
<a-list-item-meta>
<a-tooltip slot="title" placement="left">
<template slot="title">固定 Header 时可配置</template>
<div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div>
</a-tooltip>
</a-list-item-meta>
</a-list-item>
<a-list-item >
<a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" />
<a-list-item-meta>
<div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
</a-list-item-meta>
</a-list-item>
</a-list>
</div>
</div>
<a-divider />
<div :style="{ marginBottom: '24px' }">
<h3 class="setting-drawer-index-title">其他设置</h3>
<div>
<a-list :split="false">
<a-list-item>
<a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
<a-list-item-meta>
<div slot="title">色弱模式</div>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-switch slot="actions" size="small" :defaultChecked="multiTab" @change="onMultiTab" />
<a-list-item-meta>
<div slot="title">多页签模式</div>
</a-list-item-meta>
</a-list-item>
</a-list>
</div>
</div>
<a-divider />
<div :style="{ marginBottom: '24px' }">
<a-button
@click="doCopy"
icon="copy"
block
>拷贝设置</a-button>
<a-alert type="warning" :style="{ marginTop: '24px' }">
<span slot="message">
配置栏只在开发环境用于预览生产环境不会展现请手动修改配置文件
<a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/setting.js" target="_blank">src/config/setting.js</a>
</span>
</a-alert>
</div>
</div>
<div class="setting-drawer-index-handle" @click="toggle">
<a-icon type="setting" v-if="!visible"/>
<a-icon type="close" v-else/>
</div>
</a-drawer>
</div>
</template>
<script>
import SettingItem from './SettingItem'
import config from '@/config/setting'
import { updateTheme, updateColorWeak, colorList } from './settingConfig'
import { mixin, mixinDevice } from '@/utils/mixin'
export default {
components: {
SettingItem
},
mixins: [mixin, mixinDevice],
data () {
return {
visible: true,
colorList
}
},
watch: {
},
mounted () {
const vm = this
setTimeout(() => {
vm.visible = false
}, 16)
updateTheme(this.primaryColor)
if (this.colorWeak !== config.colorWeak) {
updateColorWeak(this.colorWeak)
}
},
methods: {
showDrawer () {
this.visible = true
},
onClose () {
this.visible = false
},
toggle () {
this.visible = !this.visible
},
onColorWeak (checked) {
this.$store.dispatch('ToggleWeak', checked)
updateColorWeak(checked)
},
onMultiTab (checked) {
this.$store.dispatch('ToggleMultiTab', checked)
},
handleMenuTheme (theme) {
this.$store.dispatch('ToggleTheme', theme)
},
doCopy () {
// get current settings from mixin or this.$store.state.app, pay attention to the property name
const text = `export default {
primaryColor: '${this.primaryColor}', // primary color of ant design
navTheme: '${this.navTheme}', // theme for nav menu
layout: '${this.layoutMode}', // nav menu position: sidemenu or topmenu
contentWidth: '${this.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu
fixedHeader: ${this.fixedHeader}, // sticky header
fixSiderbar: ${this.fixSiderbar}, // sticky siderbar
autoHideHeader: ${this.autoHideHeader}, // auto hide header
colorWeak: ${this.colorWeak},
multiTab: ${this.multiTab},
production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true',
// vue-ls options
storageOptions: {
namespace: 'pro__',
name: 'ls',
storage: 'local',
}
}`
this.$copyText(text).then(message => {
console.log('copy', message)
this.$message.success('复制完毕')
}).catch(err => {
console.log('copy.err', err)
this.$message.error('复制失败')
})
},
handleLayout (mode) {
this.$store.dispatch('ToggleLayoutMode', mode)
// 因为顶部菜单不能固定左侧菜单栏所以强制关闭
this.handleFixSiderbar(false)
},
handleContentWidthChange (type) {
this.$store.dispatch('ToggleContentWidth', type)
},
changeColor (color) {
if (this.primaryColor !== color) {
this.$store.dispatch('ToggleColor', color)
updateTheme(color)
}
},
handleFixedHeader (fixed) {
this.$store.dispatch('ToggleFixedHeader', fixed)
},
handleFixedHeaderHidden (autoHidden) {
this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
},
handleFixSiderbar (fixed) {
if (this.layoutMode === 'topmenu') {
this.$store.dispatch('ToggleFixSiderbar', false)
return
}
this.$store.dispatch('ToggleFixSiderbar', fixed)
}
}
}
</script>
<style lang="less" scoped>
.setting-drawer-index-content {
.setting-drawer-index-blockChecbox {
display: flex;
.setting-drawer-index-item {
margin-right: 16px;
position: relative;
border-radius: 4px;
cursor: pointer;
img {
width: 48px;
}
.setting-drawer-index-selectIcon {
position: absolute;
top: 0;
right: 0;
width: 100%;
padding-top: 15px;
padding-left: 24px;
height: 100%;
color: #1890ff;
font-size: 14px;
font-weight: 700;
}
}
}
.setting-drawer-theme-color-colorBlock {
width: 20px;
height: 20px;
border-radius: 2px;
float: left;
cursor: pointer;
margin-right: 8px;
padding-left: 0px;
padding-right: 0px;
text-align: center;
color: #fff;
font-weight: 700;
i {
font-size: 14px;
}
}
}
.setting-drawer-index-handle {
position: absolute;
top: 240px;
background: #1890ff;
width: 48px;
height: 48px;
right: 300px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
pointer-events: auto;
z-index: 1001;
text-align: center;
font-size: 16px;
border-radius: 4px 0 0 4px;
i {
color: rgb(255, 255, 255);
font-size: 20px;
}
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<div class="setting-drawer-index-item">
<h3 class="setting-drawer-index-title">{{ title }}</h3>
<slot></slot>
<a-divider v-if="divider"/>
</div>
</template>
<script>
export default {
name: 'SettingItem',
props: {
title: {
type: String,
default: ''
},
divider: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
.setting-drawer-index-item {
margin-bottom: 24px;
.setting-drawer-index-title {
font-size: 14px;
color: rgba(0, 0, 0, .85);
line-height: 22px;
margin-bottom: 12px;
}
}
</style>

View File

@@ -0,0 +1,2 @@
import SettingDrawer from './SettingDrawer'
export default SettingDrawer

View File

@@ -0,0 +1,105 @@
import { message } from 'ant-design-vue/es'
// import setting from '../setting';
import themeColor from './themeColor.js'
// let lessNodesAppended
const colorList = [
{
key: '薄暮', color: '#F5222D'
},
{
key: '火山', color: '#FA541C'
},
{
key: '日暮', color: '#FAAD14'
},
{
key: '明青', color: '#13C2C2'
},
{
key: '极光绿', color: '#52C41A'
},
{
key: '拂晓蓝(默认)', color: '#1890FF'
},
{
key: '极客蓝', color: '#2F54EB'
},
{
key: '酱紫', color: '#722ED1'
}
]
const updateTheme = newPrimaryColor => {
const hideMessage = message.loading('正在切换主题!', 0)
themeColor.changeColor(newPrimaryColor).finally(t => {
hideMessage()
})
}
/*
const updateTheme = primaryColor => {
// Don't compile less in production!
/* if (process.env.NODE_ENV === 'production') {
return;
} * /
// Determine if the component is remounted
if (!primaryColor) {
return
}
const hideMessage = message.loading('正在编译主题!', 0)
function buildIt () {
if (!window.less) {
return
}
setTimeout(() => {
window.less
.modifyVars({
'@primary-color': primaryColor
})
.then(() => {
hideMessage()
})
.catch(() => {
message.error('Failed to update theme')
hideMessage()
})
}, 200)
}
if (!lessNodesAppended) {
// insert less.js and color.less
const lessStyleNode = document.createElement('link')
const lessConfigNode = document.createElement('script')
const lessScriptNode = document.createElement('script')
lessStyleNode.setAttribute('rel', 'stylesheet/less')
lessStyleNode.setAttribute('href', '/color.less')
lessConfigNode.innerHTML = `
window.less = {
async: true,
env: 'production',
javascriptEnabled: true
};
`
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js'
lessScriptNode.async = true
lessScriptNode.onload = () => {
buildIt()
lessScriptNode.onload = null
}
document.body.appendChild(lessStyleNode)
document.body.appendChild(lessConfigNode)
document.body.appendChild(lessScriptNode)
lessNodesAppended = true
} else {
buildIt()
}
}
*/
const updateColorWeak = colorWeak => {
// document.body.className = colorWeak ? 'colorWeak' : '';
colorWeak ? document.body.classList.add('colorWeak') : document.body.classList.remove('colorWeak')
}
export { updateTheme, colorList, updateColorWeak }

View File

@@ -0,0 +1,23 @@
import client from 'webpack-theme-color-replacer/client'
import generate from '@ant-design/colors/lib/generate'
export default {
getAntdSerials (color) {
// 淡化即less的tint
const lightens = new Array(9).fill().map((t, i) => {
return client.varyColor.lighten(color, i / 10)
})
// colorPalette变换得到颜色值
const colorPalettes = generate(color)
return lightens.concat(colorPalettes)
},
changeColor (newColor) {
var options = {
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
changeUrl (cssUrl) {
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
}
}
return client.changer.changeColor(options, Promise)
}
}

View File

@@ -2,12 +2,10 @@ import MultiTab from '@/components/MultiTab'
import Result from '@/components/Result'
import TagSelect from '@/components/TagSelect'
import ExceptionPage from '@/components/Exception'
import Ellipsis from '@/components/Ellipsis'
export {
MultiTab,
Result,
ExceptionPage,
TagSelect,
Ellipsis
TagSelect
}

View File

@@ -1,6 +1,6 @@
<template>
<a-tooltip>
<template slot="title">{{ $t('documentCenter') }}</template>
<template slot="title">文档中心</template>
<span class="document-link">
<a-icon type="question-circle" @click="handleClick" />
</span>

View File

@@ -38,7 +38,7 @@
<script>
import store from '@/store'
import { gridSvg, top_agent, top_acl } from '@/core/icons'
import { getPreference } from '@/modules/cmdb/api/preference'
export default {
name: 'TopMenu',
components: { gridSvg, top_agent, top_acl },
@@ -77,7 +77,18 @@ export default {
async handleClick(route) {
this.visible = false
if (route.name !== this.current) {
this.$router.push(route.redirect)
if (route.name === 'cmdb') {
const preference = await getPreference()
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.type_ids.some((item) => item === Number(lastTypeId))) {
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
} else {
this.$router.push('/cmdb/dashboard')
}
} else {
this.$router.push(route.redirect)
}
// this.current = route.name
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
import Vue from 'vue'
import router from './router'
import store from './store'
@@ -10,23 +11,16 @@ import i18n from '@/lang'
NProgress.configure({ showSpinner: false })
// pages that do not require authentication
const whitePath = [
'/user/login',
'/user/logout',
'/user/register',
'/api/sso/login',
'/api/sso/logout',
'/user/forgetPassword'
]
// 不用认证的页面
const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/login', '/api/sso/logout', '/user/forgetPassword']
// Only handle user info authentication here, not login logic.
// Frontend permission handling; axios handles 401 -> login.
// Login page handles whether to use SSO.
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
// 登录页面处理处理 是否使用单点登录
router.beforeEach(async (to, from, next) => {
NProgress.start() // start progress bar
to.meta && (!!to.meta.title && setDocumentTitle(`${i18n.t(to.meta.title)} - ${domTitle}`))
const authed = store.state.authed
const auth_type = localStorage.getItem('ops_auth_type')
if (whitePath.includes(to.path)) {
next()
@@ -34,17 +28,17 @@ router.beforeEach(async (to, from, next) => {
store.dispatch('GetAuthDataEnable')
store.dispatch('GetInfo').then(res => {
const roles = res.result && res.result.role
store.dispatch('loadAllUsers')
store.dispatch('loadAllEmployees')
store.dispatch('loadAllDepartments')
store.dispatch("loadAllUsers")
store.dispatch("loadAllEmployees")
store.dispatch("loadAllDepartments")
store.dispatch('GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.appRoutes)
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// Ensure addRoutes is complete, set replace: true so navigation will not leave a history record
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
next({ ...to, replace: true })
} else {
// Redirect to the target route
// 跳转到目的路由
next({ path: redirect })
}
})

View File

@@ -109,29 +109,9 @@ export default {
default: 'default',
tip: 'Tip',
cmdbSearch: 'Search',
requestError: 'An error occurred, please try again later',
requestServiceError: 'Unknown error on the server, please contact the administrator',
requestWait: 'The modification has been submitted, please wait for review ({time} seconds)',
documentCenter: 'Document Center',
exception: {
backToHome: 'Back to home page',
desc1: 'Sorry, you are not authorized to access this page',
desc2: 'Sorry, the page you are visiting does not exist or is still under development',
desc3: 'Sorry, server error'
},
pagination: {
total: '{range0}-{range1} of {total} items'
},
components: {
colorTagSelectTip: 'Enter or select tags',
database: 'Database',
system: 'System',
language: 'Language',
status: 'Status',
commonComponent: 'Common Component',
data: 'Data',
cloud: 'Cloud'
},
topMenu: {
personalCenter: 'My Profile',
logout: 'Logout',

View File

@@ -109,29 +109,9 @@ export default {
default: '默认',
tip: '提示',
cmdbSearch: '搜索一下',
requestError: '出现错误,请稍后再试',
requestServiceError: '服务端未知错误, 请联系管理员!',
requestWait: '修改已提交,请等待审核({time}s',
documentCenter: '文档中心',
exception: {
backToHome: '返回首页',
desc1: '抱歉,你无权访问该页面',
desc2: '抱歉,你访问的页面不存在或仍在开发中',
desc3: '抱歉,服务器出错了'
},
pagination: {
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
},
components: {
colorTagSelectTip: '选择或输入(回车确定)标签',
database: '数据库',
system: '操作系统',
language: '语言',
status: '状态',
commonComponent: '常用组件',
data: '数据',
cloud: '云'
},
topMenu: {
personalCenter: '个人中心',
logout: '退出登录',

View File

@@ -63,6 +63,8 @@ import RouteView from './RouteView'
import MultiTab from '@/components/MultiTab'
import SideMenu from '@/components/Menu/SideMenu'
import GlobalHeader from '@/components/GlobalHeader'
import GlobalFooter from '@/components/GlobalFooter'
import SettingDrawer from '@/components/SettingDrawer'
export default {
name: 'BasicLayout',
@@ -72,6 +74,8 @@ export default {
MultiTab,
SideMenu,
GlobalHeader,
GlobalFooter,
SettingDrawer,
},
data() {
return {

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
import '@babel/polyfill'
import Vue from 'vue'
import App from './App.vue'
@@ -13,9 +14,10 @@ import i18n from './lang'
import iconFont from '../public/iconfont/iconfont'
// 存在直接crash的风险 还未到
const customIcon = Icon.createFromIconfontCN(iconFont)
Vue.component('ops-icon', customIcon)
var vue
var vue;
async function start() {
const _vue = new Vue({

View File

@@ -226,16 +226,16 @@ export default {
right: 4px;
display: none;
&:hover {
color: @primary-color;
color: #1f78d1;
}
}
&:hover .ant-transfer-list-icon {
display: inline;
background-color: @primary-color_4;
background-color: #c0eaff;
border-radius: 4px;
}
}
.ant-transfer-list-content-item-selected {
background-color: ~'@{primary-color_8}35';
background-color: #f0faff;
}
</style>

View File

@@ -63,14 +63,7 @@
show-quick-jumper
:current="tablePage.currentPage"
:total="tablePage.total"
:show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
:page-size="tablePage.pageSize"
:default-current="1"
:page-size-options="pageSizeOptions"
@@ -202,11 +195,11 @@ export default {
background-color: #fff;
height: calc(100vh - 64px);
margin-bottom: -24px;
padding: 20px;
padding: 24px;
.acl-resource-types-header {
width: 100%;
display: inline-flex;
margin-bottom: 20px;
margin-bottom: 15px;
align-items: center;
}
}

View File

@@ -138,14 +138,7 @@
show-quick-jumper
:current="tablePage.currentPage"
:total="tablePage.total"
:show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
:page-size="tablePage.pageSize"
:default-current="1"
:page-size-options="pageSizeOptions"
@@ -365,11 +358,11 @@ export default {
background-color: #fff;
height: calc(100vh - 64px);
margin-bottom: -24px;
padding: 8px 20px 20px 20px;
padding: 12px 24px 24px 24px;
.acl-resources-header {
width: 100%;
display: inline-flex;
margin-bottom: 20px;
margin-bottom: 15px;
align-items: center;
justify-content: space-between;
.ant-switch {

View File

@@ -58,7 +58,10 @@
:title="$t('acl.visualRole')"
:width="120"
align="center"
:filters="visualRoleFilters"
:filters="[
{ label: $t('yes'), value: 1 },
{ label: $t('no'), value: 0 },
]"
:filterMultiple="false"
:filter-method="
({ value, row }) => {
@@ -107,14 +110,7 @@
show-quick-jumper
:current="tablePage.currentPage"
:total="tablePage.total"
:show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`"
:page-size="tablePage.pageSize"
:default-current="1"
:page-size-options="pageSizeOptions"
@@ -159,10 +155,6 @@ export default {
pageSizeOptions: ['20', '50', '100', '200'],
searchName: '',
filterTableValue: { user_role: 1, user_only: 0 },
visualRoleFilters: [
{ label: this.$t('yes'), value: 1 },
{ label: this.$t('no'), value: 0 }
]
}
},
computed: {
@@ -299,11 +291,11 @@ export default {
background-color: #fff;
height: calc(100vh - 64px);
margin-bottom: -24px;
padding: 20px;
padding: 24px;
.acl-roles-header {
width: 100%;
display: inline-flex;
margin-bottom: 20px;
margin-bottom: 15px;
align-items: center;
.ant-checkbox-wrapper {
margin-left: auto;

View File

@@ -326,11 +326,11 @@ export default {
background-color: #fff;
height: calc(100vh - 64px);
margin-bottom: -24px;
padding: 20px;
padding: 24px;
.acl-trigger-header {
width: 100%;
display: inline-flex;
margin-bottom: 20px;
margin-bottom: 15px;
align-items: center;
}
}

View File

@@ -207,13 +207,6 @@ export function deleteTrigger(type_id, id) {
})
}
export function testTrigger(type_id, id) {
return axios({
url: `/v0.1/ci_types/${type_id}/triggers/${id}/test_notify`,
method: 'post',
})
}
// CMDB的模型和实例的授权接口
export function grantCiType(type_id, rid, data) {
return axios({

View File

@@ -84,10 +84,3 @@ export function getDCIMHistoryOperate(params) {
params
})
}
export function calcUnitFreeCount() {
return axios({
url: `/v0.1/dcim/rack/calc_u_free_count`,
method: 'POST'
})
}

View File

@@ -157,18 +157,3 @@ export function preferenceCitypeOrder(data) {
data: data
})
}
export function getAutoSubscription() {
return axios({
url: '/v0.1/preference/auto_subscription',
method: 'get',
})
}
export function putAutoSubscription(data) {
return axios({
url: '/v0.1/preference/auto_subscription',
method: 'put',
data
})
}

View File

@@ -132,7 +132,6 @@ export default {
/deep/ .ant-select-selection {
height: 28px;
line-height: 28px;
border: none;
.ant-select-selection__rendered {
height: 28px;
@@ -188,7 +187,7 @@ export default {
width: 12px;
height: 12px;
background-color: @primary-color;
border: solid 3px @primary-color_4;
border: solid 3px #E2E7FC;
border-radius: 50%;
}

View File

@@ -21,7 +21,7 @@
</template>
<span
class="ci-icon-letter"
v-else-if="title"
v-else
>
<span>
{{ title[0].toUpperCase() }}

View File

@@ -1,156 +1,150 @@
<template>
<div class="ci-type-grant">
<vxe-table
ref="xTable"
size="mini"
stripe
class="ops-stripe-table"
:data="filterTableData"
:max-height="`${tableHeight}px`"
:row-class-name="(params) => getCurrentRowClass(params, addedRids)"
>
<vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}">
<ReadCheckbox
v-if="['read'].includes(col.split('_')[0])"
:value="row[col.split('_')[0]]"
:valueKey="col"
:rid="row.rid"
@openReadGrantModal="() => openReadGrantModal(col, row)"
/>
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
</template>
</vxe-column>
<template #empty>
<div v-if="loading()" class="ci-type-grant-loading">
<a-icon type="loading" /> {{ $t('loading') }}
</div>
<div v-else>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</template>
</vxe-table>
<a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space>
</div>
</template>
<script>
import _ from 'lodash'
import { permMap } from './constants.js'
import { grantCiType, revokeCiType } from '../../api/CIType'
import ReadCheckbox from './readCheckbox.vue'
import { getCurrentRowClass } from './utils'
export default {
name: 'CiTypeGrant',
components: { ReadCheckbox },
inject: ['loading', 'isModal'],
props: {
CITypeId: {
type: Number,
default: null,
},
tableData: {
type: Array,
default: () => [],
},
grantType: {
type: String,
default: 'ci_type',
},
addedRids: {
type: Array,
default: () => [],
},
},
computed: {
filterTableData() {
const _tableData = this.tableData.filter((data) => {
const _intersection = _.intersection(
Object.keys(data),
this.columns.map((col) => col.split('_')[0])
)
return _intersection && _intersection.length
})
return _.uniqBy(_tableData, (item) => item.rid)
},
columns() {
if (this.grantType === 'ci_type') {
return ['config', 'grant']
}
return ['read_attr', 'read_ci', 'create', 'update', 'delete']
},
windowHeight() {
return this.$store.state.windowHeight
},
tableHeight() {
if (this.isModal) {
return (this.windowHeight - 104) / 2
}
return (this.windowHeight - 104) / 2 - 116
},
permMap() {
return permMap()
}
},
methods: {
getCurrentRowClass,
async handleChange(e, col, row) {
if (e.target.checked) {
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData')
})
} else {
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData')
})
}
},
grantDepart() {
this.$emit('grantDepart', this.grantType)
},
grantRole() {
this.$emit('grantRole', this.grantType)
},
openReadGrantModal(col, row) {
this.$emit('openReadGrantModal', col, row)
},
clickGrant(col, row, rowIndex) {
if (!row[col]) {
this.handleChange({ target: { checked: true } }, col, row)
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
} else {
const that = this
this.$confirm({
title: that.$t('warning'),
content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
onOk() {
that.handleChange({ target: { checked: false } }, col, row)
const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
},
})
}
},
},
}
</script>
<style lang="less" scoped>
.ci-type-grant {
padding: 10px 0;
&-loading {
height: 200px;
line-height: 200px;
color: @primary-color;
}
}
</style>
<template>
<div class="ci-type-grant">
<vxe-table
ref="xTable"
size="mini"
stripe
class="ops-stripe-table"
:data="filterTableData"
:max-height="`${tableHeight}px`"
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
>
<vxe-column field="name"></vxe-column>
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
<template #default="{row}">
<ReadCheckbox
v-if="['read'].includes(col.split('_')[0])"
:value="row[col.split('_')[0]]"
:valueKey="col"
:rid="row.rid"
@openReadGrantModal="() => openReadGrantModal(col, row)"
/>
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
</template>
</vxe-column>
<template #empty>
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
<a-icon type="loading" /> {{ $t('loading') }}
</div>
<div v-else>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</template>
</vxe-table>
<a-space>
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space>
</div>
</template>
<script>
import _ from 'lodash'
import { permMap } from './constants.js'
import { grantCiType, revokeCiType } from '../../api/CIType'
import ReadCheckbox from './readCheckbox.vue'
import { getCurrentRowStyle } from './utils'
export default {
name: 'CiTypeGrant',
components: { ReadCheckbox },
inject: ['loading', 'isModal'],
props: {
CITypeId: {
type: Number,
default: null,
},
tableData: {
type: Array,
default: () => [],
},
grantType: {
type: String,
default: 'ci_type',
},
addedRids: {
type: Array,
default: () => [],
},
},
computed: {
filterTableData() {
const _tableData = this.tableData.filter((data) => {
const _intersection = _.intersection(
Object.keys(data),
this.columns.map((col) => col.split('_')[0])
)
return _intersection && _intersection.length
})
return _.uniqBy(_tableData, (item) => item.rid)
},
columns() {
if (this.grantType === 'ci_type') {
return ['config', 'grant']
}
return ['read_attr', 'read_ci', 'create', 'update', 'delete']
},
windowHeight() {
return this.$store.state.windowHeight
},
tableHeight() {
if (this.isModal) {
return (this.windowHeight - 104) / 2
}
return (this.windowHeight - 104) / 2 - 116
},
permMap() {
return permMap()
}
},
methods: {
getCurrentRowStyle,
async handleChange(e, col, row) {
if (e.target.checked) {
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData')
})
} else {
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
this.$emit('getTableData')
})
}
},
grantDepart() {
this.$emit('grantDepart', this.grantType)
},
grantRole() {
this.$emit('grantRole', this.grantType)
},
openReadGrantModal(col, row) {
this.$emit('openReadGrantModal', col, row)
},
clickGrant(col, row, rowIndex) {
if (!row[col]) {
this.handleChange({ target: { checked: true } }, col, row)
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
} else {
const that = this
this.$confirm({
title: that.$t('warning'),
content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
onOk() {
that.handleChange({ target: { checked: false } }, col, row)
const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
},
})
}
},
},
}
</script>
<style lang="less" scoped>
.ci-type-grant {
padding: 10px 0;
}
</style>

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