mirror of https://github.com/veops/cmdb.git
Compare commits
607 Commits
Author | SHA1 | Date |
---|---|---|
|
251b9e7fd5 | |
|
f3cc12f1f9 | |
|
56f03e1624 | |
|
42ad2b6dde | |
|
5aba1ff257 | |
|
417e8fe349 | |
|
02235d8cc0 | |
|
00c7a644a2 | |
|
f3e8757450 | |
|
f0749341ba | |
|
89da671e46 | |
|
0e60aae076 | |
|
4dfa97d404 | |
|
9b778f9bc7 | |
|
eafb5f053a | |
|
834054e216 | |
|
a97cabbedc | |
|
ae77852d5f | |
|
611ee40dca | |
|
c0d55b2126 | |
|
2cc4499ef9 | |
|
1268404bca | |
|
570a9203c4 | |
|
adae7b5519 | |
|
8a91ec7b11 | |
|
92fca65383 | |
|
4b8e6c2841 | |
|
ab240cb003 | |
|
61e62e4740 | |
|
1fd72d6c78 | |
|
51e16f6b23 | |
|
037378e384 | |
|
631871a8cf | |
|
6e02f6a21f | |
|
a2224ba2ac | |
|
11a289aac9 | |
|
55ab04dd28 | |
|
256a4f4844 | |
|
018a349336 | |
|
8f62227adb | |
|
de51cb3e21 | |
|
ecb069cf14 | |
|
937cb84393 | |
|
40a4db06b5 | |
|
cc98f903ea | |
|
fb7471ce04 | |
|
e2872f041e | |
|
250fde127c | |
|
73dbb14944 | |
|
73c9a6fa72 | |
|
09d957db79 | |
|
b73d796891 | |
|
e7cbd0caa9 | |
|
3e4c385d91 | |
|
3aac012ee9 | |
|
78d762cacc | |
|
c668ba7d3f | |
|
542a876ead | |
|
68b7497bba | |
|
dfbf3d462d | |
|
692708fcba | |
|
330b64edb3 | |
|
63a3074cb7 | |
|
31b8cf49dc | |
|
b01c335456 | |
|
002fef09e2 | |
|
175778a162 | |
|
5050a1bef5 | |
|
46a6cf67d6 | |
|
4e857c2775 | |
|
835df1bdeb | |
|
579339d13c | |
|
629967ce82 | |
|
3a00bfd236 | |
|
2e97ebd895 | |
|
eb6a813cbc | |
|
ff78face48 | |
|
d55433c438 | |
|
daf0254616 | |
|
6b32009955 | |
|
d53288c1fb | |
|
586d820a08 | |
|
6776be4599 | |
|
ff2b8ea198 | |
|
ed46a1e1c1 | |
|
0dc614fb46 | |
|
bc66d33ce0 | |
|
d5db68d7d0 | |
|
b22b8b286b | |
|
dd4f3b0e9c | |
|
688f4e0ea4 | |
|
c1813f525d | |
|
b405e28498 | |
|
fa32758462 | |
|
29995b660a | |
|
b96fc06a62 | |
|
c7f30b63ff | |
|
5bff69a8a8 | |
|
d5e60fab88 | |
|
190f452118 | |
|
98a4824364 | |
|
c0f9baea79 | |
|
d4b661c77f | |
|
75cd7bde77 | |
|
ec912d3a65 | |
|
42f02b4986 | |
|
a13b999820 | |
|
5f53b0dd0e | |
|
df22085ff9 | |
|
06148b402d | |
|
3fe020505a | |
|
b34e83124f | |
|
cdc52d3f80 | |
|
b3a80d5678 | |
|
a2e3061bba | |
|
a8eb5126ea | |
|
adac2129fc | |
|
e660c901ce | |
|
ff002c0a1e | |
|
88593d6da7 | |
|
6fa0dd5bc5 | |
|
3200942373 | |
|
4fd705cc59 | |
|
74827ce187 | |
|
4ed1eb6062 | |
|
7792204658 | |
|
8621108906 | |
|
6437af19b9 | |
|
735ddb334c | |
|
4a8032202e | |
|
c7acea6422 | |
|
ac4c93de8e | |
|
8d044cf935 | |
|
54747fa789 | |
|
545f1bb30b | |
|
dc77bca17c | |
|
4973278c5a | |
|
d1c9361e47 | |
|
28c57cacd9 | |
|
711dcc4bd7 | |
|
491d3cce00 | |
|
27354a3927 | |
|
78495eb976 | |
|
ae900c7d3b | |
|
50134e6a0b | |
|
65ef58dea9 | |
|
0a2e7aa99f | |
|
8875e75883 | |
|
2f03639c57 | |
|
49bc5d94a9 | |
|
39354e1293 | |
|
d3714f3ecf | |
|
729a616282 | |
|
2d3a290aa3 | |
|
9e885a5b12 | |
|
f5822d7cba | |
|
21ea553e74 | |
|
e63038d1b6 | |
|
d56806f511 | |
|
7ac7fdc08e | |
|
ba11707146 | |
|
d49dc8a067 | |
|
6bfb34fe2a | |
|
2c7ed8c32d | |
|
5b275af54e | |
|
dde7ec6246 | |
|
9181817e96 | |
|
46b54bb7f2 | |
|
fe63310c4e | |
|
27c733aa2c | |
|
2a8e9e684e | |
|
095190a785 | |
|
ef25c94b5d | |
|
06ae1bcf13 | |
|
9ead4e7d8d | |
|
994a28dd25 | |
|
74b587e46c | |
|
091cd882bd | |
|
73093db467 | |
|
66e268ce68 | |
|
a41d1a5e97 | |
|
b4b728fe28 | |
|
d16462d8b7 | |
|
de7d98c0b4 | |
|
51332c7236 | |
|
bf1076fe4a | |
|
3454a98cfb | |
|
506dcbb40e | |
|
5ac4517187 | |
|
761e98884b | |
|
073654624e | |
|
df54244ff1 | |
|
27e9919198 | |
|
dc8b1a5de2 | |
|
d8a7728f1d | |
|
82881965fb | |
|
1bb62022f1 | |
|
ed445a8d82 | |
|
3626b1a97e | |
|
32529fba9b | |
|
a042b4fe39 | |
|
a0631414dc | |
|
5266cb5b88 | |
|
c7d4bec988 | |
|
099ddd6ca9 | |
|
bd813174b1 | |
|
0a43680d6e | |
|
976c6cfe91 | |
|
cf594f04ba | |
|
4232094aed | |
|
d08827d086 | |
|
d25ae532cd | |
|
9fbb6ee64d | |
|
b62f0e96fd | |
|
c1bcd0ce45 | |
|
c8b55c34eb | |
|
4b5906770f | |
|
4188ac7252 | |
|
2efbc6474a | |
|
03eac0c4d2 | |
|
2a861250eb | |
|
8fc19d8b7c | |
|
430d2ff6d0 | |
|
2517009d70 | |
|
67da360d80 | |
|
24c56fb259 | |
|
37d5da65de | |
|
2224ebd533 | |
|
bf6331d215 | |
|
b18b90ab4e | |
|
702e17a7a4 | |
|
a7586aa140 | |
|
ad3f96431c | |
|
1515820713 | |
|
7728b57878 | |
|
a419eefd72 | |
|
a44e5f6cf1 | |
|
7d46e92c2d | |
|
4117cf87ec | |
|
9e0fe0b818 | |
|
2a8f1ab9a4 | |
|
c0fe99b8c7 | |
|
42feb4b862 | |
|
482d34993b | |
|
7ff309b8b8 | |
|
98eb47d44f | |
|
9ab0f624ef | |
|
3f3eda8b3c | |
|
f788adc8cf | |
|
693ae4ff05 | |
|
a1a9d99eb4 | |
|
e045e0fb43 | |
|
09376dbd2b | |
|
7fda5a1e7b | |
|
113b84763f | |
|
190170acad | |
|
513d2af4b8 | |
|
4588bd8996 | |
|
082da5fade | |
|
013b116eb5 | |
|
208d29165b | |
|
d510330cde | |
|
ea4f0fc2a5 | |
|
9bcdaacdc4 | |
|
5045581ddf | |
|
232913172c | |
|
157e1809ed | |
|
ab8ccf7d1b | |
|
ae1f0f6b4f | |
|
ff47e0ade6 | |
|
0dd272fb04 | |
|
6bc5c1516d | |
|
4a4b9e6ef0 | |
|
6e3f9478b3 | |
|
691051c254 | |
|
8f066e95a6 | |
|
75bca39bf6 | |
|
3360e4d0fe | |
|
521fcd0ba2 | |
|
fc113425cb | |
|
81a76a9632 | |
|
9ec105ca37 | |
|
1e1c92a3ef | |
|
0b3cad8215 | |
|
ed41a72900 | |
|
27854d4e0a | |
|
0372459f9d | |
|
f56378e2b5 | |
|
00b276d5e7 | |
|
3faefbe8d3 | |
|
4261f6fb57 | |
|
c98199e98e | |
|
82ea1ddc79 | |
|
995e581315 | |
|
ec8f626b8f | |
|
ec884c92e1 | |
|
9ee2776bdd | |
|
7186bdac9c | |
|
0a9964375a | |
|
bff45d00b6 | |
|
e429ad59ff | |
|
ace160ae19 | |
|
7036fe023c | |
|
341f5dba53 | |
|
5883c1616e | |
|
bee0a3a3e5 | |
|
7a79a8bbf7 | |
|
100a889cb8 | |
|
b093569453 | |
|
3919dfdfbb | |
|
ef85ba2542 | |
|
3d5c2ec5bc | |
|
c143d6ae5b | |
|
10b273ee81 | |
|
855cb91b31 | |
|
ffae57642c | |
|
9ed1108c20 | |
|
72b2f8b6de | |
|
20f3e917fe | |
|
c430515377 | |
|
a6e2aca281 | |
|
18313b7bd1 | |
|
dbc44a8ad6 | |
|
f143e30cf5 | |
|
4beece5a6e | |
|
920295d955 | |
|
6eb8ae1dac | |
|
f1f86ce25a | |
|
090007487d | |
|
aa98a304c1 | |
|
fe22e363b4 | |
|
2d2fb6e1d6 | |
|
9894c77300 | |
|
4ea947f741 | |
|
e5ab2c2573 | |
|
5581fa8d0f | |
|
76c939fe5c | |
|
6e9ce08e2c | |
|
092a8b9b92 | |
|
b45fd0cbbb | |
|
5320ecfd62 | |
|
1d253d7ad3 | |
|
73d53f0440 | |
|
d4a37af183 | |
|
e03849b054 | |
|
faed3fe6a2 | |
|
21c9d9accd | |
|
a06599ce33 | |
|
2b69217136 | |
|
03a3b8b169 | |
|
cd319421d5 | |
|
c918d54ea5 | |
|
cf0ad7bad6 | |
|
e0c8263542 | |
|
275e8b15f3 | |
|
6ff942c107 | |
|
d3c87ee500 | |
|
a4f65e7fc6 | |
|
10527bf9b8 | |
|
0414121c27 | |
|
edde467c87 | |
|
d525e1ec54 | |
|
91c49b690f | |
|
05453becf9 | |
|
5fe27f8678 | |
|
0924b8846f | |
|
62a669159a | |
|
2933bf1efa | |
|
981f8b0145 | |
|
213bda671c | |
|
837aabfe77 | |
|
cc599d414a | |
|
01fcd7f80e | |
|
af254ddbeb | |
|
422e89c6c6 | |
|
c2c11b38ed | |
|
45d3f57228 | |
|
c711c3d3fd | |
|
2ae4aeee67 | |
|
46238b8b51 | |
|
b1528ec511 | |
|
8ef4edb7d2 | |
|
0172206c7c | |
|
9d5bdf1b0d | |
|
c0726b228d | |
|
5b314aa907 | |
|
c9f0de9838 | |
|
2db41dd992 | |
|
89e492c1f3 | |
|
1aeb9a2702 | |
|
b090a88b76 | |
|
d5c479f7e5 | |
|
b342258e75 | |
|
3d716eff3e | |
|
9791a184e3 | |
|
142b8c95c5 | |
|
363b89011f | |
|
321372fa88 | |
|
fc27dc6e64 | |
|
8ad98f4ba4 | |
|
9b050fa7fa | |
|
a30826827a | |
|
691b50aec4 | |
|
55ca2d5eb5 | |
|
779cd1ea9e | |
|
7d53180a2f | |
|
b6c41c00dd | |
|
fff5679f6e | |
|
7556dfe56b | |
|
44b6f2b2ad | |
|
f5607d96f3 | |
|
2e85a9971b | |
|
8d177266dc | |
|
42d870ea4e | |
|
424cad2130 | |
|
430308a679 | |
|
cc7570a4b2 | |
|
413c0dfdfe | |
|
f8fee771c4 | |
|
3d05cef7cd | |
|
30477f736e | |
|
0cd5c0277b | |
|
4fcddd1010 | |
|
b269ef894f | |
|
ea13432e4c | |
|
c8a68bd185 | |
|
f9ee895f58 | |
|
59d9f2c79a | |
|
f629558d60 | |
|
4cbf70e756 | |
|
40b9bec122 | |
|
17b27d492a | |
|
dd8f66a3fa | |
|
d501436e3d | |
|
8c6389b4f8 | |
|
63ed73fa14 | |
|
ef90506916 | |
|
c0541b7e50 | |
|
388134b38c | |
|
f57d640c5d | |
|
42c506ded8 | |
|
373346e71e | |
|
983cb4041c | |
|
718fca57d2 | |
|
2b1b0333ff | |
|
9bb787d3f4 | |
|
9f5979c1b1 | |
|
b58230fd56 | |
|
8e6ce05c04 | |
|
b8d93bc9eb | |
|
968ef93153 | |
|
e0a0113e69 | |
|
7181f2879a | |
|
ff1626ff07 | |
|
dca8781a03 | |
|
43c5d74661 | |
|
b1d0bdb536 | |
|
2420631ed5 | |
|
deabebb922 | |
|
cd451060e3 | |
|
82d4c4f961 | |
|
79f5dac661 | |
|
1a7c3fcd21 | |
|
606be7f8e8 | |
|
7f66f7688b | |
|
25ad2192fd | |
|
881b8cc1fa | |
|
68905281f2 | |
|
3cb78f30d2 | |
|
ebce839eaa | |
|
3170048ea9 | |
|
976cac6742 | |
|
99e5a932ae | |
|
058585504f | |
|
6d50a13cf0 | |
|
198ecad13a | |
|
1660139b27 | |
|
0155eb98a2 | |
|
c0c05bca86 | |
|
32227d375a | |
|
eb9c20a295 | |
|
b826de4195 | |
|
c3b55c2850 | |
|
33313b6206 | |
|
00fbe15a19 | |
|
9e5c926bfd | |
|
f0467c3d3b | |
|
6758b994f8 | |
|
d8646f8e7c | |
|
0fa95aed36 | |
|
d82356444a | |
|
b8fa68ec8d | |
|
78c542e71d | |
|
6594863934 | |
|
61e8c61fc6 | |
|
ec9b5a0fa0 | |
|
3664994cc5 | |
|
366311e59b | |
|
03f2ff912d | |
|
ff9af51465 | |
|
04fc588935 | |
|
f215e887c9 | |
|
3e6955fc7a | |
|
b1cfb88308 | |
|
d1af0eba79 | |
|
f41873dc8c | |
|
63562007df | |
|
a99ecb9ea5 | |
|
08f9bd9071 | |
|
17c851f354 | |
|
3382195a25 | |
|
cd70b16eb3 | |
|
2a98c00d97 | |
|
5044af5490 | |
|
320dc07676 | |
|
95d3b0233a | |
|
093466ef06 | |
|
ceacc0ab15 | |
|
cb3a9d9432 | |
|
23fd56cf28 | |
|
dd1c783919 | |
|
8296e9a552 | |
|
590565bdf0 | |
|
9e2bf3d1f0 | |
|
7547b67805 | |
|
4abe4d7e8f | |
|
0ebd52f3fd | |
|
4cc2e47f02 | |
|
b16bfdfa03 | |
|
ece24080d5 | |
|
a0ea8a193b | |
|
d8e09d449e | |
|
44c99e0f2e | |
|
7110b4bcb3 | |
|
3989859945 | |
|
29ed4dd708 | |
|
dbcbe33ba0 | |
|
ed340a1c33 | |
|
17ee7622b4 | |
|
51877778bf | |
|
1de8b492ea | |
|
1fe38c4ec3 | |
|
92bff08b84 | |
|
37d0dacd2e | |
|
e22f45fd25 | |
|
56ef0a055a | |
|
522991e23b | |
|
ff061d4d2e | |
|
5b84a704b1 | |
|
3ba83821f2 | |
|
45b82aa52d | |
|
c99790bda2 | |
|
7272880062 | |
|
8130dd92ab | |
|
9ca2d38307 | |
|
e61bbdbcb7 | |
|
88960c3082 | |
|
0aa433882e | |
|
d4f5713e0a | |
|
790204ea28 | |
|
927ed393d3 | |
|
bf92b89a5f | |
|
7ddaa143da | |
|
157361cc7a | |
|
dcf768376a | |
|
e339ad2c23 | |
|
8b10c5c72d | |
|
bd13f6b744 | |
|
934d00e87d | |
|
9d421993a0 | |
|
6751f673d5 | |
|
c420a2195a | |
|
ec3a6e0b6e | |
|
ab0a5399f7 | |
|
33e63c762f | |
|
53b2a228ae | |
|
c687e7ad9b | |
|
ea3cdbd5f5 | |
|
1ebe74d966 | |
|
1f935d25a2 | |
|
14e4dbacac | |
|
f7a9257e16 | |
|
99562df6cd | |
|
630013cb85 | |
|
6351d3af64 | |
|
2d96048828 | |
|
3e67bb94bc | |
|
a26c9ff542 | |
|
252003f76d | |
|
928149f2a0 | |
|
0283af812c | |
|
6ccb2e74e0 | |
|
5016464016 | |
|
b787bd9b7b | |
|
11f69d8679 | |
|
c3168035ed | |
|
5140c0a58f | |
|
ee97582579 | |
|
83e9172722 | |
|
341e687987 | |
|
93d9804127 | |
|
f3d42cb356 | |
|
04b9a3c929 | |
|
e98160b84a | |
|
7c769a53da | |
|
08dc343083 | |
|
52e654ff60 |
|
@ -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
195
README.md
|
@ -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 加关注,第一时间获取更新动态!
|
||||
|
||||

|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 搭建
|
||||
|
||||
+ 方案一:Docker 一键快速构建
|
||||
|
||||
- 第1步: 安装 Docker 环境和 Docker Compose(v2)
|
||||
- 第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 (v2))
|
||||
|
||||
- <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>
|
||||
|
|
|
@ -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
|
|
@ -11,7 +11,7 @@ click = ">=5.0"
|
|||
# Api
|
||||
Flask-RESTful = "==0.3.10"
|
||||
# Database
|
||||
Flask-SQLAlchemy = "==3.0.5"
|
||||
Flask-SQLAlchemy = "==2.5.0"
|
||||
SQLAlchemy = "==1.4.49"
|
||||
PyMySQL = "==1.1.0"
|
||||
redis = "==4.6.0"
|
||||
|
@ -68,8 +68,6 @@ pycryptodomex = ">=3.19.0"
|
|||
lz4 = ">=4.3.2"
|
||||
python-magic = "==0.4.27"
|
||||
jsonpath = "==0.82.2"
|
||||
networkx = ">=3.1"
|
||||
ipaddress = ">=1.0.23"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import click
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import click
|
||||
import requests
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from flask_login import login_user
|
||||
|
@ -23,7 +24,6 @@ from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
|||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.dcim.rack import RackManager
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import UserCache
|
||||
|
@ -37,14 +37,11 @@ from api.lib.secrets.secrets import InnerKVManger
|
|||
from api.models.acl import App
|
||||
from api.models.acl import ResourceType
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import OperationRecord
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.tasks.cmdb import batch_ci_cache
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@ -196,7 +193,7 @@ def cmdb_counter():
|
|||
today = datetime.date.today()
|
||||
while True:
|
||||
try:
|
||||
db.session.commit()
|
||||
db.session.remove()
|
||||
|
||||
CMDBCounterCache.reset()
|
||||
|
||||
|
@ -210,8 +207,6 @@ def cmdb_counter():
|
|||
|
||||
CMDBCounterCache.flush_sub_counter()
|
||||
|
||||
RackManager().check_u_slot()
|
||||
|
||||
i += 1
|
||||
except:
|
||||
import traceback
|
||||
|
@ -228,12 +223,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 +251,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 +341,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})
|
||||
|
@ -568,20 +557,5 @@ def cmdb_patch(version):
|
|||
existed.update(option=option, commit=False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
if version >= "2.4.14": # update ci columns: updated_at and updated_by
|
||||
ci_ids = []
|
||||
for i in CI.get_by(only_query=True).filter(CI.updated_at.is_(None)):
|
||||
hist = AttributeHistory.get_by(ci_id=i.id, only_query=True).order_by(AttributeHistory.id.desc()).first()
|
||||
if hist is not None:
|
||||
record = OperationRecord.get_by_id(hist.record_id)
|
||||
if record is not None:
|
||||
u = UserCache.get(record.uid)
|
||||
i.update(updated_at=record.created_at, updated_by=u and u.nickname, flush=True)
|
||||
ci_ids.append(i.id)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
batch_ci_cache.apply_async(args=(ci_ids,))
|
||||
except Exception as e:
|
||||
print("cmdb patch failed: {}".format(e))
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -512,7 +512,7 @@ class CMDBCounterCache(object):
|
|||
result[i.type_id]['rule_count'] = len(adts) + AutoDiscoveryCITypeRelation.get_by(
|
||||
ad_type_id=i.type_id, only_query=True).count()
|
||||
result[i.type_id]['exec_target_count'] = len(
|
||||
set([j.oneagent_id for adt in adts for j in db.session.query(
|
||||
set([i.oneagent_id for adt in adts for i in db.session.query(
|
||||
AutoDiscoveryRuleSyncHistory.oneagent_id).filter(
|
||||
AutoDiscoveryRuleSyncHistory.adt_id == adt.id)]))
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ from api.lib.notify import notify_send
|
|||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.secrets.inner import InnerCrypt
|
||||
from api.lib.secrets.vault import VaultClient
|
||||
from api.lib.utils import handle_arg_list
|
||||
|
@ -114,8 +113,7 @@ class CIManager(object):
|
|||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
ci_list = cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key)
|
||||
ci_list and res.update(ci_list[0])
|
||||
res.update(cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key))
|
||||
|
||||
res['_type'] = ci_type.id
|
||||
res['_id'] = ci_id
|
||||
|
@ -208,8 +206,6 @@ class CIManager(object):
|
|||
res['_type'] = ci_type.id
|
||||
res['ci_type_alias'] = ci_type.alias
|
||||
res['_id'] = ci_id
|
||||
res['_updated_at'] = str(ci.updated_at or '')
|
||||
res['_updated_by'] = ci.updated_by
|
||||
|
||||
return res
|
||||
|
||||
|
@ -295,7 +291,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:
|
||||
|
@ -357,7 +353,6 @@ class CIManager(object):
|
|||
is_auto_discovery=False,
|
||||
_is_admin=False,
|
||||
ticket_id=None,
|
||||
_sync=False,
|
||||
**ci_dict):
|
||||
"""
|
||||
add ci
|
||||
|
@ -367,7 +362,6 @@ class CIManager(object):
|
|||
:param is_auto_discovery: default is False
|
||||
:param _is_admin: default is False
|
||||
:param ticket_id:
|
||||
:param _sync:
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
|
@ -393,7 +387,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
|
||||
|
@ -498,16 +492,10 @@ class CIManager(object):
|
|||
record_id = cls.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci_type.id)
|
||||
|
||||
if record_id or has_dynamic: # has changed
|
||||
if not _sync:
|
||||
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_cache(ci.id, operate_type, record_id)
|
||||
ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
|
||||
|
||||
if ref_ci_dict: # add relations
|
||||
if not _sync:
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
||||
else:
|
||||
ci_relation_add(ref_ci_dict, ci.id, current_user.uid)
|
||||
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
return ci.id
|
||||
|
||||
|
@ -524,14 +512,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()
|
||||
|
@ -554,15 +538,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)
|
||||
|
@ -585,9 +568,6 @@ class CIManager(object):
|
|||
for attr_id in password_dict:
|
||||
record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
|
||||
|
||||
u = UserCache.get(current_user.uid)
|
||||
ci.update(updated_at=now, updated_by=u and u.nickname)
|
||||
|
||||
if record_id or has_dynamic: # has changed
|
||||
if not _sync:
|
||||
ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
|
||||
|
@ -601,9 +581,6 @@ class CIManager(object):
|
|||
else:
|
||||
ci_relation_add(ref_ci_dict, ci.id)
|
||||
|
||||
u = UserCache.get(current_user.uid)
|
||||
ci.update(updated_at=now, updated_by=u and u.nickname)
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
@ -732,18 +709,13 @@ class CIManager(object):
|
|||
elif fields:
|
||||
_res = []
|
||||
for d in res:
|
||||
if isinstance(fields, dict) and d.get("_type") not in fields:
|
||||
_res.append(d)
|
||||
continue
|
||||
|
||||
_d = dict()
|
||||
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
||||
_d["ci_type"] = d.get("ci_type")
|
||||
if unique_required:
|
||||
_d[d.get('unique')] = d.get(d.get('unique'))
|
||||
|
||||
_fields = list(fields.get(_d['_type']) or [] if isinstance(fields, dict) else fields)
|
||||
for field in _fields + ['ci_type_alias', 'unique', 'unique_alias']:
|
||||
for field in fields + ['ci_type_alias', 'unique', 'unique_alias']:
|
||||
_d[field] = d.get(field)
|
||||
_res.append(_d)
|
||||
return _res
|
||||
|
@ -760,8 +732,9 @@ class CIManager(object):
|
|||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_IDS
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||
|
||||
filter_fields_sql = ""
|
||||
if fields and isinstance(fields, list):
|
||||
if not fields:
|
||||
filter_fields_sql = ""
|
||||
else:
|
||||
_fields = list()
|
||||
for field in fields:
|
||||
attr = AttributeCache.get(field)
|
||||
|
@ -803,10 +776,6 @@ class CIManager(object):
|
|||
ci_set.add(ci_id)
|
||||
res[ci2pos[ci_id]] = ci_dict
|
||||
|
||||
if isinstance(fields, dict) and fields.get(type_id):
|
||||
if attr_name not in fields[type_id]:
|
||||
continue
|
||||
|
||||
if ret_key == RetKey.NAME:
|
||||
attr_key = attr_name
|
||||
elif ret_key == RetKey.ALIAS:
|
||||
|
@ -844,7 +813,7 @@ class CIManager(object):
|
|||
if not ci_ids:
|
||||
return []
|
||||
|
||||
fields = [] if not fields else fields
|
||||
fields = [] if fields is None or not isinstance(fields, list) else fields
|
||||
|
||||
ci_id_tuple = tuple(map(int, ci_ids))
|
||||
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields, unique_required, excludes=excludes)
|
||||
|
@ -1273,9 +1242,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)
|
||||
|
||||
|
@ -1296,10 +1263,10 @@ class CIRelationManager(object):
|
|||
return existed.id
|
||||
|
||||
@staticmethod
|
||||
def delete(cr_id, apply_async=True, valid=True):
|
||||
def delete(cr_id, apply_async=True):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker' and valid:
|
||||
if current_app.config.get('USE_ACL') and current_user.username != 'worker':
|
||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
|
@ -1338,7 +1305,7 @@ class CIRelationManager(object):
|
|||
return cr
|
||||
|
||||
@classmethod
|
||||
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True, valid=True):
|
||||
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
|
@ -1348,7 +1315,7 @@ class CIRelationManager(object):
|
|||
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
|
||||
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
|
||||
|
||||
cls.delete(cr.id, apply_async=apply_async, valid=valid)
|
||||
cls.delete(cr.id, apply_async=apply_async)
|
||||
|
||||
return cr
|
||||
|
||||
|
@ -1541,8 +1508,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():
|
||||
|
@ -1560,20 +1526,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():
|
||||
|
@ -1597,14 +1562,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
|
||||
|
||||
|
@ -1681,47 +1645,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)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
import networkx as nx
|
||||
import toposort
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
@ -17,14 +16,12 @@ from api.lib.cmdb.cache import AttributeCache
|
|||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import SysComputedAttributes
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
|
@ -66,7 +63,6 @@ class CITypeManager(object):
|
|||
"""
|
||||
manage CIType
|
||||
"""
|
||||
|
||||
cls = CIType
|
||||
|
||||
def __init__(self):
|
||||
|
@ -189,9 +185,6 @@ class CITypeManager(object):
|
|||
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
|
||||
if ci_type.name in BuiltinModelEnum.all() and kwargs.get('name', ci_type.name) != ci_type.name:
|
||||
return abort(400, ErrFormat.builtin_type_cannot_update_name)
|
||||
|
||||
cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
|
||||
# cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
|
||||
|
||||
|
@ -421,10 +414,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 +712,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 +730,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 []):
|
||||
|
@ -855,35 +845,10 @@ class CITypeRelationManager(object):
|
|||
|
||||
return ids
|
||||
|
||||
@staticmethod
|
||||
def find_path(source_type_id, target_type_ids):
|
||||
source_type_id = int(source_type_id)
|
||||
target_type_ids = map(int, target_type_ids)
|
||||
|
||||
graph = nx.DiGraph()
|
||||
|
||||
def get_children(_id, _graph):
|
||||
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)
|
||||
|
||||
get_children(source_type_id, graph)
|
||||
|
||||
paths = list(nx.all_simple_paths(graph, source_type_id, target_type_ids))
|
||||
|
||||
del graph
|
||||
|
||||
return paths
|
||||
|
||||
@staticmethod
|
||||
def _wrap_relation_type_dict(type_id, relation_inst):
|
||||
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
||||
ci_type_dict["ctr_id"] = relation_inst.id
|
||||
show_key = AttributeCache.get(ci_type_dict.get('show_id') or ci_type_dict['unique_id'])
|
||||
ci_type_dict["show_key"] = show_key and show_key.name
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
if attr_filter:
|
||||
|
@ -1106,7 +1071,6 @@ class CITypeAttributeGroupManager(object):
|
|||
|
||||
@staticmethod
|
||||
def get_by_type_id(type_id, need_other=False):
|
||||
_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found)
|
||||
parent_ids = CITypeInheritanceManager.base(type_id)
|
||||
|
||||
groups = []
|
||||
|
@ -1145,24 +1109,17 @@ 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])
|
||||
|
||||
if (_type.name in SysComputedAttributes.type2attr and
|
||||
attr['name'] in SysComputedAttributes.type2attr[_type.name]):
|
||||
attr['sys_computed'] = True
|
||||
else:
|
||||
attr['sys_computed'] = False
|
||||
|
||||
attr2pos[i.attr_id] = [group_pos, attr]
|
||||
|
||||
group.pop('inherited_from', None)
|
||||
|
@ -1557,10 +1514,7 @@ class CITypeTemplateManager(object):
|
|||
if existed is None:
|
||||
_group['type_id'] = type_id_map.get(_group['type_id'], _group['type_id'])
|
||||
|
||||
try:
|
||||
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
||||
except:
|
||||
continue
|
||||
existed = CITypeAttributeGroup.create(flush=True, **_group)
|
||||
|
||||
for order, attr in enumerate(group['attributes'] or []):
|
||||
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from api.lib.utils import BaseEnum
|
||||
|
||||
|
||||
|
@ -112,47 +110,17 @@ class ExecuteStatusEnum(BaseEnum):
|
|||
FAILED = '1'
|
||||
RUNNING = '2'
|
||||
|
||||
|
||||
class RelationSourceEnum(BaseEnum):
|
||||
ATTRIBUTE_VALUES = "0"
|
||||
AUTO_DISCOVERY = "1"
|
||||
|
||||
|
||||
class BuiltinModelEnum(BaseEnum):
|
||||
IPAM_SUBNET = "ipam_subnet"
|
||||
IPAM_ADDRESS = "ipam_address"
|
||||
IPAM_SCOPE = "ipam_scope"
|
||||
|
||||
DCIM_REGION = "dcim_region"
|
||||
DCIM_IDC = "dcim_idc"
|
||||
DCIM_SERVER_ROOM = "dcim_server_room"
|
||||
DCIM_RACK = "dcim_rack"
|
||||
|
||||
|
||||
BUILTIN_ATTRIBUTES = {
|
||||
"_updated_at": _l("Update Time"),
|
||||
"_updated_by": _l("Updated By"),
|
||||
}
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id', *BUILTIN_ATTRIBUTES.keys()}
|
||||
|
||||
|
||||
class SysComputedAttributes(object):
|
||||
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||
type2attr = {
|
||||
BuiltinModelEnum.IPAM_SUBNET: {
|
||||
SubnetBuiltinAttributes.HOSTS_COUNT,
|
||||
SubnetBuiltinAttributes.ASSIGN_COUNT,
|
||||
SubnetBuiltinAttributes.USED_COUNT,
|
||||
SubnetBuiltinAttributes.FREE_COUNT
|
||||
}
|
||||
}
|
||||
|
||||
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id'}
|
||||
|
||||
L_TYPE = None
|
||||
L_CI = None
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,33 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
|
||||
|
||||
class DCIMBase(object):
|
||||
def __init__(self):
|
||||
self.type_id = None
|
||||
|
||||
@staticmethod
|
||||
def add_relation(parent_id, child_id):
|
||||
if not parent_id or not child_id:
|
||||
return
|
||||
|
||||
CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False)
|
||||
|
||||
def add(self, parent_id, **kwargs):
|
||||
ci_id = CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)
|
||||
|
||||
if parent_id:
|
||||
self.add_relation(parent_id, ci_id)
|
||||
|
||||
return ci_id
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
CIManager().update(_id, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
CIManager().delete(_id)
|
|
@ -1,17 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from api.lib.utils import BaseEnum
|
||||
|
||||
|
||||
class RackBuiltinAttributes(BaseEnum):
|
||||
U_COUNT = 'u_count'
|
||||
U_START = 'u_start'
|
||||
FREE_U_COUNT = 'free_u_count'
|
||||
U_SLOT_ABNORMAL = 'u_slot_abnormal'
|
||||
|
||||
|
||||
class OperateTypeEnum(BaseEnum):
|
||||
ADD_DEVICE = "0"
|
||||
REMOVE_DEVICE = "1"
|
||||
MOVE_DEVICE = "2"
|
|
@ -1,40 +0,0 @@
|
|||
from flask_login import current_user
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.mixin import DBMixin
|
||||
from api.models.cmdb import DCIMOperationHistory
|
||||
|
||||
|
||||
class OperateHistoryManager(DBMixin):
|
||||
cls = DCIMOperationHistory
|
||||
|
||||
@classmethod
|
||||
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False,
|
||||
last_size=None, **kwargs):
|
||||
numfound, result = super(OperateHistoryManager, cls).search(page, page_size, fl, only_query, reverse,
|
||||
count_query, last_size, **kwargs)
|
||||
|
||||
ci_ids = [i['ci_id'] for i in result]
|
||||
id2ci = {i['_id']: i for i in (CIManager.get_cis_by_ids(ci_ids) or []) if i}
|
||||
type2show_key = dict()
|
||||
for i in id2ci.values():
|
||||
if i.get('_type') not in type2show_key:
|
||||
ci_type = CITypeCache.get(i.get('_type'))
|
||||
if ci_type:
|
||||
show_key = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
|
||||
type2show_key[i['_type']] = show_key and show_key.name
|
||||
|
||||
return numfound, result, id2ci, type2show_key
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
return kwargs
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
pass
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
pass
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.dcim.base import DCIMBase
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
|
||||
|
||||
class IDCManager(DCIMBase):
|
||||
def __init__(self):
|
||||
super(IDCManager, self).__init__()
|
||||
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_IDC) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_IDC))
|
||||
|
||||
self.type_id = self.ci_type.id
|
|
@ -1,182 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import itertools
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.dcim.base import DCIMBase
|
||||
from api.lib.cmdb.dcim.const import OperateTypeEnum
|
||||
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
|
||||
from api.lib.cmdb.dcim.history import OperateHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci_relation.search import Search as RelationSearch
|
||||
|
||||
|
||||
class RackManager(DCIMBase):
|
||||
def __init__(self):
|
||||
super(RackManager, self).__init__()
|
||||
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
if RackBuiltinAttributes.U_COUNT in kwargs:
|
||||
devices, _, _, _, _, _ = RelationSearch(
|
||||
[_id],
|
||||
level=[1],
|
||||
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||
count=1000000).search()
|
||||
for device in devices:
|
||||
u_start = device.get(RackBuiltinAttributes.U_START)
|
||||
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||
if u_start and u_start + u_count - 1 > kwargs[RackBuiltinAttributes.U_COUNT]:
|
||||
return abort(400, ErrFormat.dcim_rack_u_count_invalid)
|
||||
|
||||
CIManager().update(_id, _sync=True, **kwargs)
|
||||
|
||||
if RackBuiltinAttributes.U_COUNT in kwargs:
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls.calc_u_free_count(_id)}
|
||||
|
||||
CIManager().update(_id, _sync=True, **payload)
|
||||
|
||||
def delete(self, _id):
|
||||
super(RackManager, self).delete(_id)
|
||||
|
||||
payload = {RackBuiltinAttributes.U_START: None}
|
||||
_, _, second_cis = CIRelationManager.get_second_cis(_id, per_page='all')
|
||||
for ci in second_cis:
|
||||
CIManager().update(ci['_id'], **payload)
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
|
||||
if device_id is not None and u_count is None:
|
||||
ci = CIManager().get_ci_by_id(device_id, need_children=False)
|
||||
u_count = ci.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||
|
||||
if u_start and u_start + u_count - 1 > rack.get(RackBuiltinAttributes.U_COUNT):
|
||||
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)
|
||||
|
||||
devices, _, _, _, _, _ = RelationSearch(
|
||||
[rack_id],
|
||||
level=[1],
|
||||
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||
count=1000000).search()
|
||||
|
||||
u_count_sum = 0
|
||||
for device in devices:
|
||||
u_count_sum += (device.get(RackBuiltinAttributes.U_COUNT) or 2)
|
||||
if device_id is not None:
|
||||
_u_start = device.get(RackBuiltinAttributes.U_START)
|
||||
_u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||
if not _u_start:
|
||||
continue
|
||||
|
||||
if device.get('_id') != device_id and set(range(u_start, u_start + u_count)) & set(
|
||||
range(_u_start, _u_start + _u_count)):
|
||||
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)
|
||||
|
||||
return rack[RackBuiltinAttributes.U_COUNT] - u_count_sum
|
||||
|
||||
def check_u_slot(self):
|
||||
racks, _, _, _, _, _ = SearchFromDB(
|
||||
"_type:{}".format(self.type_id),
|
||||
count=10000000,
|
||||
fl=[RackBuiltinAttributes.U_START, RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_SLOT_ABNORMAL],
|
||||
parent_node_perm_passed=True).search()
|
||||
|
||||
for rack in racks:
|
||||
devices, _, _, _, _, _ = RelationSearch(
|
||||
[rack['_id']],
|
||||
level=[1],
|
||||
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
|
||||
count=1000000).search()
|
||||
|
||||
u_slot_sets = []
|
||||
for device in devices:
|
||||
u_start = device.get(RackBuiltinAttributes.U_START)
|
||||
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
|
||||
if u_start is not None and str(u_start).isdigit():
|
||||
u_slot_sets.append(set(range(u_start, u_start + u_count)))
|
||||
|
||||
if len(u_slot_sets) > 1:
|
||||
u_slot_abnormal = False
|
||||
for a, b in itertools.combinations(u_slot_sets, 2):
|
||||
if a.intersection(b):
|
||||
u_slot_abnormal = True
|
||||
break
|
||||
if u_slot_abnormal != rack.get(RackBuiltinAttributes.U_SLOT_ABNORMAL):
|
||||
payload = {RackBuiltinAttributes.U_SLOT_ABNORMAL: u_slot_abnormal}
|
||||
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)
|
||||
|
||||
self.add_relation(rack_id, device_id)
|
||||
|
||||
payload = {RackBuiltinAttributes.U_START: u_start}
|
||||
if u_count:
|
||||
payload[RackBuiltinAttributes.U_COUNT] = u_count
|
||||
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)}
|
||||
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)):
|
||||
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
|
||||
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id)}
|
||||
CIManager().update(rack_id, _sync=True, **payload)
|
||||
|
||||
payload = {RackBuiltinAttributes.U_START: None}
|
||||
CIManager().update(device_id, _sync=True, **payload)
|
||||
|
||||
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)}
|
||||
CIManager().update(rack_id, _sync=True, **payload)
|
||||
|
||||
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
|
||||
|
||||
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)
|
||||
|
||||
if rack_id != to_rack_id:
|
||||
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)
|
||||
|
||||
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)}
|
||||
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)}
|
||||
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)
|
|
@ -1,29 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
|
||||
|
||||
class RegionManager(object):
|
||||
def __init__(self):
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_REGION) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_REGION))
|
||||
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
def add(self, **kwargs):
|
||||
return CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def update(cls, _id, **kwargs):
|
||||
CIManager().update(_id, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
CIManager().delete(_id)
|
|
@ -1,56 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.dcim.base import DCIMBase
|
||||
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class ServerRoomManager(DCIMBase):
|
||||
def __init__(self):
|
||||
super(ServerRoomManager, self).__init__()
|
||||
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_SERVER_ROOM) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_SERVER_ROOM))
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
@staticmethod
|
||||
def get_racks(_id, q=None):
|
||||
rack_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||
|
||||
relations = CIRelation.get_by(first_ci_id=_id, only_query=True).join(
|
||||
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id == rack_type.id)
|
||||
rack_ids = [i.second_ci_id for i in relations]
|
||||
|
||||
q = "_type:{}".format(rack_type.id) if not q else "_type:{},{}".format(rack_type.id, q)
|
||||
if rack_ids:
|
||||
response, _, _, _, numfound, _ = SearchFromDB(
|
||||
q,
|
||||
ci_ids=list(rack_ids),
|
||||
count=1000000,
|
||||
parent_node_perm_passed=True).search()
|
||||
else:
|
||||
response, numfound = [], 0
|
||||
|
||||
counter = dict(rack_count=numfound)
|
||||
u_count = 0
|
||||
free_u_count = 0
|
||||
for i in response:
|
||||
_u_count = i.get(RackBuiltinAttributes.U_COUNT) or 0
|
||||
u_count += _u_count
|
||||
free_u_count += (_u_count if i.get(RackBuiltinAttributes.FREE_U_COUNT) is None else
|
||||
i.get(RackBuiltinAttributes.FREE_U_COUNT))
|
||||
counter["u_count"] = u_count
|
||||
counter["u_used_count"] = u_count - free_u_count
|
||||
counter["device_count"] = CIRelation.get_by(only_query=True).filter(
|
||||
CIRelation.first_ci_id.in_(rack_ids)).count()
|
||||
|
||||
return counter, response
|
|
@ -1,85 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
|
||||
|
||||
class TreeViewManager(object):
|
||||
@classmethod
|
||||
def get(cls):
|
||||
region_type = CITypeCache.get(BuiltinModelEnum.DCIM_REGION) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_REGION))
|
||||
|
||||
idc_type = CITypeCache.get(BuiltinModelEnum.DCIM_IDC) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_IDC))
|
||||
|
||||
server_room_type = CITypeCache.get(BuiltinModelEnum.DCIM_SERVER_ROOM) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_SERVER_ROOM))
|
||||
|
||||
rack_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
|
||||
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))
|
||||
|
||||
relations = defaultdict(set)
|
||||
ids = set()
|
||||
has_parent_ids = set()
|
||||
|
||||
for i in CIRelation.get_by(only_query=True).join(CI, CI.id == CIRelation.first_ci_id).filter(
|
||||
CI.type_id.in_([region_type.id, idc_type.id])):
|
||||
relations[i.first_ci_id].add(i.second_ci_id)
|
||||
ids.add(i.first_ci_id)
|
||||
ids.add(i.second_ci_id)
|
||||
has_parent_ids.add(i.second_ci_id)
|
||||
|
||||
for i in CIRelation.get_by(only_query=True).join(
|
||||
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id.in_([idc_type.id, server_room_type.id])):
|
||||
relations[i.first_ci_id].add(i.second_ci_id)
|
||||
ids.add(i.first_ci_id)
|
||||
ids.add(i.second_ci_id)
|
||||
has_parent_ids.add(i.second_ci_id)
|
||||
|
||||
for i in CI.get_by(only_query=True).filter(CI.type_id.in_([region_type.id, idc_type.id])):
|
||||
ids.add(i.id)
|
||||
|
||||
for _id in ids:
|
||||
if _id not in has_parent_ids:
|
||||
relations[None].add(_id)
|
||||
|
||||
type2name = dict()
|
||||
type2name[region_type.id] = AttributeCache.get(region_type.show_id or region_type.unique_id).name
|
||||
type2name[idc_type.id] = AttributeCache.get(idc_type.show_id or idc_type.unique_id).name
|
||||
type2name[server_room_type.id] = AttributeCache.get(server_room_type.show_id or server_room_type.unique_id).name
|
||||
|
||||
response, _, _, _, _, _ = SearchFromDB(
|
||||
"_type:({})".format(";".join(map(str, [region_type.id, idc_type.id, server_room_type.id]))),
|
||||
ci_ids=list(ids),
|
||||
count=1000000,
|
||||
fl=list(type2name.values()),
|
||||
parent_node_perm_passed=True).search()
|
||||
id2ci = {i['_id']: i for i in response}
|
||||
|
||||
def _build_tree(_tree, parent_id=None):
|
||||
tree = []
|
||||
for child_id in _tree.get(parent_id, []):
|
||||
children = sorted(_build_tree(_tree, child_id), key=lambda x: x['_id'])
|
||||
if not id2ci.get(child_id):
|
||||
continue
|
||||
ci = id2ci[child_id]
|
||||
if ci['ci_type'] == BuiltinModelEnum.DCIM_SERVER_ROOM:
|
||||
ci['rack_count'] = CIRelation.get_by(first_ci_id=child_id, only_query=True).join(
|
||||
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id == rack_type.id).count()
|
||||
|
||||
tree.append({'children': children, **ci})
|
||||
return tree
|
||||
|
||||
result = sorted(_build_tree(relations), key=lambda x: x['_id'])
|
||||
|
||||
return result, type2name
|
|
@ -10,7 +10,6 @@ from api.extensions import db
|
|||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import RelationTypeCache
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
|
@ -23,7 +22,6 @@ from api.models.cmdb import CITypeHistory
|
|||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import OperationRecord
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
|
||||
|
||||
class AttributeHistoryManger(object):
|
||||
|
@ -61,23 +59,8 @@ class AttributeHistoryManger(object):
|
|||
total = len(records)
|
||||
|
||||
res = {}
|
||||
show_attr_set = {}
|
||||
show_attr_cache = {}
|
||||
for record in records:
|
||||
record_id = record.OperationRecord.id
|
||||
type_id = record.OperationRecord.type_id
|
||||
ci_id = record.AttributeHistory.ci_id
|
||||
show_attr_set[ci_id] = None
|
||||
show_attr = show_attr_cache.setdefault(
|
||||
type_id,
|
||||
AttributeCache.get(
|
||||
CITypeCache.get(type_id).show_id or CITypeCache.get(type_id).unique_id) if CITypeCache.get(type_id) else None
|
||||
)
|
||||
if show_attr:
|
||||
attr_table = TableMap(attr=show_attr).table
|
||||
attr_record = attr_table.get_by(attr_id=show_attr.id, ci_id=ci_id, first=True, to_dict=False)
|
||||
show_attr_set[ci_id] = attr_record.value if attr_record else None
|
||||
|
||||
attr_hist = record.AttributeHistory.to_dict()
|
||||
attr_hist['attr'] = AttributeCache.get(attr_hist['attr_id'])
|
||||
if attr_hist['attr']:
|
||||
|
@ -93,7 +76,6 @@ class AttributeHistoryManger(object):
|
|||
|
||||
if record_id not in res:
|
||||
record_dict = record.OperationRecord.to_dict()
|
||||
record_dict['show_attr_value'] = show_attr_set.get(ci_id)
|
||||
record_dict["user"] = UserCache.get(record_dict.get("uid"))
|
||||
if record_dict["user"]:
|
||||
record_dict['user'] = record_dict['user'].nickname
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,132 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.ipam.const import IPAddressAssignStatus
|
||||
from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes
|
||||
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci_relation.search import Search as RelationSearch
|
||||
|
||||
|
||||
class IpAddressManager(object):
|
||||
def __init__(self):
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) or abort(
|
||||
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
@staticmethod
|
||||
def list_ip_address(parent_id):
|
||||
numfound, _, result = CIRelationManager.get_second_cis(parent_id, per_page="all")
|
||||
|
||||
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()
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _add_relation(parent_id, child_id):
|
||||
if not parent_id or not child_id:
|
||||
return
|
||||
|
||||
CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False)
|
||||
|
||||
@staticmethod
|
||||
def calc_used_count(subnet_id):
|
||||
q = "{}:(0;2),-{}:true".format(IPAddressBuiltinAttributes.ASSIGN_STATUS, IPAddressBuiltinAttributes.IS_USED)
|
||||
|
||||
return len(set(RelationSearch([subnet_id], level=[1], query=q, count=1000000).search(only_ids=True) or []))
|
||||
|
||||
@staticmethod
|
||||
def _calc_assign_count(subnet_id):
|
||||
q = "{}:(0;2)".format(IPAddressBuiltinAttributes.ASSIGN_STATUS)
|
||||
|
||||
return len(set(RelationSearch([subnet_id], level=[1], query=q, count=1000000).search(only_ids=True) or []))
|
||||
|
||||
def _update_subnet_count(self, subnet_id, assign_count_computed, used_count=None):
|
||||
payload = {}
|
||||
|
||||
cur = CIManager.get_ci_by_id(subnet_id, need_children=False)
|
||||
if assign_count_computed:
|
||||
payload[SubnetBuiltinAttributes.ASSIGN_COUNT] = self._calc_assign_count(subnet_id)
|
||||
if used_count is not None:
|
||||
payload[SubnetBuiltinAttributes.USED_COUNT] = used_count
|
||||
|
||||
payload[SubnetBuiltinAttributes.FREE_COUNT] = (cur[SubnetBuiltinAttributes.HOSTS_COUNT] -
|
||||
self.calc_used_count(subnet_id))
|
||||
CIManager().update(subnet_id, **payload)
|
||||
|
||||
def assign_ips(self, ips, subnet_id, cidr, **kwargs):
|
||||
"""
|
||||
|
||||
:param ips: ip list
|
||||
:param subnet_id: subnet id
|
||||
:param cidr: subnet cidr
|
||||
:param kwargs: other attributes for ip address
|
||||
:return:
|
||||
"""
|
||||
if subnet_id is not None:
|
||||
subnet = CIManager.get_ci_by_id(subnet_id)
|
||||
else:
|
||||
cis, _, _, _, _, _ = SearchFromDB("_type:{},{}:{}".format(
|
||||
BuiltinModelEnum.IPAM_SUBNET, SubnetBuiltinAttributes.CIDR, cidr),
|
||||
parent_node_perm_passed=True).search()
|
||||
if cis:
|
||||
subnet = cis[0]
|
||||
subnet_id = subnet['_id']
|
||||
else:
|
||||
return abort(400, ErrFormat.ipam_address_model_not_found)
|
||||
|
||||
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id), expire=10)):
|
||||
cis = self._get_cis(subnet_id, ips)
|
||||
ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis}
|
||||
|
||||
ci_ids = []
|
||||
for ip in ips:
|
||||
kwargs['name'] = ip
|
||||
kwargs[IPAddressBuiltinAttributes.IP] = ip
|
||||
if ip not in ip2ci:
|
||||
ci_id = CIManager.add(self.type_id, _sync=True, **kwargs)
|
||||
else:
|
||||
ci_id = ip2ci[ip]['_id']
|
||||
CIManager().update(ci_id, _sync=True, **kwargs)
|
||||
ci_ids.append(ci_id)
|
||||
|
||||
self._add_relation(subnet_id, ci_id)
|
||||
|
||||
if ips and IPAddressBuiltinAttributes.ASSIGN_STATUS in kwargs:
|
||||
self._update_subnet_count(subnet_id, True)
|
||||
|
||||
if ips and IPAddressBuiltinAttributes.IS_USED in kwargs:
|
||||
q = "{}:true".format(IPAddressBuiltinAttributes.IS_USED)
|
||||
cur_used_ids = RelationSearch([subnet_id], level=[1], query=q).search(only_ids=True)
|
||||
for _id in set(cur_used_ids) - set(ci_ids):
|
||||
CIManager().update(_id, **{IPAddressBuiltinAttributes.IS_USED: False})
|
||||
|
||||
self._update_subnet_count(subnet_id, False, used_count=len(ips))
|
||||
|
||||
if kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) in (
|
||||
IPAddressAssignStatus.ASSIGNED, IPAddressAssignStatus.RESERVED):
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.ASSIGN_ADDRESS,
|
||||
cidr=subnet.get(SubnetBuiltinAttributes.CIDR),
|
||||
description=" | ".join(ips))
|
||||
|
||||
elif kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) == IPAddressAssignStatus.UNASSIGNED:
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.REVOKE_ADDRESS,
|
||||
cidr=subnet.get(SubnetBuiltinAttributes.CIDR),
|
||||
description=" | ".join(ips))
|
|
@ -1,35 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.utils import BaseEnum
|
||||
|
||||
|
||||
class IPAddressAssignStatus(BaseEnum):
|
||||
ASSIGNED = 0
|
||||
UNASSIGNED = 1
|
||||
RESERVED = 2
|
||||
|
||||
|
||||
class OperateTypeEnum(BaseEnum):
|
||||
ADD_SCOPE = "0"
|
||||
UPDATE_SCOPE = "1"
|
||||
DELETE_SCOPE = "2"
|
||||
ADD_SUBNET = "3"
|
||||
UPDATE_SUBNET = "4"
|
||||
DELETE_SUBNET = "5"
|
||||
ASSIGN_ADDRESS = "6"
|
||||
REVOKE_ADDRESS = "7"
|
||||
|
||||
|
||||
class SubnetBuiltinAttributes(BaseEnum):
|
||||
NAME = 'name'
|
||||
CIDR = 'cidr'
|
||||
HOSTS_COUNT = 'hosts_count'
|
||||
ASSIGN_COUNT = 'assign_count'
|
||||
USED_COUNT = 'used_count'
|
||||
FREE_COUNT = 'free_count'
|
||||
|
||||
|
||||
class IPAddressBuiltinAttributes(BaseEnum):
|
||||
IP = 'ip'
|
||||
ASSIGN_STATUS = 'assign_status' # enum: 0 - assigned 1 - unassigned 2 - reserved
|
||||
IS_USED = 'is_used' # bool
|
|
@ -1,61 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes
|
||||
from api.lib.mixin import DBMixin
|
||||
from api.models.cmdb import IPAMOperationHistory
|
||||
from api.models.cmdb import IPAMSubnetScan
|
||||
from api.models.cmdb import IPAMSubnetScanHistory
|
||||
|
||||
|
||||
class OperateHistoryManager(DBMixin):
|
||||
cls = IPAMOperationHistory
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
return kwargs
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
pass
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class ScanHistoryManager(DBMixin):
|
||||
cls = IPAMSubnetScanHistory
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
return kwargs
|
||||
|
||||
def add(self, **kwargs):
|
||||
kwargs.pop('_key', None)
|
||||
kwargs.pop('_secret', None)
|
||||
ci_id = kwargs.pop('ci_id', None)
|
||||
|
||||
existed = self.cls.get_by(exec_id=kwargs['exec_id'], first=True, to_dict=False)
|
||||
if existed is None:
|
||||
self.cls.create(**kwargs)
|
||||
else:
|
||||
existed.update(**kwargs)
|
||||
|
||||
if kwargs.get('ips'):
|
||||
from api.lib.cmdb.ipam.address import IpAddressManager
|
||||
IpAddressManager().assign_ips(kwargs['ips'], ci_id, kwargs.get('cidr'),
|
||||
**{IPAddressBuiltinAttributes.IS_USED: 1})
|
||||
|
||||
scan_rule = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False)
|
||||
if scan_rule is not None:
|
||||
scan_rule.update(last_scan_time=kwargs.get('start_at'))
|
||||
|
||||
for i in self.cls.get_by(subnet_scan_id=kwargs.get('subnet_scan_id'), only_query=True).order_by(
|
||||
self.cls.id.desc()).offset(100):
|
||||
i.delete()
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
pass
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
pass
|
|
@ -1,104 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import IPAMSubnetScan
|
||||
|
||||
|
||||
class Stats(object):
|
||||
def __init__(self):
|
||||
self.address_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) or abort(
|
||||
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||
|
||||
self.address_type_id = self.address_type.id
|
||||
|
||||
self.subnet_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) or abort(
|
||||
404, ErrFormat.ipam_address_model_not_found.format(BuiltinModelEnum.IPAM_ADDRESS))
|
||||
|
||||
self.subnet_type_id = self.subnet_type.id
|
||||
|
||||
def leaf_nodes(self, parent_id):
|
||||
if str(parent_id) == '0': # all
|
||||
ci_ids = [i.id for i in CI.get_by(type_id=self.subnet_type_id, to_dict=False)]
|
||||
has_children_ci_ids = [i.first_ci_id for i in CIRelation.get_by(
|
||||
only_query=True).join(CI, CIRelation.second_ci_id == CI.id).filter(
|
||||
CIRelation.first_ci_id.in_(ci_ids)).filter(CI.type_id == self.subnet_type_id)]
|
||||
|
||||
return list(set(ci_ids) - set(has_children_ci_ids))
|
||||
|
||||
else:
|
||||
_type = CIManager().get_by_id(parent_id)
|
||||
if not _type:
|
||||
return abort(404, ErrFormat.ipam_subnet_not_found)
|
||||
key = [(str(parent_id), _type.type_id)]
|
||||
result = []
|
||||
while True:
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(
|
||||
[i[0] for i in key], REDIS_PREFIX_CI_RELATION) or []]]
|
||||
|
||||
for idx, i in enumerate(res):
|
||||
if (not i or list(i)[0][1] == self.address_type_id) and key[idx][1] == self.subnet_type_id:
|
||||
result.append(int(key[idx][0]))
|
||||
|
||||
res = [j for i in res for j in i] # [(id, type_id)]
|
||||
|
||||
if not res:
|
||||
return result
|
||||
|
||||
key = res
|
||||
|
||||
def statistic_subnets(self, subnet_ids):
|
||||
if subnet_ids:
|
||||
response, _, _, _, _, _ = SearchFromDB(
|
||||
"_type:{}".format(self.subnet_type_id),
|
||||
ci_ids=subnet_ids,
|
||||
count=1000000,
|
||||
parent_node_perm_passed=True,
|
||||
).search()
|
||||
else:
|
||||
response = []
|
||||
|
||||
scans = IPAMSubnetScan.get_by(only_query=True).filter(IPAMSubnetScan.ci_id.in_(list(map(int, subnet_ids))))
|
||||
id2scan = {i.ci_id: i for i in scans}
|
||||
|
||||
address_num, address_free_num, address_assign_num, address_used_num = 0, 0, 0, 0
|
||||
for subnet in response:
|
||||
address_num += (subnet.get('hosts_count') or 0)
|
||||
address_free_num += (subnet.get('free_count') or 0)
|
||||
address_assign_num += (subnet.get('assign_count') or 0)
|
||||
address_used_num += (subnet.get('used_count') or 0)
|
||||
|
||||
if id2scan.get(subnet['_id']):
|
||||
subnet['scan_enabled'] = id2scan[subnet['_id']].scan_enabled
|
||||
subnet['last_scan_time'] = id2scan[subnet['_id']].last_scan_time
|
||||
else:
|
||||
subnet['scan_enabled'] = False
|
||||
subnet['last_scan_time'] = None
|
||||
|
||||
return response, address_num, address_free_num, address_assign_num, address_used_num
|
||||
|
||||
def summary(self, parent_id):
|
||||
subnet_ids = self.leaf_nodes(parent_id)
|
||||
|
||||
subnets, address_num, address_free_num, address_assign_num, address_used_num = (
|
||||
self.statistic_subnets(subnet_ids))
|
||||
|
||||
return dict(subnet_num=len(subnets),
|
||||
address_num=address_num,
|
||||
address_free_num=address_free_num,
|
||||
address_assign_num=address_assign_num,
|
||||
address_unassign_num=address_num - address_assign_num,
|
||||
address_used_num=address_used_num,
|
||||
address_used_free_num=address_num - address_used_num,
|
||||
subnets=subnets)
|
|
@ -1,355 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import datetime
|
||||
import ipaddress
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.const import BuiltinModelEnum
|
||||
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||
from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes
|
||||
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import IPAMSubnetScan
|
||||
|
||||
|
||||
class SubnetManager(object):
|
||||
def __init__(self):
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) or abort(
|
||||
404, ErrFormat.ipam_subnet_model_not_found.format(BuiltinModelEnum.IPAM_SUBNET))
|
||||
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
def scan_rules(self, oneagent_id, last_update_at=None):
|
||||
result = []
|
||||
rules = IPAMSubnetScan.get_by(agent_id=oneagent_id, to_dict=True)
|
||||
ci_ids = [i['ci_id'] for i in rules]
|
||||
if ci_ids:
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||
ci_ids=list(ci_ids),
|
||||
count=1000000,
|
||||
fl=[SubnetBuiltinAttributes.CIDR],
|
||||
parent_node_perm_passed=True).search()
|
||||
id2ci = {i['_id']: i for i in response}
|
||||
|
||||
for rule in rules:
|
||||
if rule['ci_id'] in id2ci:
|
||||
rule[SubnetBuiltinAttributes.CIDR] = id2ci[rule['ci_id']][SubnetBuiltinAttributes.CIDR]
|
||||
result.append(rule)
|
||||
|
||||
new_last_update_at = ""
|
||||
for i in result:
|
||||
__last_update_at = max([i['rule_updated_at'] or "", i['created_at'] or ""])
|
||||
if new_last_update_at < __last_update_at:
|
||||
new_last_update_at = __last_update_at
|
||||
|
||||
if not last_update_at or new_last_update_at > last_update_at:
|
||||
return result, new_last_update_at
|
||||
else:
|
||||
return [], new_last_update_at
|
||||
|
||||
@staticmethod
|
||||
def get_hosts(cidr):
|
||||
try:
|
||||
return list(map(str, ipaddress.ip_network(cidr).hosts()))
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def get_by_id(self, subnet_id):
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||
ci_ids=[subnet_id],
|
||||
parent_node_perm_passed=True).search()
|
||||
scan_rule = IPAMSubnetScan.get_by(ci_id=subnet_id, first=True, to_dict=True)
|
||||
if scan_rule and response:
|
||||
scan_rule.update(response[0])
|
||||
|
||||
return scan_rule
|
||||
|
||||
def tree_view(self):
|
||||
scope = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE)
|
||||
ci_types = scope and [scope.id, self.type_id] or [self.type_id]
|
||||
|
||||
relations = defaultdict(set)
|
||||
ids = set()
|
||||
has_parent_ids = set()
|
||||
for i in CIRelation.get_by(only_query=True).join(
|
||||
CI, CI.id == CIRelation.first_ci_id).filter(CI.type_id.in_(ci_types)):
|
||||
relations[i.first_ci_id].add(i.second_ci_id)
|
||||
ids.add(i.first_ci_id)
|
||||
ids.add(i.second_ci_id)
|
||||
has_parent_ids.add(i.second_ci_id)
|
||||
for i in CIRelation.get_by(only_query=True).join(
|
||||
CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id.in_(ci_types)):
|
||||
relations[i.first_ci_id].add(i.second_ci_id)
|
||||
ids.add(i.first_ci_id)
|
||||
ids.add(i.second_ci_id)
|
||||
has_parent_ids.add(i.second_ci_id)
|
||||
|
||||
for i in CI.get_by(only_query=True).filter(CI.type_id.in_(ci_types)):
|
||||
ids.add(i.id)
|
||||
|
||||
for _id in ids:
|
||||
if _id not in has_parent_ids:
|
||||
relations[None].add(_id)
|
||||
|
||||
type2name = dict()
|
||||
type2name[self.type_id] = AttributeCache.get(self.ci_type.show_id or self.ci_type.unique_id).name
|
||||
|
||||
fl = [type2name[self.type_id]]
|
||||
if scope:
|
||||
type2name[scope.id] = AttributeCache.get(scope.show_id or scope.unique_id).name
|
||||
fl.append(type2name[scope.id])
|
||||
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:({})".format(";".join(map(str, ci_types))),
|
||||
ci_ids=list(ids),
|
||||
count=1000000,
|
||||
fl=list(set(fl + [SubnetBuiltinAttributes.CIDR])),
|
||||
parent_node_perm_passed=True).search()
|
||||
id2ci = {i['_id']: i for i in response}
|
||||
|
||||
def _build_tree(_tree, parent_id=None):
|
||||
tree = []
|
||||
for child_id in _tree.get(parent_id, []):
|
||||
children = sorted(_build_tree(_tree, child_id), key=lambda x: x['_id'])
|
||||
if not id2ci.get(child_id):
|
||||
continue
|
||||
tree.append({'children': children, **id2ci[child_id]})
|
||||
return tree
|
||||
|
||||
result = sorted(_build_tree(relations), key=lambda x: x['_id'])
|
||||
|
||||
return result, type2name
|
||||
|
||||
@staticmethod
|
||||
def _is_valid_cidr(cidr):
|
||||
try:
|
||||
cidr = ipaddress.ip_network(cidr)
|
||||
if not (8 <= cidr.prefixlen <= 31):
|
||||
raise ValueError
|
||||
|
||||
return str(cidr)
|
||||
except ValueError:
|
||||
return abort(400, ErrFormat.ipam_cidr_invalid_notation.format(cidr))
|
||||
|
||||
def _check_root_node_is_overlapping(self, cidr, _id=None):
|
||||
none_root_nodes = [i.id for i in CI.get_by(only_query=True).join(
|
||||
CIRelation, CIRelation.second_ci_id == CI.id).filter(CI.type_id == self.type_id)]
|
||||
all_nodes = [i.id for i in CI.get_by(type_id=self.type_id, to_dict=False, fl=['id'])]
|
||||
|
||||
root_nodes = set(all_nodes) - set(none_root_nodes) - set(_id and [_id] or [])
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||
ci_ids=list(root_nodes),
|
||||
count=1000000,
|
||||
parent_node_perm_passed=True).search()
|
||||
|
||||
cur_subnet = ipaddress.ip_network(cidr)
|
||||
for item in response:
|
||||
if item['_id'] == _id:
|
||||
continue
|
||||
|
||||
if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))):
|
||||
return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR)))
|
||||
|
||||
return cidr
|
||||
|
||||
def _check_child_node_is_overlapping(self, parent_id, cidr, _id=None):
|
||||
child_nodes = [i.second_ci_id for i in CIRelation.get_by(
|
||||
first_ci_id=parent_id, to_dict=False, fl=['second_ci_id']) if i.second_ci_id != _id]
|
||||
if not child_nodes:
|
||||
return
|
||||
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id),
|
||||
ci_ids=list(child_nodes),
|
||||
count=1000000,
|
||||
parent_node_perm_passed=True).search()
|
||||
|
||||
cur_subnet = ipaddress.ip_network(cidr)
|
||||
for item in response:
|
||||
if item['_id'] == _id:
|
||||
continue
|
||||
|
||||
if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))):
|
||||
return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR)))
|
||||
|
||||
def validate_cidr(self, parent_id, cidr, _id=None):
|
||||
cidr = self._is_valid_cidr(cidr)
|
||||
|
||||
if not parent_id:
|
||||
return self._check_root_node_is_overlapping(cidr, _id)
|
||||
|
||||
parent_subnet = CIManager().get_ci_by_id(parent_id, need_children=False)
|
||||
if parent_subnet['ci_type'] == BuiltinModelEnum.IPAM_SUBNET:
|
||||
if parent_subnet.get(SubnetBuiltinAttributes.CIDR):
|
||||
prefix = int(cidr.split('/')[1])
|
||||
if int(parent_subnet[SubnetBuiltinAttributes.CIDR].split('/')[1]) >= prefix:
|
||||
return abort(400, ErrFormat.ipam_subnet_prefix_length_invalid.format(prefix))
|
||||
|
||||
valid_subnets = [str(i) for i in
|
||||
ipaddress.ip_network(parent_subnet[SubnetBuiltinAttributes.CIDR]).subnets(
|
||||
new_prefix=prefix)]
|
||||
if cidr not in valid_subnets:
|
||||
return abort(400, ErrFormat.ipam_cidr_invalid_subnet.format(cidr, valid_subnets))
|
||||
else:
|
||||
return abort(400, ErrFormat.ipam_parent_subnet_node_cidr_cannot_empty)
|
||||
|
||||
self._check_child_node_is_overlapping(parent_id, cidr, _id)
|
||||
|
||||
return cidr
|
||||
|
||||
def _add_subnet(self, cidr, **kwargs):
|
||||
kwargs[SubnetBuiltinAttributes.HOSTS_COUNT] = len(list(ipaddress.ip_network(cidr).hosts()))
|
||||
kwargs[SubnetBuiltinAttributes.USED_COUNT] = 0
|
||||
kwargs[SubnetBuiltinAttributes.ASSIGN_COUNT] = 0
|
||||
kwargs[SubnetBuiltinAttributes.FREE_COUNT] = kwargs[SubnetBuiltinAttributes.HOSTS_COUNT]
|
||||
|
||||
return CIManager().add(self.type_id, cidr=cidr, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _add_scan_rule(ci_id, agent_id, cron, scan_enabled=True):
|
||||
IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled)
|
||||
|
||||
@staticmethod
|
||||
def _add_relation(parent_id, child_id):
|
||||
if not parent_id or not child_id:
|
||||
return
|
||||
|
||||
CIRelationManager().add(parent_id, child_id, valid=False)
|
||||
|
||||
def add(self, cidr, parent_id, agent_id, cron, scan_enabled=True, **kwargs):
|
||||
cidr = self.validate_cidr(parent_id, cidr)
|
||||
|
||||
ci_id = self._add_subnet(cidr, **kwargs)
|
||||
|
||||
self._add_scan_rule(ci_id, agent_id, cron, scan_enabled)
|
||||
|
||||
self._add_relation(parent_id, ci_id)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SUBNET,
|
||||
cidr=cidr,
|
||||
description=cidr)
|
||||
|
||||
return ci_id
|
||||
|
||||
@staticmethod
|
||||
def _update_subnet(_id, **kwargs):
|
||||
return CIManager().update(_id, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _update_scan_rule(ci_id, agent_id, cron, scan_enabled=True):
|
||||
existed = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False)
|
||||
if existed is not None:
|
||||
existed.update(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled,
|
||||
rule_updated_at=datetime.datetime.now())
|
||||
else:
|
||||
IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled)
|
||||
|
||||
def update(self, _id, **kwargs):
|
||||
kwargs[SubnetBuiltinAttributes.CIDR] = self.validate_cidr(kwargs.pop('parent_id', None),
|
||||
kwargs.get(SubnetBuiltinAttributes.CIDR), _id)
|
||||
|
||||
agent_id = kwargs.pop('agent_id', None)
|
||||
cron = kwargs.pop('cron', None)
|
||||
scan_enabled = kwargs.pop('scan_enabled', True)
|
||||
|
||||
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||
|
||||
self._update_subnet(_id, **kwargs)
|
||||
|
||||
self._update_scan_rule(_id, agent_id, cron, scan_enabled)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SUBNET,
|
||||
cidr=cur.get(SubnetBuiltinAttributes.CIDR),
|
||||
description="{} -> {}".format(cur.get(SubnetBuiltinAttributes.CIDR),
|
||||
kwargs.get(SubnetBuiltinAttributes.CIDR)))
|
||||
|
||||
return _id
|
||||
|
||||
@classmethod
|
||||
def delete(cls, _id):
|
||||
if CIRelation.get_by(only_query=True).filter(CIRelation.first_ci_id == _id).first():
|
||||
return abort(400, ErrFormat.ipam_subnet_cannot_delete)
|
||||
|
||||
existed = IPAMSubnetScan.get_by(ci_id=_id, first=True, to_dict=False)
|
||||
existed and existed.delete()
|
||||
|
||||
delete_ci_ids = []
|
||||
for i in CIRelation.get_by(first_ci_id=_id, to_dict=False):
|
||||
delete_ci_ids.append(i.second_ci_id)
|
||||
i.delete()
|
||||
|
||||
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||
|
||||
CIManager().delete(_id)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SUBNET,
|
||||
cidr=cur.get(SubnetBuiltinAttributes.CIDR),
|
||||
description=cur.get(SubnetBuiltinAttributes.CIDR))
|
||||
|
||||
# batch_delete_ci.apply_async(args=(delete_ci_ids,))
|
||||
|
||||
return _id
|
||||
|
||||
|
||||
class SubnetScopeManager(object):
|
||||
def __init__(self):
|
||||
self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE)
|
||||
not self.ci_type and abort(400, ErrFormat.ipam_subnet_model_not_found.format(
|
||||
BuiltinModelEnum.IPAM_SCOPE))
|
||||
|
||||
self.type_id = self.ci_type.id
|
||||
|
||||
def _add_scope(self, name):
|
||||
return CIManager().add(self.type_id, name=name)
|
||||
|
||||
@staticmethod
|
||||
def _add_relation(parent_id, child_id):
|
||||
if not parent_id or not child_id:
|
||||
return
|
||||
|
||||
CIRelationManager().add(parent_id, child_id, valid=False)
|
||||
|
||||
def add(self, parent_id, name):
|
||||
ci_id = self._add_scope(name)
|
||||
|
||||
self._add_relation(parent_id, ci_id)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SCOPE,
|
||||
description=name)
|
||||
|
||||
return ci_id
|
||||
|
||||
@staticmethod
|
||||
def _update_scope(_id, name):
|
||||
return CIManager().update(_id, name=name)
|
||||
|
||||
def update(self, _id, name):
|
||||
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||
|
||||
res = self._update_scope(_id, name)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SCOPE,
|
||||
description="{} -> {}".format(cur.get('name'), name))
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
if CIRelation.get_by(first_ci_id=_id, first=True, to_dict=False):
|
||||
return abort(400, ErrFormat.ipam_scope_cannot_delete)
|
||||
|
||||
cur = CIManager.get_ci_by_id(_id, need_children=False)
|
||||
|
||||
CIManager().delete(_id)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SCOPE,
|
||||
description=cur.get('name'))
|
||||
|
||||
return _id
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
import copy
|
||||
import functools
|
||||
|
||||
import redis_lock
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
@ -9,7 +10,6 @@ from flask_login import current_user
|
|||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.mixin import DBMixin
|
||||
|
@ -27,7 +27,7 @@ class CIFilterPermsCRUD(DBMixin):
|
|||
result = {}
|
||||
for i in res:
|
||||
if i['attr_filter']:
|
||||
i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys())
|
||||
i['attr_filter'] = i['attr_filter'].split(',')
|
||||
|
||||
if i['rid'] not in result:
|
||||
result[i['rid']] = i
|
||||
|
@ -62,7 +62,7 @@ class CIFilterPermsCRUD(DBMixin):
|
|||
result = {}
|
||||
for i in res:
|
||||
if i['attr_filter']:
|
||||
i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys())
|
||||
i['attr_filter'] = i['attr_filter'].split(',')
|
||||
|
||||
if i['type_id'] not in result:
|
||||
result[i['type_id']] = i
|
||||
|
@ -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,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
|
@ -16,16 +17,15 @@ 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.const import BUILTIN_ATTRIBUTES
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.const import SysComputedAttributes
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
|
@ -49,7 +49,7 @@ 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()
|
||||
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
|
@ -133,24 +133,21 @@ class PreferenceManager(object):
|
|||
|
||||
@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)
|
||||
attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join(
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == current_user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by(
|
||||
CITypeAttribute.attr_id).all()
|
||||
|
||||
result = []
|
||||
for i in sorted(attrs, key=lambda x: x.order):
|
||||
if i.attr_id:
|
||||
item = i.attr.to_dict()
|
||||
elif i.builtin_attr:
|
||||
item = dict(name=i.builtin_attr, alias=BUILTIN_ATTRIBUTES[i.builtin_attr])
|
||||
else:
|
||||
item = dict(name="", alias="")
|
||||
item.update(dict(is_fixed=i.is_fixed))
|
||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||
item = i.PreferenceShowAttributes.attr.to_dict()
|
||||
item.update(dict(is_fixed=i.PreferenceShowAttributes.is_fixed))
|
||||
result.append(item)
|
||||
|
||||
is_subscribed = True
|
||||
|
@ -159,23 +156,13 @@ class PreferenceManager(object):
|
|||
choice_web_hook_parse=False,
|
||||
choice_other_parse=False)
|
||||
result = [i for i in result if i['default_show']]
|
||||
|
||||
for i in BUILTIN_ATTRIBUTES:
|
||||
result.append(dict(name=i, alias=BUILTIN_ATTRIBUTES[i]))
|
||||
|
||||
is_subscribed = False
|
||||
|
||||
for i in result:
|
||||
if i.get("is_choice"):
|
||||
if i["is_choice"]:
|
||||
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
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
@classmethod
|
||||
|
@ -186,34 +173,24 @@ class PreferenceManager(object):
|
|||
_attr, is_fixed = x
|
||||
else:
|
||||
_attr, is_fixed = x, False
|
||||
|
||||
if _attr in BUILTIN_ATTRIBUTES:
|
||||
attr = None
|
||||
builtin_attr = _attr
|
||||
else:
|
||||
attr = AttributeCache.get(_attr) or abort(
|
||||
404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
||||
builtin_attr = None
|
||||
attr = AttributeCache.get(_attr) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
||||
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
||||
uid=current_user.uid,
|
||||
attr_id=attr and attr.id,
|
||||
builtin_attr=builtin_attr,
|
||||
attr_id=attr.id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is None:
|
||||
PreferenceShowAttributes.create(type_id=type_id,
|
||||
uid=current_user.uid,
|
||||
attr_id=attr and attr.id,
|
||||
builtin_attr=builtin_attr,
|
||||
attr_id=attr.id,
|
||||
order=order,
|
||||
is_fixed=is_fixed)
|
||||
else:
|
||||
existed.update(order=order, is_fixed=is_fixed)
|
||||
|
||||
attr_dict = {(int(i[0]) if i[0].isdigit() else i[0]) if isinstance(i, list) else
|
||||
(int(i) if i.isdigit() else i): j for i, j in attr_order}
|
||||
attr_dict = {int(i[0]) if isinstance(i, list) else int(i): j for i, j in attr_order}
|
||||
for i in existed_all:
|
||||
if (i.attr_id and i.attr_id not in attr_dict) or (i.builtin_attr and i.builtin_attr not in attr_dict):
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
|
||||
if not existed_all and attr_order:
|
||||
|
@ -407,7 +384,7 @@ class PreferenceManager(object):
|
|||
def add_search_option(**kwargs):
|
||||
kwargs['uid'] = current_user.uid
|
||||
|
||||
if kwargs['name'] in ('__recent__', '__favor__', '__relation_favor__'):
|
||||
if kwargs['name'] in ('__recent__', '__favor__'):
|
||||
if kwargs['name'] == '__recent__':
|
||||
for i in PreferenceSearchOption.get_by(
|
||||
only_query=True, name=kwargs['name'], uid=current_user.uid).order_by(
|
||||
|
|
|
@ -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!")
|
||||
# 只有管理员才可以定义执行机器为: 所有节点!
|
||||
|
@ -156,23 +154,3 @@ class ErrFormat(CommonErrFormat):
|
|||
topology_group_exists = _l("Topology group {} already exists") # 拓扑视图分组 {} 已经存在
|
||||
# 因为该分组下定义了拓扑视图,不能删除
|
||||
topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists")
|
||||
|
||||
relation_path_search_src_target_required = _l("Both the source model and the target model must be selected")
|
||||
|
||||
builtin_type_cannot_update_name = _l("The names of built-in models cannot be changed")
|
||||
# # IPAM
|
||||
ipam_subnet_model_not_found = _l("The subnet model {} does not exist")
|
||||
ipam_address_model_not_found = _l("The IP Address model {} does not exist")
|
||||
ipam_cidr_invalid_notation = _l("CIDR {} is an invalid notation")
|
||||
ipam_cidr_invalid_subnet = _l("Invalid CIDR: {}, available subnets: {}")
|
||||
ipam_subnet_prefix_length_invalid = _l("Invalid subnet prefix length: {}")
|
||||
ipam_parent_subnet_node_cidr_cannot_empty = _l("parent node cidr must be required")
|
||||
ipam_subnet_overlapped = _l("{} and {} overlap")
|
||||
ipam_subnet_cannot_delete = _l("Cannot delete because child nodes exist")
|
||||
ipam_subnet_not_found = _l("Subnet is not found")
|
||||
ipam_scope_cannot_delete = _l("Cannot delete because child nodes exist")
|
||||
|
||||
# # DCIM
|
||||
dcim_builtin_model_not_found = _l("The dcim model {} does not exist")
|
||||
dcim_rack_u_slot_invalid = _l("Irregularities in Rack Units")
|
||||
dcim_rack_u_count_invalid = _l("The device's position is greater than the rack unit height")
|
||||
|
|
|
@ -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
|
||||
"""
|
|
@ -4,9 +4,8 @@
|
|||
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
|
||||
|
@ -16,7 +15,6 @@ from api.extensions import db
|
|||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import BUILTIN_ATTRIBUTES
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
|
@ -28,7 +26,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
|
||||
|
@ -69,7 +66,6 @@ class Search(object):
|
|||
self.use_id_filter = use_id_filter
|
||||
self.use_ci_filter = use_ci_filter
|
||||
self.only_ids = only_ids
|
||||
self.multi_type_has_ci_filter = False
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
|
@ -108,58 +104,35 @@ class Search(object):
|
|||
else:
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(key))
|
||||
|
||||
def _type_query_handler(self, v, queries, is_sub=False):
|
||||
def _type_query_handler(self, v, queries):
|
||||
new_v = v[1:-1].split(";") if v.startswith("(") and v.endswith(")") else [v]
|
||||
type_num = len(new_v)
|
||||
type_id_list = []
|
||||
for _v in new_v:
|
||||
ci_type = CITypeCache.get(_v)
|
||||
|
||||
if type_num == 1 and not self.sort and ci_type and ci_type.default_order_attr:
|
||||
if len(new_v) == 1 and not self.sort and ci_type and ci_type.default_order_attr:
|
||||
self.sort = ci_type.default_order_attr
|
||||
|
||||
if ci_type is not None:
|
||||
if self.valid_type_names == "ALL" or ci_type.name in self.valid_type_names:
|
||||
if not is_sub:
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
type_id_list.append(str(ci_type.id))
|
||||
if ci_type.id in self.type2filter_perms and not is_sub:
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
if ci_type.id in self.type2filter_perms:
|
||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||
if ci_filter and self.use_ci_filter and not self.use_id_filter:
|
||||
sub = []
|
||||
ci_filter = Template(ci_filter).render(user=current_user)
|
||||
for i in ci_filter.split(','):
|
||||
if type_num == 1:
|
||||
if i.startswith("~") and not sub:
|
||||
queries.append(i)
|
||||
else:
|
||||
sub.append(i)
|
||||
if i.startswith("~") and not sub:
|
||||
queries.append(i)
|
||||
else:
|
||||
sub.append(i)
|
||||
if sub:
|
||||
if type_num == 1:
|
||||
queries.append(dict(operator="&", queries=sub))
|
||||
else:
|
||||
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))])
|
||||
queries.append(dict(operator="&", queries=sub))
|
||||
|
||||
sub.insert(0, "_type:{}".format(ci_type.id))
|
||||
queries.append(dict(operator="|", queries=sub))
|
||||
self.multi_type_has_ci_filter = True
|
||||
if self.type2filter_perms[ci_type.id].get('attr_filter'):
|
||||
if type_num == 1:
|
||||
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
|
||||
if not self.fl:
|
||||
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):
|
||||
self.fl[ci_type.id] = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
|
||||
if self.type2filter_perms[ci_type.id].get('id_filter') and self.use_id_filter:
|
||||
|
||||
|
@ -173,17 +146,13 @@ class Search(object):
|
|||
else:
|
||||
raise SearchError(ErrFormat.ci_type_not_found2.format(_v))
|
||||
|
||||
if type_num != len(self.type_id_list) and queries and queries[0].startswith('_type') and not is_sub:
|
||||
queries[0] = "_type:({})".format(";".join(self.type_id_list))
|
||||
|
||||
if type_id_list:
|
||||
type_ids = ",".join(type_id_list)
|
||||
if self.type_id_list:
|
||||
type_ids = ",".join(self.type_id_list)
|
||||
_query_sql = QUERY_CI_BY_TYPE.format(type_ids)
|
||||
if self.only_type_query or self.multi_type_has_ci_filter:
|
||||
if self.only_type_query:
|
||||
return _query_sql
|
||||
elif type_num > 1: # there must be instance-level access control
|
||||
return "select c_cis.id as ci_id from c_cis where c_cis.id=0"
|
||||
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
|
@ -260,7 +229,7 @@ class Search(object):
|
|||
return ret_sql.format(query_sql, "ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
elif self.type_id_list and not self.multi_type_has_ci_filter:
|
||||
elif self.type_id_list:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
||||
|
@ -285,7 +254,7 @@ class Search(object):
|
|||
def __sort_by_type(self, sort_type, query_sql):
|
||||
ret_sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id FROM ({0}) AS B {1}"
|
||||
|
||||
if self.type_id_list and not self.multi_type_has_ci_filter:
|
||||
if self.type_id_list:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
||||
|
@ -309,23 +278,16 @@ class Search(object):
|
|||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
def __sort_by_field(self, field, sort_type, query_sql):
|
||||
if field not in BUILTIN_ATTRIBUTES:
|
||||
attr = AttributeCache.get(field)
|
||||
attr_id = attr.id
|
||||
|
||||
attr = AttributeCache.get(field)
|
||||
attr_id = attr.id
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
||||
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
||||
new_table = _v_query_sql
|
||||
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
_v_query_sql = """SELECT ALIAS.ci_id, {0}.value
|
||||
FROM ({1}) AS ALIAS INNER JOIN {0} ON {0}.ci_id = ALIAS.ci_id
|
||||
WHERE {0}.attr_id = {2}""".format(table_name, query_sql, attr_id)
|
||||
new_table = _v_query_sql
|
||||
else:
|
||||
_v_query_sql = """SELECT c_cis.id AS ci_id, c_cis.{0} AS value
|
||||
FROM c_cis INNER JOIN ({1}) AS ALIAS ON ALIAS.ci_id = c_cis.id""".format(
|
||||
field[1:], query_sql)
|
||||
new_table = _v_query_sql
|
||||
|
||||
if self.only_type_query or not self.type_id_list or self.multi_type_has_ci_filter:
|
||||
if self.only_type_query or not self.type_id_list:
|
||||
return ("SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id FROM ({0}) AS C ORDER BY C.value {2} "
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
|
@ -363,9 +325,7 @@ class Search(object):
|
|||
INNER JOIN ({2}) as {3} USING(ci_id)""".format(query_sql, alias, _query_sql, alias + "A")
|
||||
|
||||
elif operator == "|" or operator == "|~":
|
||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL SELECT * FROM ({2}) as {3}".format(query_sql, alias,
|
||||
_query_sql,
|
||||
alias + "A")
|
||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
||||
|
||||
elif operator == "~":
|
||||
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
||||
|
@ -437,14 +397,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))
|
||||
|
@ -473,14 +430,14 @@ class Search(object):
|
|||
|
||||
return result
|
||||
|
||||
def __query_by_attr(self, q, queries, alias, is_sub=False):
|
||||
def __query_by_attr(self, q, queries, alias):
|
||||
k = q.split(":")[0].strip()
|
||||
v = "\:".join(q.split(":")[1:]).strip()
|
||||
v = v.replace("'", "\\'")
|
||||
v = v.replace('"', '\\"')
|
||||
field, field_type, operator, attr = self._attr_name_proc(k)
|
||||
if field == "_type":
|
||||
_query_sql = self._type_query_handler(v, queries, is_sub)
|
||||
_query_sql = self._type_query_handler(v, queries)
|
||||
|
||||
elif field == "_id":
|
||||
_query_sql = self._id_query_handler(v)
|
||||
|
@ -527,36 +484,26 @@ class Search(object):
|
|||
|
||||
return alias, _query_sql, operator
|
||||
|
||||
def __query_build_by_field(self, queries, is_first=True, only_type_query_special=True, alias='A', operator='&',
|
||||
is_sub=False):
|
||||
def __query_build_by_field(self, queries, is_first=True, only_type_query_special=True, alias='A', operator='&'):
|
||||
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)
|
||||
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)
|
||||
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias)
|
||||
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)
|
||||
|
@ -600,6 +547,7 @@ class Search(object):
|
|||
queries = handle_arg_list(self.orig_query)
|
||||
queries = self._extra_handle_query_expr(queries)
|
||||
queries = self.__confirm_type_first(queries)
|
||||
current_app.logger.debug(queries)
|
||||
|
||||
_, query_sql, _ = self.__query_build_by_field(queries)
|
||||
|
||||
|
@ -637,16 +585,13 @@ class Search(object):
|
|||
return facet_result
|
||||
|
||||
def _fl_build(self):
|
||||
if isinstance(self.fl, list):
|
||||
_fl = list()
|
||||
for f in self.fl:
|
||||
k, _, _, _ = self._attr_name_proc(f)
|
||||
if k:
|
||||
_fl.append(k)
|
||||
_fl = list()
|
||||
for f in self.fl:
|
||||
k, _, _, _ = self._attr_name_proc(f)
|
||||
if k:
|
||||
_fl.append(k)
|
||||
|
||||
return _fl
|
||||
else:
|
||||
return self.fl
|
||||
return _fl
|
||||
|
||||
def search(self):
|
||||
numfound, ci_ids = self._query_build_raw()
|
||||
|
@ -665,8 +610,6 @@ class Search(object):
|
|||
if ci_ids:
|
||||
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl, excludes=self.excludes)
|
||||
for res in response:
|
||||
if not res:
|
||||
continue
|
||||
ci_type = res.get("ci_type")
|
||||
if ci_type not in counter.keys():
|
||||
counter[ci_type] = 0
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
from collections import Counter
|
||||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
import json
|
||||
import networkx as nx
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask_login import current_user
|
||||
|
@ -16,7 +13,6 @@ from api.lib.cmdb.cache import CITypeCache
|
|||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
|
@ -29,12 +25,10 @@ from api.lib.cmdb.utils import ValueTypeMap
|
|||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
class Search(object):
|
||||
def __init__(self, root_id=None,
|
||||
def __init__(self, root_id,
|
||||
level=None,
|
||||
query=None,
|
||||
fl=None,
|
||||
|
@ -64,7 +58,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:
|
||||
|
@ -391,10 +385,9 @@ class Search(object):
|
|||
id2children[str(i)] = item['children']
|
||||
|
||||
for lv in range(1, self.level):
|
||||
type_id = type_ids[lv]
|
||||
|
||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_id):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_id])
|
||||
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
|
||||
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
|
||||
else:
|
||||
id_filter_limit = {}
|
||||
|
||||
|
@ -402,12 +395,12 @@ class Search(object):
|
|||
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
|
||||
else:
|
||||
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
|
||||
res = [[i for i in x if i[1] == type_id and (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
|
||||
int(i[0]) in id_filter_limit[key[idx]]) or
|
||||
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
|
||||
_level_ids = []
|
||||
type_id = type_ids[lv]
|
||||
id2name = _get_id2name(type_id)
|
||||
for idx, node_path in enumerate(level_ids):
|
||||
for child_id, _ in (res[idx] or []):
|
||||
|
@ -426,169 +419,3 @@ class Search(object):
|
|||
level_ids = _level_ids
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _get_src_ids(src):
|
||||
q = src.get('q') or ''
|
||||
if not q.startswith('_type:'):
|
||||
q = "_type:{},{}".format(src['type_id'], q)
|
||||
|
||||
return SearchFromDB(q, use_ci_filter=True, only_ids=True, count=100000).search()
|
||||
|
||||
@staticmethod
|
||||
def _filter_target_ids(target_ids, type_ids, q):
|
||||
if not q.startswith('_type:'):
|
||||
q = "_type:({}),{}".format(";".join(map(str, type_ids)), q)
|
||||
|
||||
ci_ids = SearchFromDB(q, ci_ids=target_ids, use_ci_filter=True, only_ids=True, count=100000).search()
|
||||
cis = CI.get_by(fl=['id', 'type_id'], only_query=True).filter(CI.id.in_(ci_ids))
|
||||
|
||||
return [(str(i.id), i.type_id) for i in cis]
|
||||
|
||||
@staticmethod
|
||||
def _path2level(src_type_id, target_type_ids, path):
|
||||
if not src_type_id or not target_type_ids:
|
||||
return abort(400, ErrFormat.relation_path_search_src_target_required)
|
||||
|
||||
graph = nx.DiGraph()
|
||||
graph.add_edges_from([(n, _path[idx + 1]) for _path in path for idx, n in enumerate(_path[:-1])])
|
||||
relation_types = defaultdict(dict)
|
||||
level2type = defaultdict(set)
|
||||
type2show_key = dict()
|
||||
for _path in path:
|
||||
for idx, node in enumerate(_path[1:]):
|
||||
level2type[idx + 1].add(node)
|
||||
|
||||
src = CITypeCache.get(_path[idx])
|
||||
target = CITypeCache.get(node)
|
||||
relation_type = RelationType.get_by(only_query=True).join(
|
||||
CITypeRelation, CITypeRelation.relation_type_id == RelationType.id).filter(
|
||||
CITypeRelation.parent_id == src.id).filter(CITypeRelation.child_id == target.id).first()
|
||||
relation_types[src.alias].update({target.alias: relation_type.name})
|
||||
|
||||
if src.id not in type2show_key:
|
||||
type2show_key[src.id] = AttributeCache.get(src.show_id or src.unique_id).name
|
||||
if target.id not in type2show_key:
|
||||
type2show_key[target.id] = AttributeCache.get(target.show_id or target.unique_id).name
|
||||
|
||||
nodes = graph.nodes()
|
||||
|
||||
return level2type, list(nodes), relation_types, type2show_key
|
||||
|
||||
def _build_graph(self, source_ids, source_type_id, level2type, target_type_ids, acl):
|
||||
type2filter_perms = dict()
|
||||
if not self.is_app_admin:
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
target_type_ids = set(target_type_ids)
|
||||
graph = nx.DiGraph()
|
||||
target_ids = []
|
||||
key = [(str(i), source_type_id) for i in source_ids]
|
||||
graph.add_nodes_from(key)
|
||||
for level in level2type:
|
||||
filter_type_ids = level2type[level]
|
||||
id_filter_limit = dict()
|
||||
for _type_id in filter_type_ids:
|
||||
if type2filter_perms.get(_type_id):
|
||||
_id_filter_limit, _ = self._get_ci_filter(type2filter_perms[_type_id])
|
||||
id_filter_limit.update(_id_filter_limit)
|
||||
|
||||
has_target = filter_type_ids & target_type_ids
|
||||
|
||||
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get([i[0] for i in key],
|
||||
REDIS_PREFIX_CI_RELATION) or []]]
|
||||
_key = []
|
||||
for idx, _id in enumerate(key):
|
||||
valid_targets = [i for i in res[idx] if i[1] in filter_type_ids and
|
||||
(not id_filter_limit or int(i[0]) in id_filter_limit)]
|
||||
_key.extend(valid_targets)
|
||||
graph.add_edges_from(zip([_id] * len(valid_targets), valid_targets))
|
||||
|
||||
if has_target:
|
||||
target_ids.extend([j[0] for i in res for j in i if j[1] in target_type_ids])
|
||||
|
||||
key = copy.deepcopy(_key)
|
||||
|
||||
return graph, target_ids
|
||||
|
||||
@staticmethod
|
||||
def _find_paths(graph, source_ids, source_type_id, target_ids, valid_path, max_depth=6):
|
||||
paths = []
|
||||
for source_id in source_ids:
|
||||
_paths = nx.all_simple_paths(graph,
|
||||
source=(source_id, source_type_id),
|
||||
target=target_ids,
|
||||
cutoff=max_depth)
|
||||
for __path in _paths:
|
||||
if tuple([i[1] for i in __path]) in valid_path:
|
||||
paths.append([i[0] for i in __path])
|
||||
|
||||
return paths
|
||||
|
||||
@staticmethod
|
||||
def _wrap_path_result(paths, types, valid_path, target_types, type2show_key):
|
||||
ci_ids = [j for i in paths for j in i]
|
||||
|
||||
response, _, _, _, _, _ = SearchFromDB("_type:({})".format(";".join(map(str, types))),
|
||||
use_ci_filter=False,
|
||||
ci_ids=list(map(int, ci_ids)),
|
||||
count=1000000).search()
|
||||
id2ci = {str(i.get('_id')): i if i['_type'] in target_types else {
|
||||
type2show_key[i['_type']]: i[type2show_key[i['_type']]],
|
||||
"ci_type_alias": i["ci_type_alias"],
|
||||
"_type": i["_type"],
|
||||
} for i in response}
|
||||
|
||||
result = defaultdict(list)
|
||||
counter = defaultdict(int)
|
||||
|
||||
for path in paths:
|
||||
key = "-".join([id2ci.get(i, {}).get('ci_type_alias') or '' for i in path])
|
||||
if tuple([id2ci.get(i, {}).get('_type') for i in path]) in valid_path:
|
||||
counter[key] += 1
|
||||
result[key].append(path)
|
||||
|
||||
return result, counter, id2ci
|
||||
|
||||
def search_by_path(self, source, target, path):
|
||||
"""
|
||||
|
||||
:param source: {type_id: id, q: expr}
|
||||
:param target: {type_ids: [id], q: expr}
|
||||
:param path: [source_type_id, ..., target_type_id], use type id
|
||||
:return:
|
||||
"""
|
||||
acl = ACLManager('cmdb')
|
||||
if not self.is_app_admin:
|
||||
res = {i['name'] for i in acl.get_resources(ResourceTypeEnum.CI_TYPE)}
|
||||
for type_id in (source.get('type_id') and [source['type_id']] or []) + (target.get('type_ids') or []):
|
||||
_type = CITypeCache.get(type_id)
|
||||
if _type and _type.name not in res:
|
||||
return abort(403, ErrFormat.no_permission.format(_type.alias, PermEnum.READ))
|
||||
|
||||
target['type_ids'] = [i[-1] for i in path]
|
||||
level2type, types, relation_types, type2show_key = self._path2level(
|
||||
source.get('type_id'), target.get('type_ids'), path)
|
||||
if not level2type:
|
||||
return [], {}, 0, self.page, 0, {}, {}
|
||||
|
||||
source_ids = self._get_src_ids(source)
|
||||
|
||||
graph, target_ids = self._build_graph(source_ids, source['type_id'], level2type, target['type_ids'], acl)
|
||||
target_ids = self._filter_target_ids(target_ids, target['type_ids'], target.get('q') or '')
|
||||
paths = self._find_paths(graph,
|
||||
source_ids,
|
||||
source['type_id'],
|
||||
set(target_ids),
|
||||
{tuple(i): 1 for i in path})
|
||||
|
||||
numfound = len(paths)
|
||||
paths = paths[(self.page - 1) * self.count:self.page * self.count]
|
||||
response, counter, id2ci = self._wrap_path_result(paths,
|
||||
types,
|
||||
{tuple(i): 1 for i in path},
|
||||
set(target.get('type_ids') or []),
|
||||
type2show_key)
|
||||
return response, counter, len(paths), self.page, numfound, id2ci, relation_types, type2show_key
|
||||
|
|
|
@ -55,6 +55,7 @@ def str2datetime(x):
|
|||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
|
||||
class ValueTypeMap(object):
|
||||
deserialize = {
|
||||
ValueTypeEnum.INT: string2int,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import importlib.util
|
||||
import imp
|
||||
|
||||
import copy
|
||||
import jinja2
|
||||
|
@ -97,8 +97,6 @@ class AttributeValueManager(object):
|
|||
deserialize = ValueTypeMap.deserialize[value_type]
|
||||
try:
|
||||
v = deserialize(value)
|
||||
if value_type in (ValueTypeEnum.DATE, ValueTypeEnum.DATETIME):
|
||||
return str(v)
|
||||
return v
|
||||
except ValueDeserializeError as e:
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, e))
|
||||
|
@ -136,7 +134,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 +144,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:
|
||||
|
@ -198,11 +196,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()
|
||||
|
@ -237,10 +235,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 {}
|
||||
|
@ -271,8 +266,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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,15 +48,11 @@ 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"],
|
||||
},
|
||||
{"page": "IPAM", "page_cn": "IPAM", "perms": ["read"]},
|
||||
{"page": "DCIM", "page_cn": "数据中心", "perms": ["read"]},
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import current_app
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
|
@ -33,21 +32,11 @@ class DBMixin(object):
|
|||
|
||||
for k in kwargs:
|
||||
if hasattr(cls.cls, k):
|
||||
if isinstance(kwargs[k], list):
|
||||
query = query.filter(getattr(cls.cls, k).in_(kwargs[k]))
|
||||
if count_query:
|
||||
_query = _query.filter(getattr(cls.cls, k).in_(kwargs[k]))
|
||||
else:
|
||||
if "*" in str(kwargs[k]):
|
||||
query = query.filter(getattr(cls.cls, k).ilike(kwargs[k].replace('*', '%')))
|
||||
if count_query:
|
||||
_query = _query.filter(getattr(cls.cls, k).ilike(kwargs[k].replace('*', '%')))
|
||||
else:
|
||||
query = query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
if count_query:
|
||||
_query = _query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
query = query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
if count_query:
|
||||
_query = _query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
|
||||
if reverse in current_app.config.get('BOOL_TRUE'):
|
||||
if reverse:
|
||||
query = query.order_by(cls.cls.id.desc())
|
||||
|
||||
if only_query and not count_query:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -376,7 +376,7 @@ class AuditCRUD(object):
|
|||
origin=origin, current=current, extra=extra, source=source.value)
|
||||
|
||||
@classmethod
|
||||
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None, ip=None, browser=None):
|
||||
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
|
||||
if _id is not None:
|
||||
existed = AuditLoginLog.get_by_id(_id)
|
||||
if existed is not None:
|
||||
|
@ -387,9 +387,8 @@ class AuditCRUD(object):
|
|||
is_ok=is_ok,
|
||||
description=description,
|
||||
logout_at=logout_at,
|
||||
ip=(ip or request.headers.get('X-Forwarded-For') or
|
||||
request.headers.get('X-Real-IP') or request.remote_addr or '').split(',')[0],
|
||||
browser=browser or request.headers.get('User-Agent'),
|
||||
ip=request.headers.get('X-Real-IP') or request.remote_addr,
|
||||
browser=request.headers.get('User-Agent'),
|
||||
channel=request.values.get('channel', 'web'),
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -71,7 +71,7 @@ class PermissionCRUD(object):
|
|||
|
||||
@classmethod
|
||||
def get_all2(cls, resource_name, resource_type_name, app_id):
|
||||
rt = ResourceType.get_by(name=resource_type_name, app_id=app_id, first=True, to_dict=False)
|
||||
rt = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
|
||||
rt or abort(404, ErrFormat.resource_type_not_found.format(resource_type_name))
|
||||
|
||||
r = Resource.get_by(name=resource_name, resource_type_id=rt.id, app_id=app_id, first=True, to_dict=False)
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import base64
|
||||
from typing import Set
|
||||
|
||||
import elasticsearch
|
||||
import redis
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -253,7 +253,6 @@ class CI(Model):
|
|||
status = db.Column(db.Enum(*CIStatusEnum.all(), name="status"))
|
||||
heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
|
||||
is_auto_discovery = db.Column('a', db.Boolean, default=False)
|
||||
updated_by = db.Column(db.String(64))
|
||||
|
||||
ci_type = db.relationship("CIType", backref="c_cis.type_id")
|
||||
|
||||
|
@ -476,7 +475,6 @@ class PreferenceShowAttributes(Model):
|
|||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
builtin_attr = db.Column(db.String(256), nullable=True)
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
is_fixed = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
@ -536,7 +534,6 @@ class CustomDashboard(Model):
|
|||
|
||||
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'))
|
||||
builtin_attr = db.Column(db.String(256), nullable=True)
|
||||
level = db.Column(db.Integer)
|
||||
|
||||
options = db.Column(db.JSON)
|
||||
|
@ -669,52 +666,3 @@ class InnerKV(Model):
|
|||
|
||||
key = db.Column(db.String(128), index=True)
|
||||
value = db.Column(db.Text)
|
||||
|
||||
|
||||
class IPAMSubnetScan(Model):
|
||||
__tablename__ = "c_ipam_subnet_scans"
|
||||
|
||||
ci_id = db.Column(db.Integer, index=True, nullable=False)
|
||||
scan_enabled = db.Column(db.Boolean, default=True)
|
||||
rule_updated_at = db.Column(db.DateTime)
|
||||
last_scan_time = db.Column(db.DateTime)
|
||||
|
||||
# scan rules
|
||||
agent_id = db.Column(db.String(8), index=True)
|
||||
cron = db.Column(db.String(128))
|
||||
|
||||
|
||||
class IPAMSubnetScanHistory(Model2):
|
||||
__tablename__ = "c_ipam_subnet_scan_histories"
|
||||
|
||||
subnet_scan_id = db.Column(db.Integer, index=True)
|
||||
exec_id = db.Column(db.String(64), index=True)
|
||||
cidr = db.Column(db.String(18), index=True)
|
||||
start_at = db.Column(db.DateTime)
|
||||
end_at = db.Column(db.DateTime)
|
||||
status = db.Column(db.Integer, default=0) # 0 is ok
|
||||
stdout = db.Column(db.Text)
|
||||
ip_num = db.Column(db.Integer)
|
||||
ips = db.Column(db.JSON) # keep only the last 10 records
|
||||
|
||||
|
||||
class IPAMOperationHistory(Model2):
|
||||
__tablename__ = "c_ipam_operation_histories"
|
||||
|
||||
from api.lib.cmdb.ipam.const import OperateTypeEnum
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
cidr = db.Column(db.String(18), index=True)
|
||||
operate_type = db.Column(db.Enum(*OperateTypeEnum.all()))
|
||||
description = db.Column(db.Text)
|
||||
|
||||
|
||||
class DCIMOperationHistory(Model2):
|
||||
__tablename__ = "c_dcim_operation_histories"
|
||||
|
||||
from api.lib.cmdb.dcim.const import OperateTypeEnum
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
rack_id = db.Column(db.Integer, index=True)
|
||||
ci_id = db.Column(db.Integer, index=True)
|
||||
operate_type = db.Column(db.Enum(*OperateTypeEnum.all()))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import redis_lock
|
||||
from flask import current_app
|
||||
from flask import has_request_context
|
||||
from flask_login import login_user
|
||||
|
||||
import api.lib.cmdb.ci
|
||||
|
@ -54,9 +53,8 @@ def ci_cache(ci_id, operate_type, record_id):
|
|||
current_app.logger.info("{0} flush..........".format(ci_id))
|
||||
|
||||
if operate_type:
|
||||
if not has_request_context():
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
_, enum_map = CITypeAttributeManager.get_attr_names_label_enum(ci_dict.get('_type'))
|
||||
payload = dict()
|
||||
|
@ -145,7 +143,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 {}
|
||||
|
||||
|
@ -186,9 +184,8 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
if not has_request_context():
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
for parent in parent_dict:
|
||||
parent_ci_type_name, _attr_name = parent.strip()[1:].split('.', 1)
|
||||
|
@ -223,7 +220,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 {}
|
||||
|
||||
|
@ -275,9 +272,8 @@ def ci_type_attribute_order_rebuild(type_id, uid):
|
|||
def calc_computed_attribute(attr_id, uid):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
if not has_request_context():
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
cim = CIManager()
|
||||
for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False):
|
||||
|
@ -374,25 +370,3 @@ def build_relations_for_ad_accept(adc, ci_id, ad_key2attr):
|
|||
source=RelationSourceEnum.AUTO_DISCOVERY)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@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)
|
||||
|
|
Binary file not shown.
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-11-26 18:54+0800\n"
|
||||
"POT-Creation-Date: 2024-08-20 13:47+0800\n"
|
||||
"PO-Revision-Date: 2023-12-25 20:21+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh\n"
|
||||
|
@ -92,14 +92,6 @@ msgstr "您没有操作权限!"
|
|||
msgid "Only the creator or administrator has permission!"
|
||||
msgstr "只有创建人或者管理员才有权限!"
|
||||
|
||||
#: api/lib/cmdb/const.py:133
|
||||
msgid "Update Time"
|
||||
msgstr "更新时间"
|
||||
|
||||
#: api/lib/cmdb/const.py:134
|
||||
msgid "Updated By"
|
||||
msgstr "更新人"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:9
|
||||
msgid "CI Model"
|
||||
msgstr "模型配置"
|
||||
|
@ -482,11 +474,11 @@ msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
|
|||
|
||||
#: api/lib/cmdb/resp_format.py:150
|
||||
msgid "CMDB data reconciliation results"
|
||||
msgstr "CMDB数据合规检查结果"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:151
|
||||
msgid "Number of {} illegal: {}"
|
||||
msgstr "{} 不合规数: {}"
|
||||
msgstr ""
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:153
|
||||
msgid "Topology view {} already exists"
|
||||
|
@ -500,62 +492,6 @@ msgstr "拓扑视图分组 {} 已经存在"
|
|||
msgid "The group cannot be deleted because the topology view already exists"
|
||||
msgstr "因为该分组下定义了拓扑视图,不能删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:158
|
||||
msgid "Both the source model and the target model must be selected"
|
||||
msgstr "源模型和目标模型不能为空!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:160
|
||||
msgid "The names of built-in models cannot be changed"
|
||||
msgstr "内置模型的名字不能修改"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:162
|
||||
msgid "The subnet model {} does not exist"
|
||||
msgstr "子网模型 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:163
|
||||
msgid "The IP Address model {} does not exist"
|
||||
msgstr "IP地址模型 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:164
|
||||
msgid "CIDR {} is an invalid notation"
|
||||
msgstr "CIDR {} 写法不正确!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:165
|
||||
msgid "Invalid CIDR: {}, available subnets: {}"
|
||||
msgstr "无效的CIDR: {}, 可用的子网: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:166
|
||||
msgid "Invalid subnet prefix length: {}"
|
||||
msgstr "无效的子网前缀长度: {}"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:167
|
||||
msgid "parent node cidr must be required"
|
||||
msgstr "必须要有父节点"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:168
|
||||
msgid "{} and {} overlap"
|
||||
msgstr "{} 和 {} 有重叠"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:169 api/lib/cmdb/resp_format.py:171
|
||||
msgid "Cannot delete because child nodes exist"
|
||||
msgstr "因为子节点已经存在,不能删除"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:170
|
||||
msgid "Subnet is not found"
|
||||
msgstr "子网不存在"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:174
|
||||
msgid "The dcim model {} does not exist"
|
||||
msgstr "DCIM模型 {} 不存在!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:175
|
||||
msgid "Irregularities in Rack Units"
|
||||
msgstr "机架U位异常!"
|
||||
|
||||
#: api/lib/cmdb/resp_format.py:176
|
||||
msgid "The device's position is greater than the rack unit height"
|
||||
msgstr "有设备的位置大于机柜的U数!"
|
||||
|
||||
#: api/lib/common_setting/resp_format.py:8
|
||||
msgid "Company info already existed"
|
||||
msgstr "公司信息已存在,无法创建!"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
import jwt
|
||||
import six
|
||||
from flask import abort
|
||||
|
@ -16,12 +17,10 @@ from api.lib.decorator import args_required
|
|||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.audit import AuditCRUD
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import RoleCache
|
||||
from api.lib.perm.acl.cache import User
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.role import RoleRelationCRUD
|
||||
from api.lib.perm.auth import auth_abandoned
|
||||
from api.lib.perm.auth import auth_with_app_token
|
||||
from api.models.acl import Role
|
||||
|
@ -125,17 +124,10 @@ class AuthWithKeyView(APIView):
|
|||
if not user.get('username'):
|
||||
user['username'] = user.get('name')
|
||||
|
||||
result = dict(user=user,
|
||||
authenticated=authenticated,
|
||||
rid=role and role.id,
|
||||
can_proxy=can_proxy)
|
||||
|
||||
if request.values.get('need_parentRoles') in current_app.config.get('BOOL_TRUE'):
|
||||
app_id = AppCache.get(request.values.get('app_id'))
|
||||
parent_ids = RoleRelationCRUD.recursive_parent_ids(role and role.id, app_id and app_id.id)
|
||||
result['user']['parentRoles'] = [RoleCache.get(rid).name for rid in set(parent_ids) if RoleCache.get(rid)]
|
||||
|
||||
return self.jsonify(result)
|
||||
return self.jsonify(user=user,
|
||||
authenticated=authenticated,
|
||||
rid=role and role.id,
|
||||
can_proxy=can_proxy)
|
||||
|
||||
|
||||
class AuthWithTokenView(APIView):
|
||||
|
@ -192,8 +184,6 @@ class LogoutView(APIView):
|
|||
def post(self):
|
||||
logout_user()
|
||||
|
||||
AuditCRUD.add_login_log(None, None, None,
|
||||
_id=session.get('LOGIN_ID') or request.values.get('LOGIN_ID'),
|
||||
logout_at=datetime.datetime.now())
|
||||
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
|
||||
|
||||
self.jsonify(code=200)
|
||||
|
|
|
@ -11,7 +11,6 @@ from flask_login import current_user
|
|||
from api.lib.decorator import args_required
|
||||
from api.lib.decorator import args_validate
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import AuditCRUD
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
|
@ -49,13 +48,6 @@ class GetUserInfoView(APIView):
|
|||
role=dict(permissions=user_info.get('parents')),
|
||||
avatar=user_info.get('avatar'))
|
||||
|
||||
if request.values.get('channel'):
|
||||
_id = AuditCRUD.add_login_log(name, True, ErrFormat.login_succeed,
|
||||
ip=request.values.get('ip'),
|
||||
browser=request.values.get('browser'))
|
||||
session['LOGIN_ID'] = _id
|
||||
result['LOGIN_ID'] = _id
|
||||
|
||||
current_app.logger.info("get user info for3: {}".format(result))
|
||||
return self.jsonify(result=result)
|
||||
|
||||
|
|
|
@ -24,13 +24,11 @@ from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
|
|||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.ipam.subnet import SubnetManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search as ci_search
|
||||
from api.lib.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
|
||||
|
@ -295,16 +293,9 @@ class AutoDiscoveryRuleSyncView(APIView):
|
|||
|
||||
return self.jsonify(rules=rules, last_update_at=last_update_at)
|
||||
|
||||
rules, last_update_at1 = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
|
||||
rules, last_update_at = 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 = [], ""
|
||||
|
||||
return self.jsonify(rules=rules,
|
||||
subnet_scan_rules=subnet_scan_rules,
|
||||
last_update_at=max(last_update_at1 or "", last_update_at2 or ""))
|
||||
return self.jsonify(rules=rules, last_update_at=last_update_at)
|
||||
|
||||
|
||||
class AutoDiscoveryRuleSyncHistoryView(APIView):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
|
||||
import time
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
@ -64,42 +65,6 @@ class CIRelationSearchView(APIView):
|
|||
result=response)
|
||||
|
||||
|
||||
class CIRelationSearchPathView(APIView):
|
||||
url_prefix = ("/ci_relations/path/s", "/ci_relations/path/search")
|
||||
|
||||
@args_required("source", "target", "path")
|
||||
def post(self):
|
||||
"""@params: page: page number
|
||||
page_size | count: page size
|
||||
source: source CIType, e.g. {type_id: 1, q: `search expr`}
|
||||
target: target CIType, e.g. {type_ids: [2], q: `search expr`}
|
||||
path: Path from the Source CIType to the Target CIType, e.g. [1, ..., 2]
|
||||
"""
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
|
||||
|
||||
source = request.values.get("source")
|
||||
target = request.values.get("target")
|
||||
path = request.values.get("path")
|
||||
|
||||
s = Search(page=page, count=count)
|
||||
try:
|
||||
(response, counter, total, page, numfound, id2ci,
|
||||
relation_types, type2show_key) = s.search_by_path(source, target, path)
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
|
||||
return self.jsonify(numfound=numfound,
|
||||
total=total,
|
||||
page=page,
|
||||
counter=counter,
|
||||
paths=response,
|
||||
id2ci=id2ci,
|
||||
relation_types=relation_types,
|
||||
type2show_key=type2show_key)
|
||||
|
||||
|
||||
class CIRelationStatisticsView(APIView):
|
||||
url_prefix = "/ci_relations/statistics"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from api.lib.cmdb.ci_type import CITypeManager
|
|||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
|
@ -16,7 +17,7 @@ from api.lib.decorator import args_required
|
|||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import has_perm_from_args
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.lib.perm.acl.acl import role_required
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
@ -41,19 +42,6 @@ class GetParentsView(APIView):
|
|||
return self.jsonify(parents=CITypeRelationManager.get_parents(child_id))
|
||||
|
||||
|
||||
class CITypeRelationPathView(APIView):
|
||||
url_prefix = ("/ci_type_relations/path",)
|
||||
|
||||
@args_required("source_type_id", "target_type_ids")
|
||||
def get(self):
|
||||
source_type_id = request.values.get("source_type_id")
|
||||
target_type_ids = handle_arg_list(request.values.get("target_type_ids"))
|
||||
|
||||
paths = CITypeRelationManager.find_path(source_type_id, target_type_ids)
|
||||
|
||||
return self.jsonify(paths=paths)
|
||||
|
||||
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,30 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.dcim.history import OperateHistoryManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class DCIMOperateHistoryView(APIView):
|
||||
url_prefix = ("/dcim/history/operate",)
|
||||
|
||||
@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 get(self):
|
||||
page = get_page(request.values.pop("page", 1))
|
||||
page_size = get_page_size(request.values.pop("page_size", None))
|
||||
operate_type = handle_arg_list(request.values.pop('operate_type', []))
|
||||
if operate_type:
|
||||
request.values["operate_type"] = operate_type
|
||||
|
||||
numfound, result, id2ci, type2show_key = OperateHistoryManager.search(page, page_size, **request.values)
|
||||
|
||||
return self.jsonify(numfound=numfound, result=result, id2ci=id2ci, type2show_key=type2show_key)
|
|
@ -1,35 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.dcim.idc import IDCManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class IDCView(APIView):
|
||||
url_prefix = ("/dcim/idc", "/dcim/idc/<int:_id>")
|
||||
|
||||
@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):
|
||||
parent_id = request.values.pop("parent_id")
|
||||
|
||||
return self.jsonify(ci_id=IDCManager().add(parent_id, **request.values))
|
||||
|
||||
@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 put(self, _id):
|
||||
IDCManager().update(_id, **request.values)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
||||
|
||||
@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 delete(self, _id):
|
||||
IDCManager().delete(_id)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
|
@ -1,102 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class RackView(APIView):
|
||||
url_prefix = ("/dcim/rack", "/dcim/rack/<int:_id>")
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("parent_id")
|
||||
def post(self):
|
||||
parent_id = request.values.pop("parent_id")
|
||||
|
||||
return self.jsonify(ci_id=RackManager().add(parent_id, **request.values))
|
||||
|
||||
@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 put(self, _id):
|
||||
RackManager().update(_id, **request.values)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
||||
|
||||
@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 delete(self, _id):
|
||||
RackManager().delete(_id)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
||||
|
||||
|
||||
class RackDetailView(APIView):
|
||||
url_prefix = ("/dcim/rack/<int:rack_id>/device/<int:device_id>",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required(RackBuiltinAttributes.U_START)
|
||||
def post(self, rack_id, device_id):
|
||||
u_start = request.values.pop(RackBuiltinAttributes.U_START)
|
||||
u_count = request.values.get(RackBuiltinAttributes.U_COUNT)
|
||||
|
||||
RackManager().add_device(rack_id, device_id, u_start, u_count)
|
||||
|
||||
return self.jsonify(rack_id=rack_id, device_id=device_id)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("to_u_start")
|
||||
def put(self, rack_id, device_id):
|
||||
to_u_start = request.values.pop("to_u_start")
|
||||
|
||||
RackManager().move_device(rack_id, device_id, to_u_start)
|
||||
|
||||
return self.jsonify(rack_id=rack_id, device_id=device_id, to_u_start=to_u_start)
|
||||
|
||||
@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 delete(self, rack_id, device_id):
|
||||
RackManager().remove_device(rack_id, device_id)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
||||
|
||||
class RackDeviceMigrateView(APIView):
|
||||
url_prefix = ("/dcim/rack/<int:rack_id>/device/<int:device_id>/migrate",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("to_rack_id")
|
||||
@args_required("to_u_start")
|
||||
def put(self, rack_id, device_id):
|
||||
to_rack_id = request.values.pop("to_rack_id")
|
||||
to_u_start = request.values.pop("to_u_start")
|
||||
|
||||
RackManager().migrate_device(rack_id, device_id, to_rack_id, to_u_start)
|
||||
|
||||
return self.jsonify(rack_id=rack_id,
|
||||
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)
|
|
@ -1,33 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.dcim.region import RegionManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class RegionView(APIView):
|
||||
url_prefix = ("/dcim/region", "/dcim/region/<int:_id>")
|
||||
|
||||
@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):
|
||||
return self.jsonify(ci_id=RegionManager().add(**request.values))
|
||||
|
||||
@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 put(self, _id):
|
||||
RegionManager().update(_id, **request.values)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
||||
|
||||
@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 delete(self, _id):
|
||||
RegionManager().delete(_id)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
|
@ -1,43 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.dcim.server_room import ServerRoomManager
|
||||
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
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class ServerRoomView(APIView):
|
||||
url_prefix = ("/dcim/server_room", "/dcim/server_room/<int:_id>", "/dcim/server_room/<int:_id>/racks")
|
||||
|
||||
def get(self, _id):
|
||||
q = request.values.get('q')
|
||||
counter, result = ServerRoomManager.get_racks(_id, q)
|
||||
|
||||
return self.jsonify(counter=counter, result=result)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
@args_required("parent_id")
|
||||
def post(self):
|
||||
parent_id = request.values.pop("parent_id")
|
||||
|
||||
return self.jsonify(ci_id=ServerRoomManager().add(parent_id, **request.values))
|
||||
|
||||
@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 put(self, _id):
|
||||
ServerRoomManager().update(_id, **request.values)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
||||
|
||||
@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 delete(self, _id):
|
||||
ServerRoomManager().delete(_id)
|
||||
|
||||
return self.jsonify(ci_id=_id)
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.cmdb.dcim.tree_view import TreeViewManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class DCIMTreeView(APIView):
|
||||
url_prefix = "/dcim/tree_view"
|
||||
|
||||
@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 get(self):
|
||||
result, type2name = TreeViewManager.get()
|
||||
|
||||
return self.jsonify(result=result, type2name=type2name)
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ipam.address import IpAddressManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class IPAddressView(APIView):
|
||||
url_prefix = ("/ipam/address",)
|
||||
|
||||
@args_required("parent_id")
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
parent_id = request.args.get("parent_id")
|
||||
|
||||
numfound, result = IpAddressManager.list_ip_address(parent_id)
|
||||
|
||||
return self.jsonify(numfound=numfound, result=result)
|
||||
|
||||
@args_required("ips")
|
||||
@args_required("assign_status", value_required=False)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def post(self):
|
||||
ips = handle_arg_list(request.values.pop("ips"))
|
||||
parent_id = request.values.pop("parent_id", None)
|
||||
cidr = request.values.pop("cidr", None)
|
||||
|
||||
IpAddressManager().assign_ips(ips, parent_id, cidr, **request.values)
|
||||
|
||||
return self.jsonify(code=200)
|
|
@ -1,53 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ipam.history import OperateHistoryManager
|
||||
from api.lib.cmdb.ipam.history import ScanHistoryManager
|
||||
from api.lib.common_setting.decorator import perms_role_required
|
||||
from api.lib.common_setting.role_perm_base import CMDBApp
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.resource import APIView
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class IPAMOperateHistoryView(APIView):
|
||||
url_prefix = ("/ipam/history/operate",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.pop("page", 1))
|
||||
page_size = get_page_size(request.values.pop("page_size", None))
|
||||
operate_type = handle_arg_list(request.values.pop('operate_type', []))
|
||||
if operate_type:
|
||||
request.values["operate_type"] = operate_type
|
||||
|
||||
numfound, result = OperateHistoryManager.search(page, page_size, **request.values)
|
||||
|
||||
return self.jsonify(numfound=numfound, result=result)
|
||||
|
||||
|
||||
class IPAMScanHistoryView(APIView):
|
||||
url_prefix = ("/ipam/history/scan",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
page = get_page(request.values.pop("page", 1))
|
||||
page_size = get_page_size(request.values.pop("page_size", None))
|
||||
|
||||
numfound, result = ScanHistoryManager.search(page, page_size, **request.values)
|
||||
|
||||
return self.jsonify(numfound=numfound, result=result)
|
||||
|
||||
@args_required("exec_id")
|
||||
def post(self):
|
||||
|
||||
ScanHistoryManager().add(**request.values)
|
||||
|
||||
return self.jsonify(code=200)
|
|
@ -1,24 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ipam.stats import Stats
|
||||
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
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class IPAMStatsView(APIView):
|
||||
url_prefix = '/ipam/stats'
|
||||
|
||||
@args_required("parent_id")
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self):
|
||||
parent_id = request.values.get("parent_id")
|
||||
|
||||
return self.jsonify(Stats().summary(parent_id))
|
|
@ -1,75 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ipam.subnet import SubnetManager
|
||||
from api.lib.cmdb.ipam.subnet import SubnetScopeManager
|
||||
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
|
||||
|
||||
app_cli = CMDBApp()
|
||||
|
||||
|
||||
class SubnetView(APIView):
|
||||
url_prefix = ("/ipam/subnet", "/ipam/subnet/hosts", "/ipam/subnet/<int:_id>")
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def get(self, _id=None):
|
||||
if "hosts" in request.url:
|
||||
return self.jsonify(SubnetManager.get_hosts(request.values.get('cidr')))
|
||||
|
||||
if _id is not None:
|
||||
return self.jsonify(SubnetManager().get_by_id(_id))
|
||||
|
||||
result, type2name = SubnetManager().tree_view()
|
||||
|
||||
return self.jsonify(result=result, type2name=type2name)
|
||||
|
||||
@args_required("cidr")
|
||||
@args_required("parent_id", value_required=False)
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def post(self):
|
||||
cidr = request.values.pop("cidr")
|
||||
parent_id = request.values.pop("parent_id")
|
||||
agent_id = request.values.pop("agent_id", None)
|
||||
cron = request.values.pop("cron", None)
|
||||
|
||||
return self.jsonify(SubnetManager().add(cidr, parent_id, agent_id, cron, **request.values))
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def put(self, _id):
|
||||
return self.jsonify(id=SubnetManager().update(_id, **request.values))
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
return self.jsonify(id=SubnetManager().delete(_id))
|
||||
|
||||
|
||||
class SubnetScopeView(APIView):
|
||||
url_prefix = ("/ipam/scope", "/ipam/scope/<int:_id>")
|
||||
|
||||
@args_required("parent_id", value_required=False)
|
||||
@args_required("name")
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def post(self):
|
||||
parent_id = request.values.pop("parent_id")
|
||||
name = request.values.pop("name")
|
||||
|
||||
return self.jsonify(SubnetScopeManager().add(parent_id, name))
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def put(self, _id):
|
||||
return self.jsonify(id=SubnetScopeManager().update(_id, **request.values))
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def delete(self, _id):
|
||||
return self.jsonify(id=SubnetScopeManager.delete(_id))
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -16,7 +16,7 @@ Flask-Cors==4.0.0
|
|||
Flask-Login>=0.6.2
|
||||
Flask-Migrate==2.5.2
|
||||
Flask-RESTful==0.3.10
|
||||
Flask-SQLAlchemy==3.0.5
|
||||
Flask-SQLAlchemy==2.5.0
|
||||
future==0.18.3
|
||||
gunicorn==21.0.1
|
||||
hvac==2.0.0
|
||||
|
@ -56,6 +56,3 @@ colorama>=0.4.6
|
|||
lz4>=4.3.2
|
||||
python-magic==0.4.27
|
||||
jsonpath==0.82.2
|
||||
networkx>=3.1
|
||||
ipaddress>=1.0.23
|
||||
ruff==0.8.3
|
||||
|
|
|
@ -39,9 +39,9 @@ SQLALCHEMY_ENGINE_OPTIONS = {
|
|||
|
||||
# # cache
|
||||
CACHE_TYPE = 'redis'
|
||||
CACHE_REDIS_HOST = env.str('CACHE_REDIS_HOST', default='redis')
|
||||
CACHE_REDIS_PORT = env.str('CACHE_REDIS_PORT', default='6379')
|
||||
CACHE_REDIS_PASSWORD = env.str('CACHE_REDIS_PASSWORD', default='')
|
||||
CACHE_REDIS_HOST = '127.0.0.1'
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_REDIS_PASSWORD = ''
|
||||
CACHE_KEY_PREFIX = 'CMDB::'
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
src: url('iconfont.woff2?t=1725331691589') format('woff2'),
|
||||
url('iconfont.woff?t=1725331691589') format('woff'),
|
||||
url('iconfont.ttf?t=1725331691589') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -13,346 +13,6 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.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";
|
||||
}
|
||||
|
||||
.veops-front:before {
|
||||
content: "\ea03";
|
||||
}
|
||||
|
||||
.veops-xianggang:before {
|
||||
content: "\ea01";
|
||||
}
|
||||
|
||||
.a-veops-device2:before {
|
||||
content: "\ea00";
|
||||
}
|
||||
|
||||
.a-veops-room1:before {
|
||||
content: "\e9ff";
|
||||
}
|
||||
|
||||
.veops-IDC:before {
|
||||
content: "\e9fe";
|
||||
}
|
||||
|
||||
.veops-region:before {
|
||||
content: "\e9fd";
|
||||
}
|
||||
|
||||
.veops-device:before {
|
||||
content: "\e9fb";
|
||||
}
|
||||
|
||||
.veops-cabinet:before {
|
||||
content: "\e9fc";
|
||||
}
|
||||
|
||||
.veops-data_center:before {
|
||||
content: "\e9f9";
|
||||
}
|
||||
|
||||
.ops-setting-holidays:before {
|
||||
content: "\e9fa";
|
||||
}
|
||||
|
||||
.ops-itsm-logs:before {
|
||||
content: "\e9f8";
|
||||
}
|
||||
|
||||
.ops-setting-workday:before {
|
||||
content: "\e9f6";
|
||||
}
|
||||
|
||||
.ops-setting-holiday:before {
|
||||
content: "\e9f7";
|
||||
}
|
||||
|
||||
.ops-setting-festival:before {
|
||||
content: "\e9f5";
|
||||
}
|
||||
|
||||
.itsm-calc:before {
|
||||
content: "\e9f4";
|
||||
}
|
||||
|
||||
.itsm-reports_4:before {
|
||||
content: "\e9f3";
|
||||
}
|
||||
|
||||
.veops-folder:before {
|
||||
content: "\e9f2";
|
||||
}
|
||||
|
||||
.veops-entire_network_:before {
|
||||
content: "\e9f1";
|
||||
}
|
||||
|
||||
.veops-subnet:before {
|
||||
content: "\e9f0";
|
||||
}
|
||||
|
||||
.veops-map_view:before {
|
||||
content: "\e9ef";
|
||||
}
|
||||
|
||||
.veops-recycle:before {
|
||||
content: "\e9ee";
|
||||
}
|
||||
|
||||
.veops-catalog:before {
|
||||
content: "\e9ed";
|
||||
}
|
||||
|
||||
.veops-ipam:before {
|
||||
content: "\e9ec";
|
||||
}
|
||||
|
||||
.cmdb-calc:before {
|
||||
content: "\e9eb";
|
||||
}
|
||||
|
||||
.ai-users:before {
|
||||
content: "\e9ea";
|
||||
}
|
||||
|
||||
.ai-tokens:before {
|
||||
content: "\e9e9";
|
||||
}
|
||||
|
||||
.oneterm-mysql:before {
|
||||
content: "\e9e8";
|
||||
}
|
||||
|
||||
.oneterm-redis:before {
|
||||
content: "\e9e7";
|
||||
}
|
||||
|
||||
.veops-sign_out:before {
|
||||
content: "\e9e6";
|
||||
}
|
||||
|
||||
.veops-company:before {
|
||||
content: "\e9e5";
|
||||
}
|
||||
|
||||
.veops-emails:before {
|
||||
content: "\e9e4";
|
||||
}
|
||||
|
||||
.veops-switch:before {
|
||||
content: "\e9e3";
|
||||
}
|
||||
|
||||
.qiyeweixin:before {
|
||||
content: "\e9e2";
|
||||
}
|
||||
|
||||
.veops-progress:before {
|
||||
content: "\e9e1";
|
||||
}
|
||||
|
||||
.veops-completed:before {
|
||||
content: "\e9e0";
|
||||
}
|
||||
|
||||
.itsm-ticketTime:before {
|
||||
content: "\e9df";
|
||||
}
|
||||
|
||||
.veops-notification:before {
|
||||
content: "\e9dc";
|
||||
}
|
||||
|
||||
.a-veops-account1:before {
|
||||
content: "\e9dd";
|
||||
}
|
||||
|
||||
.veops-personal:before {
|
||||
content: "\e9de";
|
||||
}
|
||||
|
||||
.itsm-customer_satisfaction2:before {
|
||||
content: "\e9da";
|
||||
}
|
||||
|
||||
.itsm-over2:before {
|
||||
content: "\e9db";
|
||||
}
|
||||
|
||||
.veops-search1:before {
|
||||
content: "\e9d9";
|
||||
}
|
||||
|
||||
.itsm-customer_satisfaction:before {
|
||||
content: "\e9d8";
|
||||
}
|
||||
|
||||
.itsm-over:before {
|
||||
content: "\e9d7";
|
||||
}
|
||||
|
||||
.itsm-request:before {
|
||||
content: "\e9d6";
|
||||
}
|
||||
|
||||
.itsm-release:before {
|
||||
content: "\e9d5";
|
||||
}
|
||||
|
||||
.veops-link:before {
|
||||
content: "\e9d4";
|
||||
}
|
||||
|
||||
.oneterm-command_record:before {
|
||||
content: "\e9d3";
|
||||
}
|
||||
|
||||
.ai-question:before {
|
||||
content: "\e9d2";
|
||||
}
|
||||
|
||||
.ai-sending:before {
|
||||
content: "\e9d1";
|
||||
}
|
||||
|
||||
.ai-dialogue:before {
|
||||
content: "\e9d0";
|
||||
}
|
||||
|
||||
.ai-report2:before {
|
||||
content: "\e9cf";
|
||||
}
|
||||
|
||||
.ai-delete:before {
|
||||
content: "\e9cd";
|
||||
}
|
||||
|
||||
.caise-knowledge:before {
|
||||
content: "\e9ce";
|
||||
}
|
||||
|
||||
.ai-article:before {
|
||||
content: "\e9cc";
|
||||
}
|
||||
|
||||
.ai-model_setup1:before {
|
||||
content: "\e9cb";
|
||||
}
|
||||
|
||||
.ai-report:before {
|
||||
content: "\e9ca";
|
||||
}
|
||||
|
||||
.ai-customer_service:before {
|
||||
content: "\e9c9";
|
||||
}
|
||||
|
||||
.oneterm-connect1:before {
|
||||
content: "\e9c6";
|
||||
}
|
||||
|
||||
.oneterm-session1:before {
|
||||
content: "\e9c7";
|
||||
}
|
||||
|
||||
.oneterm-assets:before {
|
||||
content: "\e9c8";
|
||||
}
|
||||
|
||||
.a-oneterm-ssh1:before {
|
||||
content: "\e9c3";
|
||||
}
|
||||
|
||||
.a-oneterm-ssh2:before {
|
||||
content: "\e9c4";
|
||||
}
|
||||
|
||||
.oneterm-rdp:before {
|
||||
content: "\e9c5";
|
||||
}
|
||||
|
||||
.caise-websphere:before {
|
||||
content: "\e9c2";
|
||||
}
|
||||
|
||||
.caise-vps:before {
|
||||
content: "\e9c1";
|
||||
}
|
||||
|
||||
.caise-F5:before {
|
||||
content: "\e9c0";
|
||||
}
|
||||
|
||||
.caise-HAProxy:before {
|
||||
content: "\e9bf";
|
||||
}
|
||||
|
||||
.caise-JBoss:before {
|
||||
content: "\e9be";
|
||||
}
|
||||
|
||||
.caise-dongfangtong:before {
|
||||
content: "\e9bd";
|
||||
}
|
||||
|
||||
.caise-kafka:before {
|
||||
content: "\e9b7";
|
||||
}
|
||||
|
||||
.caise-weblogic:before {
|
||||
content: "\e9b8";
|
||||
}
|
||||
|
||||
.caise-TDSQL:before {
|
||||
content: "\e9b9";
|
||||
}
|
||||
|
||||
.caise-kingbase:before {
|
||||
content: "\e9ba";
|
||||
}
|
||||
|
||||
.caise-dameng:before {
|
||||
content: "\e9bb";
|
||||
}
|
||||
|
||||
.caise-TIDB:before {
|
||||
content: "\e9bc";
|
||||
}
|
||||
|
||||
.veops-expand:before {
|
||||
content: "\e9b6";
|
||||
}
|
||||
|
@ -1101,11 +761,11 @@
|
|||
content: "\e914";
|
||||
}
|
||||
|
||||
.itsm-reports_3:before {
|
||||
.itsm-duration:before {
|
||||
content: "\e913";
|
||||
}
|
||||
|
||||
.itsm-reports_2:before {
|
||||
.itsm-workload:before {
|
||||
content: "\e912";
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,601 +5,6 @@
|
|||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"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",
|
||||
"font_class": "veops-rear",
|
||||
"unicode": "ea02",
|
||||
"unicode_decimal": 59906
|
||||
},
|
||||
{
|
||||
"icon_id": "42510708",
|
||||
"name": "veops-front",
|
||||
"font_class": "veops-front",
|
||||
"unicode": "ea03",
|
||||
"unicode_decimal": 59907
|
||||
},
|
||||
{
|
||||
"icon_id": "42497603",
|
||||
"name": "veops-xianggang",
|
||||
"font_class": "veops-xianggang",
|
||||
"unicode": "ea01",
|
||||
"unicode_decimal": 59905
|
||||
},
|
||||
{
|
||||
"icon_id": "42485038",
|
||||
"name": "veops-device (2)",
|
||||
"font_class": "a-veops-device2",
|
||||
"unicode": "ea00",
|
||||
"unicode_decimal": 59904
|
||||
},
|
||||
{
|
||||
"icon_id": "42455620",
|
||||
"name": "veops-room (1)",
|
||||
"font_class": "a-veops-room1",
|
||||
"unicode": "e9ff",
|
||||
"unicode_decimal": 59903
|
||||
},
|
||||
{
|
||||
"icon_id": "42455607",
|
||||
"name": "veops-IDC",
|
||||
"font_class": "veops-IDC",
|
||||
"unicode": "e9fe",
|
||||
"unicode_decimal": 59902
|
||||
},
|
||||
{
|
||||
"icon_id": "42455609",
|
||||
"name": "veops-region",
|
||||
"font_class": "veops-region",
|
||||
"unicode": "e9fd",
|
||||
"unicode_decimal": 59901
|
||||
},
|
||||
{
|
||||
"icon_id": "42448953",
|
||||
"name": "veops-device",
|
||||
"font_class": "veops-device",
|
||||
"unicode": "e9fb",
|
||||
"unicode_decimal": 59899
|
||||
},
|
||||
{
|
||||
"icon_id": "42448948",
|
||||
"name": "veops-cabinet",
|
||||
"font_class": "veops-cabinet",
|
||||
"unicode": "e9fc",
|
||||
"unicode_decimal": 59900
|
||||
},
|
||||
{
|
||||
"icon_id": "42433324",
|
||||
"name": "veops-data_center",
|
||||
"font_class": "veops-data_center",
|
||||
"unicode": "e9f9",
|
||||
"unicode_decimal": 59897
|
||||
},
|
||||
{
|
||||
"icon_id": "42337844",
|
||||
"name": "ops-setting-holiday_management-copy",
|
||||
"font_class": "ops-setting-holidays",
|
||||
"unicode": "e9fa",
|
||||
"unicode_decimal": 59898
|
||||
},
|
||||
{
|
||||
"icon_id": "42335414",
|
||||
"name": "itsm-system_log",
|
||||
"font_class": "ops-itsm-logs",
|
||||
"unicode": "e9f8",
|
||||
"unicode_decimal": 59896
|
||||
},
|
||||
{
|
||||
"icon_id": "42334782",
|
||||
"name": "ops-setting-adjustday",
|
||||
"font_class": "ops-setting-workday",
|
||||
"unicode": "e9f6",
|
||||
"unicode_decimal": 59894
|
||||
},
|
||||
{
|
||||
"icon_id": "42334768",
|
||||
"name": "ops-setting-holiday",
|
||||
"font_class": "ops-setting-holiday",
|
||||
"unicode": "e9f7",
|
||||
"unicode_decimal": 59895
|
||||
},
|
||||
{
|
||||
"icon_id": "42334734",
|
||||
"name": "ops-setting-festival",
|
||||
"font_class": "ops-setting-festival",
|
||||
"unicode": "e9f5",
|
||||
"unicode_decimal": 59893
|
||||
},
|
||||
{
|
||||
"icon_id": "42281202",
|
||||
"name": "itsm-count",
|
||||
"font_class": "itsm-calc",
|
||||
"unicode": "e9f4",
|
||||
"unicode_decimal": 59892
|
||||
},
|
||||
{
|
||||
"icon_id": "42270632",
|
||||
"name": "itsm-satisfaction",
|
||||
"font_class": "itsm-reports_4",
|
||||
"unicode": "e9f3",
|
||||
"unicode_decimal": 59891
|
||||
},
|
||||
{
|
||||
"icon_id": "42205149",
|
||||
"name": "veops-folder",
|
||||
"font_class": "veops-folder",
|
||||
"unicode": "e9f2",
|
||||
"unicode_decimal": 59890
|
||||
},
|
||||
{
|
||||
"icon_id": "42205128",
|
||||
"name": "veops-entire_network_",
|
||||
"font_class": "veops-entire_network_",
|
||||
"unicode": "e9f1",
|
||||
"unicode_decimal": 59889
|
||||
},
|
||||
{
|
||||
"icon_id": "42205094",
|
||||
"name": "veops-subnet",
|
||||
"font_class": "veops-subnet",
|
||||
"unicode": "e9f0",
|
||||
"unicode_decimal": 59888
|
||||
},
|
||||
{
|
||||
"icon_id": "42201912",
|
||||
"name": "veops-map_view",
|
||||
"font_class": "veops-map_view",
|
||||
"unicode": "e9ef",
|
||||
"unicode_decimal": 59887
|
||||
},
|
||||
{
|
||||
"icon_id": "42201676",
|
||||
"name": "veops-recycle",
|
||||
"font_class": "veops-recycle",
|
||||
"unicode": "e9ee",
|
||||
"unicode_decimal": 59886
|
||||
},
|
||||
{
|
||||
"icon_id": "42201586",
|
||||
"name": "veops-catalog",
|
||||
"font_class": "veops-catalog",
|
||||
"unicode": "e9ed",
|
||||
"unicode_decimal": 59885
|
||||
},
|
||||
{
|
||||
"icon_id": "42201534",
|
||||
"name": "veops-ipam",
|
||||
"font_class": "veops-ipam",
|
||||
"unicode": "e9ec",
|
||||
"unicode_decimal": 59884
|
||||
},
|
||||
{
|
||||
"icon_id": "42179262",
|
||||
"name": "cmdb-calc",
|
||||
"font_class": "cmdb-calc",
|
||||
"unicode": "e9eb",
|
||||
"unicode_decimal": 59883
|
||||
},
|
||||
{
|
||||
"icon_id": "42161413",
|
||||
"name": "ai-users",
|
||||
"font_class": "ai-users",
|
||||
"unicode": "e9ea",
|
||||
"unicode_decimal": 59882
|
||||
},
|
||||
{
|
||||
"icon_id": "42161417",
|
||||
"name": "ai-tokens",
|
||||
"font_class": "ai-tokens",
|
||||
"unicode": "e9e9",
|
||||
"unicode_decimal": 59881
|
||||
},
|
||||
{
|
||||
"icon_id": "42155223",
|
||||
"name": "oneterm-mysql",
|
||||
"font_class": "oneterm-mysql",
|
||||
"unicode": "e9e8",
|
||||
"unicode_decimal": 59880
|
||||
},
|
||||
{
|
||||
"icon_id": "42155225",
|
||||
"name": "oneterm-redis",
|
||||
"font_class": "oneterm-redis",
|
||||
"unicode": "e9e7",
|
||||
"unicode_decimal": 59879
|
||||
},
|
||||
{
|
||||
"icon_id": "42154436",
|
||||
"name": "veops-sign_out",
|
||||
"font_class": "veops-sign_out",
|
||||
"unicode": "e9e6",
|
||||
"unicode_decimal": 59878
|
||||
},
|
||||
{
|
||||
"icon_id": "42154310",
|
||||
"name": "veops-company",
|
||||
"font_class": "veops-company",
|
||||
"unicode": "e9e5",
|
||||
"unicode_decimal": 59877
|
||||
},
|
||||
{
|
||||
"icon_id": "42154325",
|
||||
"name": "veops-emails",
|
||||
"font_class": "veops-emails",
|
||||
"unicode": "e9e4",
|
||||
"unicode_decimal": 59876
|
||||
},
|
||||
{
|
||||
"icon_id": "42154350",
|
||||
"name": "veops-switch",
|
||||
"font_class": "veops-switch",
|
||||
"unicode": "e9e3",
|
||||
"unicode_decimal": 59875
|
||||
},
|
||||
{
|
||||
"icon_id": "42154370",
|
||||
"name": "veops-qiyeweixin",
|
||||
"font_class": "qiyeweixin",
|
||||
"unicode": "e9e2",
|
||||
"unicode_decimal": 59874
|
||||
},
|
||||
{
|
||||
"icon_id": "42134185",
|
||||
"name": "veops-progress",
|
||||
"font_class": "veops-progress",
|
||||
"unicode": "e9e1",
|
||||
"unicode_decimal": 59873
|
||||
},
|
||||
{
|
||||
"icon_id": "42134110",
|
||||
"name": "veops-completed",
|
||||
"font_class": "veops-completed",
|
||||
"unicode": "e9e0",
|
||||
"unicode_decimal": 59872
|
||||
},
|
||||
{
|
||||
"icon_id": "42133882",
|
||||
"name": "itsm-ticketTime",
|
||||
"font_class": "itsm-ticketTime",
|
||||
"unicode": "e9df",
|
||||
"unicode_decimal": 59871
|
||||
},
|
||||
{
|
||||
"icon_id": "42122869",
|
||||
"name": "veops-notification",
|
||||
"font_class": "veops-notification",
|
||||
"unicode": "e9dc",
|
||||
"unicode_decimal": 59868
|
||||
},
|
||||
{
|
||||
"icon_id": "42122868",
|
||||
"name": "veops-account_password",
|
||||
"font_class": "a-veops-account1",
|
||||
"unicode": "e9dd",
|
||||
"unicode_decimal": 59869
|
||||
},
|
||||
{
|
||||
"icon_id": "42122861",
|
||||
"name": "veops-personal",
|
||||
"font_class": "veops-personal",
|
||||
"unicode": "e9de",
|
||||
"unicode_decimal": 59870
|
||||
},
|
||||
{
|
||||
"icon_id": "42101103",
|
||||
"name": "itsm-evaluation2",
|
||||
"font_class": "itsm-customer_satisfaction2",
|
||||
"unicode": "e9da",
|
||||
"unicode_decimal": 59866
|
||||
},
|
||||
{
|
||||
"icon_id": "42101098",
|
||||
"name": "itsm-over2",
|
||||
"font_class": "itsm-over2",
|
||||
"unicode": "e9db",
|
||||
"unicode_decimal": 59867
|
||||
},
|
||||
{
|
||||
"icon_id": "42065574",
|
||||
"name": "veops-search",
|
||||
"font_class": "veops-search1",
|
||||
"unicode": "e9d9",
|
||||
"unicode_decimal": 59865
|
||||
},
|
||||
{
|
||||
"icon_id": "42063479",
|
||||
"name": "itsm-evaluation",
|
||||
"font_class": "itsm-customer_satisfaction",
|
||||
"unicode": "e9d8",
|
||||
"unicode_decimal": 59864
|
||||
},
|
||||
{
|
||||
"icon_id": "42062436",
|
||||
"name": "itsm-over",
|
||||
"font_class": "itsm-over",
|
||||
"unicode": "e9d7",
|
||||
"unicode_decimal": 59863
|
||||
},
|
||||
{
|
||||
"icon_id": "42050642",
|
||||
"name": "itsm-requirement",
|
||||
"font_class": "itsm-request",
|
||||
"unicode": "e9d6",
|
||||
"unicode_decimal": 59862
|
||||
},
|
||||
{
|
||||
"icon_id": "42050622",
|
||||
"name": "itsm-release",
|
||||
"font_class": "itsm-release",
|
||||
"unicode": "e9d5",
|
||||
"unicode_decimal": 59861
|
||||
},
|
||||
{
|
||||
"icon_id": "41903314",
|
||||
"name": "veops-link",
|
||||
"font_class": "veops-link",
|
||||
"unicode": "e9d4",
|
||||
"unicode_decimal": 59860
|
||||
},
|
||||
{
|
||||
"icon_id": "41876664",
|
||||
"name": "oneterm-command_record",
|
||||
"font_class": "oneterm-command_record",
|
||||
"unicode": "e9d3",
|
||||
"unicode_decimal": 59859
|
||||
},
|
||||
{
|
||||
"icon_id": "41859436",
|
||||
"name": "ai-question",
|
||||
"font_class": "ai-question",
|
||||
"unicode": "e9d2",
|
||||
"unicode_decimal": 59858
|
||||
},
|
||||
{
|
||||
"icon_id": "41859414",
|
||||
"name": "ai-sending",
|
||||
"font_class": "ai-sending",
|
||||
"unicode": "e9d1",
|
||||
"unicode_decimal": 59857
|
||||
},
|
||||
{
|
||||
"icon_id": "41859374",
|
||||
"name": "ai-dialogue",
|
||||
"font_class": "ai-dialogue",
|
||||
"unicode": "e9d0",
|
||||
"unicode_decimal": 59856
|
||||
},
|
||||
{
|
||||
"icon_id": "41859191",
|
||||
"name": "ai-report2",
|
||||
"font_class": "ai-report2",
|
||||
"unicode": "e9cf",
|
||||
"unicode_decimal": 59855
|
||||
},
|
||||
{
|
||||
"icon_id": "41858720",
|
||||
"name": "ai-delete",
|
||||
"font_class": "ai-delete",
|
||||
"unicode": "e9cd",
|
||||
"unicode_decimal": 59853
|
||||
},
|
||||
{
|
||||
"icon_id": "41858484",
|
||||
"name": "caise-knowledge",
|
||||
"font_class": "caise-knowledge",
|
||||
"unicode": "e9ce",
|
||||
"unicode_decimal": 59854
|
||||
},
|
||||
{
|
||||
"icon_id": "41833445",
|
||||
"name": "ai-article",
|
||||
"font_class": "ai-article",
|
||||
"unicode": "e9cc",
|
||||
"unicode_decimal": 59852
|
||||
},
|
||||
{
|
||||
"icon_id": "41811974",
|
||||
"name": "ai-model_setup (1)",
|
||||
"font_class": "ai-model_setup1",
|
||||
"unicode": "e9cb",
|
||||
"unicode_decimal": 59851
|
||||
},
|
||||
{
|
||||
"icon_id": "41811980",
|
||||
"name": "ai-report",
|
||||
"font_class": "ai-report",
|
||||
"unicode": "e9ca",
|
||||
"unicode_decimal": 59850
|
||||
},
|
||||
{
|
||||
"icon_id": "41811915",
|
||||
"name": "ai-customer_service",
|
||||
"font_class": "ai-customer_service",
|
||||
"unicode": "e9c9",
|
||||
"unicode_decimal": 59849
|
||||
},
|
||||
{
|
||||
"icon_id": "41735717",
|
||||
"name": "oneterm-connect",
|
||||
"font_class": "oneterm-connect1",
|
||||
"unicode": "e9c6",
|
||||
"unicode_decimal": 59846
|
||||
},
|
||||
{
|
||||
"icon_id": "41735716",
|
||||
"name": "oneterm-session",
|
||||
"font_class": "oneterm-session1",
|
||||
"unicode": "e9c7",
|
||||
"unicode_decimal": 59847
|
||||
},
|
||||
{
|
||||
"icon_id": "41735703",
|
||||
"name": "oneterm-assets",
|
||||
"font_class": "oneterm-assets",
|
||||
"unicode": "e9c8",
|
||||
"unicode_decimal": 59848
|
||||
},
|
||||
{
|
||||
"icon_id": "41725683",
|
||||
"name": "oneterm-RDP",
|
||||
"font_class": "a-oneterm-ssh1",
|
||||
"unicode": "e9c3",
|
||||
"unicode_decimal": 59843
|
||||
},
|
||||
{
|
||||
"icon_id": "41725684",
|
||||
"name": "oneterm-SSH",
|
||||
"font_class": "a-oneterm-ssh2",
|
||||
"unicode": "e9c4",
|
||||
"unicode_decimal": 59844
|
||||
},
|
||||
{
|
||||
"icon_id": "41725685",
|
||||
"name": "oneterm-VNC",
|
||||
"font_class": "oneterm-rdp",
|
||||
"unicode": "e9c5",
|
||||
"unicode_decimal": 59845
|
||||
},
|
||||
{
|
||||
"icon_id": "41724497",
|
||||
"name": "caise-websphere",
|
||||
"font_class": "caise-websphere",
|
||||
"unicode": "e9c2",
|
||||
"unicode_decimal": 59842
|
||||
},
|
||||
{
|
||||
"icon_id": "41724575",
|
||||
"name": "caise-vps",
|
||||
"font_class": "caise-vps",
|
||||
"unicode": "e9c1",
|
||||
"unicode_decimal": 59841
|
||||
},
|
||||
{
|
||||
"icon_id": "41724631",
|
||||
"name": "caise-F5",
|
||||
"font_class": "caise-F5",
|
||||
"unicode": "e9c0",
|
||||
"unicode_decimal": 59840
|
||||
},
|
||||
{
|
||||
"icon_id": "41724653",
|
||||
"name": "caise-HAProxy",
|
||||
"font_class": "caise-HAProxy",
|
||||
"unicode": "e9bf",
|
||||
"unicode_decimal": 59839
|
||||
},
|
||||
{
|
||||
"icon_id": "41722953",
|
||||
"name": "caise-JBoss",
|
||||
"font_class": "caise-JBoss",
|
||||
"unicode": "e9be",
|
||||
"unicode_decimal": 59838
|
||||
},
|
||||
{
|
||||
"icon_id": "41722960",
|
||||
"name": "caise-dongfangtong",
|
||||
"font_class": "caise-dongfangtong",
|
||||
"unicode": "e9bd",
|
||||
"unicode_decimal": 59837
|
||||
},
|
||||
{
|
||||
"icon_id": "41722681",
|
||||
"name": "caise-kafka",
|
||||
"font_class": "caise-kafka",
|
||||
"unicode": "e9b7",
|
||||
"unicode_decimal": 59831
|
||||
},
|
||||
{
|
||||
"icon_id": "41722680",
|
||||
"name": "caise-weblogic",
|
||||
"font_class": "caise-weblogic",
|
||||
"unicode": "e9b8",
|
||||
"unicode_decimal": 59832
|
||||
},
|
||||
{
|
||||
"icon_id": "41722679",
|
||||
"name": "caise-TDSQL",
|
||||
"font_class": "caise-TDSQL",
|
||||
"unicode": "e9b9",
|
||||
"unicode_decimal": 59833
|
||||
},
|
||||
{
|
||||
"icon_id": "41722678",
|
||||
"name": "caise-kingbase",
|
||||
"font_class": "caise-kingbase",
|
||||
"unicode": "e9ba",
|
||||
"unicode_decimal": 59834
|
||||
},
|
||||
{
|
||||
"icon_id": "41722677",
|
||||
"name": "达梦",
|
||||
"font_class": "caise-dameng",
|
||||
"unicode": "e9bb",
|
||||
"unicode_decimal": 59835
|
||||
},
|
||||
{
|
||||
"icon_id": "41722675",
|
||||
"name": "caise-TIDB",
|
||||
"font_class": "caise-TIDB",
|
||||
"unicode": "e9bc",
|
||||
"unicode_decimal": 59836
|
||||
},
|
||||
{
|
||||
"icon_id": "41681675",
|
||||
"name": "veops-expand",
|
||||
|
@ -1912,14 +1317,14 @@
|
|||
{
|
||||
"icon_id": "39926816",
|
||||
"name": "itsm-duration",
|
||||
"font_class": "itsm-reports_3",
|
||||
"font_class": "itsm-duration",
|
||||
"unicode": "e913",
|
||||
"unicode_decimal": 59667
|
||||
},
|
||||
{
|
||||
"icon_id": "39926833",
|
||||
"name": "itsm-workload (1)",
|
||||
"font_class": "itsm-reports_2",
|
||||
"font_class": "itsm-workload",
|
||||
"unicode": "e912",
|
||||
"unicode_decimal": 59666
|
||||
},
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 249 KiB |
|
@ -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')"
|
||||
|
@ -139,9 +139,9 @@
|
|||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
id: String(node[0] || ''),
|
||||
label: node[1] ? node[1].label || node[0] : node[0],
|
||||
children: node.children && node.children.length ? node.children : undefined,
|
||||
}
|
||||
}
|
||||
"
|
||||
|
@ -352,12 +352,8 @@ export default {
|
|||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find?.choice_value?.length) {
|
||||
return _find.choice_value.map((node) => ({
|
||||
id: String(node?.[0] ?? ''),
|
||||
label: node?.[1]?.label || node?.[0] || '',
|
||||
children: node?.children?.length ? node.children : undefined
|
||||
}))
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
@ -398,6 +394,7 @@ export default {
|
|||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 24px;
|
||||
background: #f7f8fa;
|
||||
line-height: 24px;
|
||||
border: none;
|
||||
|
||||
|
|
|
@ -1,302 +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: {
|
||||
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: @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>
|
||||
|
|
|
@ -341,7 +341,7 @@ export default {
|
|||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
<style scoped>
|
||||
.pop_btn {
|
||||
text-align: right;
|
||||
margin-top: 24px;
|
||||
|
|
|
@ -177,7 +177,7 @@ export const linearIconList = [
|
|||
}]
|
||||
}, {
|
||||
value: 'icon-xianxing-application',
|
||||
label: '常用组件',
|
||||
label: '应用',
|
||||
list: [{
|
||||
value: 'icon-xianxing-yilianjie',
|
||||
label: '已连接'
|
||||
|
@ -517,7 +517,7 @@ export const fillIconList = [
|
|||
}]
|
||||
}, {
|
||||
value: 'icon-shidi-application',
|
||||
label: '常用组件',
|
||||
label: '应用',
|
||||
list: [{
|
||||
value: 'icon-shidi-yilianjie',
|
||||
label: '已连接'
|
||||
|
@ -729,18 +729,6 @@ export const multicolorIconList = [
|
|||
value: 'database',
|
||||
label: '数据库',
|
||||
list: [{
|
||||
value: 'caise-TIDB',
|
||||
label: 'TIDB'
|
||||
}, {
|
||||
value: 'caise-dameng',
|
||||
label: '达梦'
|
||||
}, {
|
||||
value: 'caise-kingbase',
|
||||
label: 'KingBase'
|
||||
}, {
|
||||
value: 'caise-TDSQL',
|
||||
label: 'TDSQL'
|
||||
}, {
|
||||
value: 'caise-DB2',
|
||||
label: 'DB2'
|
||||
}, {
|
||||
|
@ -821,9 +809,6 @@ export const multicolorIconList = [
|
|||
value: 'system',
|
||||
label: '操作系统',
|
||||
list: [{
|
||||
value: 'ciase-aix',
|
||||
label: 'aix'
|
||||
}, {
|
||||
value: 'caise-Windows',
|
||||
label: 'Windows'
|
||||
}, {
|
||||
|
@ -918,38 +903,8 @@ export const multicolorIconList = [
|
|||
}]
|
||||
}, {
|
||||
value: 'caise-application',
|
||||
label: '常用组件',
|
||||
label: '应用',
|
||||
list: [{
|
||||
value: 'caise-websphere',
|
||||
label: 'WebSphere'
|
||||
}, {
|
||||
value: 'caise-vps',
|
||||
label: 'VPS'
|
||||
}, {
|
||||
value: 'caise-F5',
|
||||
label: 'F5'
|
||||
}, {
|
||||
value: 'caise-HAProxy',
|
||||
label: 'HAProxy'
|
||||
}, {
|
||||
value: 'caise-kafka',
|
||||
label: 'kafka'
|
||||
}, {
|
||||
value: 'caise-dongfangtong',
|
||||
label: '东方通'
|
||||
}, {
|
||||
value: 'cmdb-vcenter',
|
||||
label: 'VCenter'
|
||||
}, {
|
||||
value: 'ops-KVM',
|
||||
label: 'KVM'
|
||||
}, {
|
||||
value: 'caise-JBoss',
|
||||
label: 'JBoss'
|
||||
}, {
|
||||
value: 'caise-weblogic',
|
||||
label: 'WebLogic'
|
||||
}, {
|
||||
value: 'caise-disk_array',
|
||||
label: '磁盘阵列'
|
||||
}, {
|
||||
|
@ -973,6 +928,9 @@ export const multicolorIconList = [
|
|||
}, {
|
||||
value: 'caise_pool',
|
||||
label: 'ip池'
|
||||
}, {
|
||||
value: 'ciase-aix',
|
||||
label: 'aix'
|
||||
}, {
|
||||
value: 'caise-storage_volume1',
|
||||
label: '存储卷'
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
.cmdb-side-menu-search {
|
||||
background-color: #FFFFFF !important;
|
||||
cursor: auto !important;
|
||||
|
||||
:global {
|
||||
.ant-input-affix-wrapper {
|
||||
max-width: 170px !important;
|
||||
width: 170px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.ant-input {
|
||||
box-shadow: none;
|
||||
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 {
|
||||
right: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@ import {
|
|||
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'
|
||||
|
||||
const { Item, SubMenu } = Menu
|
||||
|
||||
|
@ -42,8 +40,7 @@ export default {
|
|||
openKeys: [],
|
||||
selectedKeys: [],
|
||||
cachedOpenKeys: [],
|
||||
resource_type: {},
|
||||
currentAppRoute: ''
|
||||
resource_type: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -67,7 +64,6 @@ export default {
|
|||
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
||||
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
||||
})
|
||||
this.currentAppRoute = this.$route?.matched?.[0]?.name || ''
|
||||
this.updateMenu()
|
||||
},
|
||||
watch: {
|
||||
|
@ -79,20 +75,18 @@ export default {
|
|||
this.openKeys = this.cachedOpenKeys
|
||||
}
|
||||
},
|
||||
$route: function (route) {
|
||||
this.currentAppRoute = route?.matched?.[0]?.name
|
||||
$route: function () {
|
||||
this.updateMenu()
|
||||
},
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
...mapActions(['UpdateCMDBSEarchValue']),
|
||||
cancelAttributes(e, menu) {
|
||||
const that = this
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
title: this.$t('alert'),
|
||||
content: this.$t('cmdb.preference.confirmcancelSub2', { name: menu.meta.title }),
|
||||
onOk() {
|
||||
const citypeId = menu.meta.typeId
|
||||
|
@ -171,6 +165,7 @@ export default {
|
|||
},
|
||||
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 } }
|
||||
|
@ -204,9 +199,11 @@ export default {
|
|||
<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>
|
||||
)
|
||||
},
|
||||
|
@ -289,44 +286,6 @@ export default {
|
|||
this.$message.error(this.$t('noPermission'))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
jumpCMDBSearch(value) {
|
||||
this.UpdateCMDBSEarchValue(value)
|
||||
|
||||
if (this.$route.name !== 'cmdb_resource_search') {
|
||||
this.$router.push({
|
||||
name: 'cmdb_resource_search',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
renderCMDBSearch() {
|
||||
if (this.currentAppRoute !== 'cmdb' || this.collapsed) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<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' : ''}`}
|
||||
placeholder={this.$t('cmdbSearch')}
|
||||
onPressEnter={(e) => {
|
||||
this.jumpCMDBSearch(e.target.value)
|
||||
}}
|
||||
>
|
||||
<ops-icon
|
||||
slot="suffix"
|
||||
type="veops-search1"
|
||||
onClick={() => {
|
||||
const value = this.$refs?.cmdbSideMenuSearchInputRef?.$refs?.input?.value || ''
|
||||
this.jumpCMDBSearch(value)
|
||||
}}
|
||||
/>
|
||||
</a-input>
|
||||
</Item>
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -354,7 +313,6 @@ export default {
|
|||
// {...{ props, on: on }}
|
||||
return (
|
||||
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
||||
{this.renderCMDBSearch()}
|
||||
{menuTree}
|
||||
</Menu>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,187 +1,183 @@
|
|||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
calcBasedParent: {
|
||||
type: Boolean,
|
||||
defualt: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer && this.calcBasedParent
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||
if (paneLengthPixel) {
|
||||
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||
}
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
if (this.direction === 'row') {
|
||||
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
|
||||
} else {
|
||||
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
const clientRect = this.$refs.splitPane.getBoundingClientRect()
|
||||
let paneLengthPixel = 0
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||
if (paneLengthPixel) {
|
||||
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||
}
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
if (this.direction === 'row') {
|
||||
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
|
||||
} else {
|
||||
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
const clientRect = this.$refs.splitPane.getBoundingClientRect()
|
||||
let paneLengthPixel = 0
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
:paneLengthPixel.sync="paneLengthPixel"
|
||||
:appName="appName"
|
||||
:triggerColor="triggerColor"
|
||||
:triggerLength="triggerLength"
|
||||
:calcBasedParent="calcBasedParent"
|
||||
:triggerLength="18"
|
||||
>
|
||||
<template #one>
|
||||
<div class="two-column-layout-sidebar">
|
||||
|
@ -38,14 +37,6 @@ export default {
|
|||
type: String,
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 18
|
||||
},
|
||||
calcBasedParent: {
|
||||
type: Boolean,
|
||||
defualt: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -11,11 +11,38 @@
|
|||
<span class="common-settings-btn-text">{{ $t('settings') }}</span>
|
||||
</span>
|
||||
<a-popover
|
||||
overlayClassName="lang-popover-wrap"
|
||||
placement="bottomRight"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
>
|
||||
<span class="locale">{{ languageList.find((lang) => lang.key === locale).title }}</span>
|
||||
<div class="lang-menu" slot="content">
|
||||
<a
|
||||
v-for="(lang) in languageList"
|
||||
:key="lang.key"
|
||||
:class="['lang-menu-item', lang.key === locale ? 'lang-menu-item_active' : '']"
|
||||
@click="changeLang(lang.key)"
|
||||
>
|
||||
{{ lang.title }}
|
||||
</a>
|
||||
</div>
|
||||
</a-popover>
|
||||
<a-popover
|
||||
:overlayStyle="{ width: '130px' }"
|
||||
placement="bottomRight"
|
||||
overlayClassName="custom-user"
|
||||
>
|
||||
<template slot="content">
|
||||
<UserPanel />
|
||||
<router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }">
|
||||
<div class="custom-user-item">
|
||||
<a-icon type="user" :style="{ marginRight: '10px' }" />
|
||||
<span>{{ $t('topMenu.personalCenter') }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<div @click="handleLogout" class="custom-user-item">
|
||||
<a-icon type="logout" :style="{ marginRight: '10px' }" />
|
||||
<span>{{ $t('topMenu.logout') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span class="action ant-dropdown-link user-dropdown-menu user-info-wrap">
|
||||
<a-avatar
|
||||
|
@ -36,13 +63,11 @@
|
|||
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
|
||||
import DocumentLink from './DocumentLink.vue'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import UserPanel from './userPanel.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserMenu',
|
||||
components: {
|
||||
DocumentLink,
|
||||
UserPanel
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -1,487 +0,0 @@
|
|||
<template>
|
||||
<div class="user-panel">
|
||||
<a-avatar
|
||||
class="user-panel-avatar"
|
||||
size="small"
|
||||
icon="user"
|
||||
:src="avatarSrc"
|
||||
/>
|
||||
<div class="user-panel-nickname">
|
||||
{{ userInfo.nickname }}
|
||||
</div>
|
||||
<div class="user-panel-info">
|
||||
<ops-icon
|
||||
type="veops-company"
|
||||
class="user-panel-info-icon"
|
||||
/>
|
||||
<div class="user-panel-info-text">
|
||||
{{ companyName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-panel-info">
|
||||
<ops-icon
|
||||
type="veops-emails"
|
||||
class="user-panel-info-icon"
|
||||
/>
|
||||
<div class="user-panel-info-text">
|
||||
{{ email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-panel-btn">
|
||||
<div
|
||||
v-for="(item) in userBtnGroup"
|
||||
:key="item.type"
|
||||
class="user-panel-btn-item"
|
||||
@click="clickBtnGroup(item.type)"
|
||||
>
|
||||
<ops-icon
|
||||
:type="item.icon"
|
||||
class="user-panel-btn-icon"
|
||||
/>
|
||||
<span class="user-panel-btn-title">
|
||||
{{ $t(item.title) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-panel-row">
|
||||
<div class="user-panel-row-label">
|
||||
{{ $t('userPanel.switchLanguage') }}
|
||||
</div>
|
||||
|
||||
<div class="user-panel-lang">
|
||||
<div
|
||||
v-for="(lang, index) in languageList"
|
||||
:key="index"
|
||||
:class="['user-panel-lang-item', lang.key === locale ? 'user-panel-lang-item_active' : '']"
|
||||
@click="changeLang(lang.key)"
|
||||
>
|
||||
{{ lang.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-panel-row">
|
||||
<div class="user-panel-row-label">
|
||||
{{ $t('userPanel.bindAccount') }}
|
||||
</div>
|
||||
|
||||
<div class="user-panel-bind">
|
||||
<a-tooltip
|
||||
v-for="(item) in bindList"
|
||||
:key="item.type"
|
||||
:title="$t(item.title)"
|
||||
>
|
||||
<ops-icon
|
||||
class="user-panel-bind-item"
|
||||
:type="userInfo.notice_info && userInfo.notice_info[item.type] ? item.existedIcon : item.icon"
|
||||
@click="handleBindInfo(item.type)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-panel-account">
|
||||
<div
|
||||
v-for="(item, index) in accountActions"
|
||||
:key="index"
|
||||
class="user-panel-account-item"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<ops-icon class="user-panel-account-icon" :type="item.icon" />
|
||||
<span class="user-panel-account-title">
|
||||
{{ $t(item.title) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState, mapMutations } from 'vuex'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import {
|
||||
bindPlatformByUid,
|
||||
unbindPlatformByUid,
|
||||
} from '@/api/employee'
|
||||
import { getCompanyInfo } from '@/api/company'
|
||||
|
||||
export default {
|
||||
name: 'UserPanel',
|
||||
data() {
|
||||
return {
|
||||
userBtnGroup: [
|
||||
{
|
||||
icon: 'veops-personal',
|
||||
title: 'userPanel.myProfile',
|
||||
type: 'myProfile'
|
||||
},
|
||||
{
|
||||
icon: 'a-veops-account1',
|
||||
title: 'userPanel.accountPassword',
|
||||
type: 'accountPassword'
|
||||
}
|
||||
],
|
||||
languageList: [
|
||||
{
|
||||
title: '简中',
|
||||
key: 'zh'
|
||||
},
|
||||
{
|
||||
title: 'EN',
|
||||
key: 'en'
|
||||
},
|
||||
],
|
||||
bindList: [
|
||||
{
|
||||
type: 'wechatApp',
|
||||
icon: 'qiyeweixin',
|
||||
existedIcon: 'wechatApp',
|
||||
title: 'wechat'
|
||||
},
|
||||
{
|
||||
type: 'feishuApp',
|
||||
icon: 'ops-setting-notice-feishu-selected',
|
||||
existedIcon: 'feishuApp',
|
||||
title: 'feishu'
|
||||
},
|
||||
{
|
||||
type: 'dingdingApp',
|
||||
icon: 'ops-setting-notice-dingding-selected',
|
||||
existedIcon: 'dingdingApp',
|
||||
title: 'dingding'
|
||||
},
|
||||
],
|
||||
accountActions: [
|
||||
{
|
||||
icon: 'veops-switch',
|
||||
title: 'userPanel.switchAccount'
|
||||
},
|
||||
{
|
||||
icon: 'veops-sign_out',
|
||||
title: 'userPanel.logout'
|
||||
},
|
||||
],
|
||||
hoverBindAccountList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
email: (state) => state.user.email,
|
||||
locale: (state) => state.locale,
|
||||
userInfo: (state) => state.user,
|
||||
companyName: (state) => state.company.name
|
||||
}),
|
||||
avatarSrc() {
|
||||
const avatar = this.userInfo.avatar
|
||||
if (!avatar) {
|
||||
return null
|
||||
}
|
||||
|
||||
return avatar.startsWith('https') ? avatar : `/api/common-setting/v1/file/${avatar}`
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.companyName === undefined) {
|
||||
this.getCompanyInfo()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['Logout', 'GetInfo']),
|
||||
...mapMutations(['SET_LOCALE', 'SET_COMPANY_NAME']),
|
||||
async getCompanyInfo() {
|
||||
const res = await getCompanyInfo()
|
||||
const name = res?.info?.name || ''
|
||||
this.SET_COMPANY_NAME(name)
|
||||
},
|
||||
|
||||
changeLang(lang) {
|
||||
this.SET_LOCALE(lang)
|
||||
this.$i18n.locale = lang
|
||||
this.$nextTick(() => {
|
||||
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
|
||||
})
|
||||
},
|
||||
handleBindInfo(platform) {
|
||||
const isBind = this?.userInfo?.notice_info?.[platform]
|
||||
const uid = this?.userInfo?.uid
|
||||
|
||||
if (isBind) {
|
||||
this.$confirm({
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('cs.person.confirmUnbind'),
|
||||
onOk: () => {
|
||||
unbindPlatformByUid(platform, uid)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('cs.person.unbindSuccess'))
|
||||
})
|
||||
.finally(() => {
|
||||
this.GetInfo()
|
||||
})
|
||||
},
|
||||
})
|
||||
} else {
|
||||
bindPlatformByUid(platform, uid)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('cs.person.bindSuccess'))
|
||||
})
|
||||
.finally(() => {
|
||||
this.GetInfo()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleLogout() {
|
||||
this.$confirm({
|
||||
title: this.$t('tip'),
|
||||
content: this.$t('topMenu.confirmLogout'),
|
||||
onOk: () => {
|
||||
this.Logout()
|
||||
},
|
||||
onCancel() {},
|
||||
})
|
||||
},
|
||||
|
||||
clickBtnGroup(type) {
|
||||
switch (type) {
|
||||
case 'myProfile':
|
||||
if (this.$route.name === 'setting_person') {
|
||||
this.$bus.$emit('changeSettingPersonCurrent', '1')
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'setting_person',
|
||||
query: {
|
||||
current: '1'
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'accountPassword':
|
||||
if (this.$route.name === 'setting_person') {
|
||||
this.$bus.$emit('changeSettingPersonCurrent', '2')
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'setting_person',
|
||||
query: {
|
||||
current: '2'
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
handleBindAccountMouse(type, isHover) {
|
||||
const index = this.hoverBindAccountList.findIndex((item) => item === type)
|
||||
if (isHover && index === -1) {
|
||||
this.hoverBindAccountList.push(type)
|
||||
} else if (!isHover && index !== -1) {
|
||||
this.hoverBindAccountList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
padding: 0 20px;
|
||||
|
||||
&-avatar {
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
border-radius: 62px;
|
||||
margin-top: 13px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #000000;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 48px !important;
|
||||
}
|
||||
|
||||
&-nickname {
|
||||
color: #1D2129;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
margin-top: 6px;
|
||||
max-width: 100%;
|
||||
|
||||
&-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&-btn {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 11px;
|
||||
|
||||
&-icon {
|
||||
font-size: 22px;
|
||||
color: #CACDD9;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1D2129;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&-item {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #F7F8FA;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #EBEFF8;
|
||||
|
||||
.user-panel-btn-icon {
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
.user-panel-btn-title {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-row {
|
||||
width: 100%;
|
||||
margin-top: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
}
|
||||
}
|
||||
|
||||
&-lang {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
width: 108px;
|
||||
border-radius: 28px;
|
||||
overflow: hidden;
|
||||
|
||||
&-item {
|
||||
flex: 1;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #F7F8FA;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
border-right: solid 1px #E4E7ED;
|
||||
}
|
||||
|
||||
&_active {
|
||||
background-color: #EBEFF8;
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-bind {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 22px;
|
||||
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&-account {
|
||||
margin-top: 22px;
|
||||
padding-top: 13px;
|
||||
padding-bottom: 20px;
|
||||
border-top: solid 1px #F0F1F5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
|
||||
&-icon {
|
||||
font-size: 14px;
|
||||
color: #CACDD9;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
color: #86909C;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.user-panel-account-icon {
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
.user-panel-account-title {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -108,7 +108,6 @@ export default {
|
|||
visual: 'Visual',
|
||||
default: 'default',
|
||||
tip: 'Tip',
|
||||
cmdbSearch: 'Search',
|
||||
pagination: {
|
||||
total: '{range0}-{range1} of {total} items'
|
||||
},
|
||||
|
@ -168,15 +167,6 @@ export default {
|
|||
monetaryAmount: 'monetary amount',
|
||||
custom: 'custom',
|
||||
},
|
||||
userPanel: {
|
||||
myProfile: 'My Profile',
|
||||
accountPassword: 'Password',
|
||||
notice: 'Notice',
|
||||
switchLanguage: 'Switch Language',
|
||||
bindAccount: 'Bind Account',
|
||||
switchAccount: 'Switch Account',
|
||||
logout: 'Logout'
|
||||
},
|
||||
cmdb: cmdb_en,
|
||||
cs: cs_en,
|
||||
acl: acl_en,
|
||||
|
|
|
@ -108,7 +108,6 @@ export default {
|
|||
visual: '虚拟',
|
||||
default: '默认',
|
||||
tip: '提示',
|
||||
cmdbSearch: '搜索一下',
|
||||
pagination: {
|
||||
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
||||
},
|
||||
|
@ -168,15 +167,6 @@ export default {
|
|||
monetaryAmount: '货币金额',
|
||||
custom: '自定义',
|
||||
},
|
||||
userPanel: {
|
||||
myProfile: '个人中心',
|
||||
accountPassword: '账号密码',
|
||||
notice: '通知中心',
|
||||
switchLanguage: '切换语言',
|
||||
bindAccount: '绑定账号',
|
||||
switchAccount: '切换账号',
|
||||
logout: '退出账号'
|
||||
},
|
||||
cmdb: cmdb_zh,
|
||||
cs: cs_zh,
|
||||
acl: acl_zh,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -195,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -358,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 {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
@ -152,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: {
|
||||
|
@ -292,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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue