mirror of https://github.com/veops/cmdb.git
Compare commits
711 Commits
Author | SHA1 | Date |
---|---|---|
|
47332aca3c | |
|
f24cb55585 | |
|
f1594550e0 | |
|
a025c844bc | |
|
1a03a0b800 | |
|
e35efea712 | |
|
3f1e8beae8 | |
|
3ea81987a1 | |
|
6a20e2f578 | |
|
f6de9b42ab | |
|
25f6bbcc3e | |
|
bc3201656c | |
|
89db5a060e | |
|
b669775cd6 | |
|
655b642930 | |
|
b253fdfea0 | |
|
d0129439cd | |
|
aaa4ca1327 | |
|
747475b6a6 | |
|
ada23262bb | |
|
0c57b2b83d | |
|
082724e7bd | |
|
d782ceddab | |
|
510ea5dc2d | |
|
41ce5db1c7 | |
|
c3aab86844 | |
|
d1e40b4e5e | |
|
ea4ea9d6b6 | |
|
1c5d2c8e9e | |
|
6bd3de8951 | |
|
a0ff3d69cb | |
|
fccf5db886 | |
|
95b55d2963 | |
|
47ebe55291 | |
|
f65c5a8c56 | |
|
a6f0791852 | |
|
05181dbed3 | |
|
f5dc53e5b8 | |
|
40b452cd4b | |
|
d322761ef7 | |
|
ba7b78af63 | |
|
f575291a1f | |
|
183b8c1f7d | |
|
12743c20fe | |
|
b7c3cce83b | |
|
191ce95717 | |
|
9f7a91ab26 | |
|
9ffa9c943d | |
|
9a2229ab10 | |
|
d4518002f9 | |
|
584215b569 | |
|
52b0a41d16 | |
|
8a16badf25 | |
|
1dc006426b | |
|
1a2929c44d | |
|
ccc45bec0f | |
|
aa3beefe75 | |
|
c4997458f4 | |
|
c70153c3a7 | |
|
6532a937bf | |
|
7cb6bcae1e | |
|
6b16d393c7 | |
|
c03dc851a6 | |
|
f7e748701d | |
|
1bf8588984 | |
|
54d645b711 | |
|
c50133b3e4 | |
|
cce88bb4b0 | |
|
1138783267 | |
|
48951ecd0a | |
|
ce8ac744d6 | |
|
c790aa3ab4 | |
|
2d09bd9c13 | |
|
2f3b5efea7 | |
|
e6be756e42 | |
|
00ceee3408 | |
|
9afdec9ba3 | |
|
75c31d4256 | |
|
a09336f00b | |
|
dbbff56395 | |
|
0ce42334f2 | |
|
b967de2d10 | |
|
e369a55333 | |
|
4b5d43de57 | |
|
bbcc0f986e | |
|
06e2924256 | |
|
c986cfc6a6 | |
|
310bb6ea39 | |
|
626aa7b094 | |
|
03b1139bb3 | |
|
4b300e772d | |
|
3beea17770 | |
|
26f0a65ae4 | |
|
6ccf5b261d | |
|
afda71f135 | |
|
5ccbfec178 | |
|
179463e733 | |
|
d0779d17fa | |
|
12c7b564cd | |
|
9a3897838d | |
|
766609ad89 | |
|
70bdd8f151 | |
|
4e363176fe | |
|
ef8ddeebe7 | |
|
478e9519e8 | |
|
5487d07b53 | |
|
ade4fc72e3 | |
|
13ed4671b0 | |
|
c3b7303a08 | |
|
d3080bad3c | |
|
1f3df6921d | |
|
6ccc010d08 | |
|
69bde5ee29 | |
|
52eef2d315 | |
|
9bf621dc2c | |
|
ad91d208b8 | |
|
6d404c2e3e | |
|
17d75fb329 | |
|
97311b2b51 | |
|
f3b0efabb4 | |
|
7b65ab325e | |
|
5d3221b93a | |
|
785c63e397 | |
|
9244eea71b | |
|
aaa3a1e829 | |
|
61cf798a3a | |
|
40d1a53537 | |
|
5bc294e405 | |
|
2717f65280 | |
|
f166824efb | |
|
e235224e3c | |
|
b417d98469 | |
|
65ecd827ba | |
|
dec7435e9b | |
|
ffcb533957 | |
|
3c1a2fe3e4 | |
|
24d9f3758e | |
|
d18c1fba6e | |
|
9729725c6a | |
|
e0c255ffa9 | |
|
b4241c92a0 | |
|
b75143108f | |
|
6ae4707323 | |
|
0a087df03f | |
|
0679fb96e9 | |
|
2a38401f5a | |
|
1e5ca4ff43 | |
|
a23d091667 | |
|
09b10eec45 | |
|
23c3ac44bf | |
|
e893ea1b19 | |
|
d80c1d7ad2 | |
|
785b4d4a7d | |
|
0c1b017266 | |
|
a77aefb436 | |
|
6ad3d167e1 | |
|
5c8f050585 | |
|
41d7cdf4d2 | |
|
95506e6f4e | |
|
f280603d00 | |
|
72beec08d7 | |
|
c08e4529de | |
|
b9c701bfb0 | |
|
c0c84de600 | |
|
126eb03550 | |
|
deea300620 | |
|
ad1bb86daf | |
|
5d3fe652b0 | |
|
9ace26cd04 | |
|
021b53dad4 | |
|
3720008c2f | |
|
e4c3a4bee1 | |
|
1336a24044 | |
|
16c4a08b74 | |
|
88def811ec | |
|
fa9bd5a926 | |
|
578b26737a | |
|
00d468efc6 | |
|
2b30bd4486 | |
|
caf7642863 | |
|
c748352d82 | |
|
6954c3bd7e | |
|
5cbcbaf93d | |
|
82ceb75d5b | |
|
ba1064495b | |
|
3b7c1adfb4 | |
|
4063b148a1 | |
|
4e441ee7a9 | |
|
341fb410f2 | |
|
fff6da943d | |
|
53dcf5d0d6 | |
|
3e9ae3e73a | |
|
39145989c3 | |
|
3ad8378eab | |
|
d00544d92f | |
|
8343d4eee3 | |
|
ecfc3e073d | |
|
23ee5e75b9 | |
|
bd61775048 | |
|
86afad1f68 | |
|
d017e5083a | |
|
e6f2aadc13 | |
|
72c0fbcd11 | |
|
ca352e984b | |
|
26266b2d6d | |
|
9a49636f63 | |
|
20f1e82ffa | |
|
244df5fa88 | |
|
4c652959d5 | |
|
4a42d26eef | |
|
3401fb6ac3 | |
|
7c7b107180 | |
|
f1cb8569a2 | |
|
dd611a1523 | |
|
6b3b00c9ea | |
|
a59e8b74ea | |
|
41131e82ce | |
|
5e0e64861f | |
|
c5761fc805 | |
|
e7f02817bf | |
|
23f1edcc65 | |
|
b379ff74c0 | |
|
cbc58d4a4c | |
|
a4be8a77aa | |
|
3a1920da32 | |
|
b5b8f899fe | |
|
87d65a35bd | |
|
819cd2884b | |
|
24e2f3fde4 | |
|
ed23d6d930 | |
|
f81cae18ee | |
|
81d25dbaed | |
|
fb1a2dd151 | |
|
7908fd1b96 | |
|
062c729d98 | |
|
0b683478cd | |
|
8137a599d1 | |
|
3398fdf499 | |
|
ebe460934d | |
|
33602dcff5 | |
|
5327fc3445 | |
|
fa737e75c3 | |
|
1a774490ac | |
|
5033c539de | |
|
104d163db8 | |
|
b29f498748 | |
|
5358fb41b2 | |
|
b7a6484579 | |
|
df3dc7cb1b | |
|
4dfbf7cd62 | |
|
0209ba3778 | |
|
95eeda2ebe | |
|
0aeac5b0df | |
|
b0e7748ad0 | |
|
9ccaaffa4a | |
|
58981a7301 | |
|
29dab8ad06 | |
|
f0713c5ac8 | |
|
e4750d122e | |
|
2da89ca1ef | |
|
5f6f22bff4 | |
|
9d0990b58b | |
|
51027e9ac2 | |
|
d276b3122e | |
|
e727391fed | |
|
46f4cb5be0 | |
|
59d948fb54 | |
|
11ff531730 | |
|
f89c18db51 | |
|
a889fe503a | |
|
55960aeb54 | |
|
0a13ca82c6 | |
|
6955714951 | |
|
c92a3a5f31 | |
|
a903738cdc | |
|
6b05ab1acc | |
|
dc454f081d | |
|
618c68423a | |
|
123c35c890 | |
|
3b4aa14bad | |
|
2b639dd11f | |
|
3b95fb9bb5 | |
|
657d57a742 | |
|
c0fc534958 | |
|
373dda6f41 | |
|
bd31043608 | |
|
3da43b6cef | |
|
03ec2e7d01 | |
|
7d9ef229c2 | |
|
b4326722e6 | |
|
69fb7f88ae | |
|
d2b7161e39 | |
|
d811f4d83f | |
|
644cd98af9 | |
|
f07b87e568 | |
|
e06bf67b5e | |
|
5a2581d569 | |
|
f317e24ae8 | |
|
7d1a05e487 | |
|
0966d104a7 | |
|
11dc7a6013 | |
|
00b022d620 | |
|
e6ffcf9ebd | |
|
e6eb1b8247 | |
|
780dbbc280 | |
|
0d7101c9f8 | |
|
5008fe0491 | |
|
bb72881f3b | |
|
2f7896b3db | |
|
cf8ed6cda6 | |
|
9e62780d50 | |
|
a97d3d6198 | |
|
9dfea3b478 | |
|
c252ef2d08 | |
|
10406942a0 | |
|
33e58a658b | |
|
bb7fd13cb2 | |
|
7f5e5a0921 | |
|
9793734655 | |
|
8f74be216e | |
|
a7c3a0a072 | |
|
0437da5797 | |
|
f0ac4d10ff | |
|
85dcb997fb | |
|
a4729a3c1d | |
|
6cda354c21 | |
|
702d8d65f0 | |
|
9ce5a96232 | |
|
79c9abe383 | |
|
e9d2365766 | |
|
614766563e | |
|
472642c958 | |
|
5ad73366ad | |
|
2c12f5fc6f | |
|
b08fa206e4 | |
|
dc569c32a5 | |
|
ec55dadc57 | |
|
24af71c1fc | |
|
cf45f608d4 | |
|
53943f1244 | |
|
6b2d4902af | |
|
2b0261f055 | |
|
b63ca2a059 | |
|
4ebaf9c102 | |
|
df5c62d98e | |
|
39e38b10cd | |
|
02f332606e | |
|
f30b8ecd3a | |
|
7a170ab397 | |
|
34204ec4c6 | |
|
f867ccbf94 | |
|
60df081e49 | |
|
2979d2056a | |
|
06af1f656d | |
|
fe8582447e | |
|
d96d529aa9 | |
|
aa000cabe2 | |
|
27affe02a8 | |
|
f010b9625e | |
|
ef1d0c34cf | |
|
7474a92377 | |
|
26c3404f28 | |
|
d74f201710 | |
|
5b3fb7ee32 | |
|
0f404fe9bf | |
|
498feee0a2 | |
|
2bd1a45ae6 | |
|
d4279600b5 | |
|
c52fa3bc80 | |
|
849af21855 | |
|
861564c0da | |
|
9511e33736 | |
|
b6ef49f139 | |
|
9141c06530 | |
|
9bc0ab6009 | |
|
67081ef005 | |
|
4111ac8d31 | |
|
7b593ce1bc | |
|
e1b81561c9 | |
|
c53e5ecd30 | |
|
6f6da3c228 | |
|
95d85234c6 | |
|
996151f0f4 | |
|
c2ba819076 | |
|
77d8a21bde | |
|
0f23feda2f | |
|
21bb741a02 | |
|
6c7bc690cc | |
|
56dab953e7 | |
|
cdf7506b5d | |
|
f3ca1fbea3 | |
|
6c9c987979 | |
|
4e48dd2b37 | |
|
1a8d54d4e2 | |
|
be0712f202 | |
|
ff8d4bd51b | |
|
af148b7c8f | |
|
5e79aab93d | |
|
99022bdabb | |
|
19c6009c64 | |
|
b3ef1aa5c1 | |
|
2992bc2fae | |
|
5c2cb9073f | |
|
ee50ea1cf3 | |
|
11259b4067 | |
|
a23bdab10e | |
|
5c8e93e194 | |
|
f0bf740d70 | |
|
98025ae47c | |
|
f65d81bf46 | |
|
33f9f190e9 | |
|
8646f4693a | |
|
d0575331d5 | |
|
885c346407 | |
|
b887de2ab8 | |
|
51c42f90be | |
|
a938477d85 | |
|
f965ad3bf3 | |
|
5fe6676d83 | |
|
41ad610c00 | |
|
7b8e120974 | |
|
1ee8ed7c4f | |
|
1da629b877 | |
|
5279d96c84 | |
|
718335231c | |
|
c361997591 | |
|
3e2943b49e | |
|
75122f7a40 | |
|
6674510697 | |
|
4a0233df24 | |
|
9afef06c54 | |
|
37b3c1aa01 | |
|
e597c2aee9 | |
|
b61c14ba07 | |
|
3a760c3a80 | |
|
92b54f045f | |
|
3e6ebec9af | |
|
4a92d95d2f | |
|
0806f2ed73 | |
|
ef052c4ab9 | |
|
3c315ff397 | |
|
fc4bb7ffb4 | |
|
e1c797b6a1 | |
|
faeb9a04f9 | |
|
c89ebf6518 | |
|
a21afbe909 | |
|
6aef26b82c | |
|
ee0b74bec7 | |
|
32e073f3fd | |
|
67d64abf42 | |
|
093065551b | |
|
b84d5d717e | |
|
65664ae8f9 | |
|
7f1d796fd1 | |
|
6c45ca7cb0 | |
|
64f891df31 | |
|
beb2f01ec9 | |
|
4b133c56fd | |
|
a1a6b11072 | |
|
379cabbfca | |
|
f1350a2940 | |
|
86fbd62048 | |
|
4d783235b7 | |
|
34c846581e | |
|
7c7b360ffc | |
|
4b8c5b8495 | |
|
c523ffaf65 | |
|
315aa40b72 | |
|
7ef23bd779 | |
|
91555ffa64 | |
|
2699c263d9 | |
|
8f3421bc29 | |
|
17612105a5 | |
|
21d445458d | |
|
3b73971bf8 | |
|
8856115e7c | |
|
fb371a0d46 | |
|
6aa02eed73 | |
|
e5d4015f7e | |
|
1b16a0b9c0 | |
|
3d4c4d6b56 | |
|
a50f6d4fe4 | |
|
07412169b3 | |
|
e577dce4ee | |
|
c394c41c3c | |
|
2554a7d1be | |
|
2c70cb49e1 | |
|
5e5ec3f887 | |
|
5132481fef | |
|
f75756b71a | |
|
4440654a25 | |
|
072daeea35 | |
|
1ccbafce08 | |
|
e785d1a2f6 | |
|
2d59757ba7 | |
|
a94575df0d | |
|
36a451686e | |
|
c273ecdd29 | |
|
2154834f44 | |
|
c020d2fce8 | |
|
02b6e1d66b | |
|
be72a4579b | |
|
3c660e0969 | |
|
b44d9d7e47 | |
|
b31bbd3d63 | |
|
be32b9b043 | |
|
6c200f12a7 | |
|
92dc81ec53 | |
|
4754bb59f4 | |
|
9e180af9f4 | |
|
67779ccc67 | |
|
8c47105fbc | |
|
30ccb87499 | |
|
0ac14fa318 | |
|
819b994c2d | |
|
6047c59f51 | |
|
c869827ffd | |
|
bdd2adcfc2 | |
|
e0e2ca6294 | |
|
2a5091c51a | |
|
788dd684ab | |
|
ee1b068b62 | |
|
b08ed43105 | |
|
41d810642b | |
|
f033af2f58 | |
|
cbe579fdb4 | |
|
4e1f25b389 | |
|
a27b9a37df | |
|
ebc8a1e254 | |
|
e51fa08208 | |
|
55c2c557fc | |
|
5aa36daaac | |
|
ba519a2c43 | |
|
9dbea9f403 | |
|
d7bbc3ccf8 | |
|
501d86341a | |
|
59915f5f11 | |
|
b8fed4e655 | |
|
297270063c | |
|
ccce5c830a | |
|
31d24b19a8 | |
|
89a2985ee4 | |
|
d42513c4a4 | |
|
55f74a4eba | |
|
17dd2e2a93 | |
|
a779b37c44 | |
|
cf3f961b0d | |
|
4f7eddf906 | |
|
8b729e63cd | |
|
bc91b33beb | |
|
d2985a7564 | |
|
2ef4270ae7 | |
|
11c6f9001b | |
|
ab26033cea | |
|
ca0f332650 | |
|
43be749c58 | |
|
788fefd9a1 | |
|
36a24d4a68 | |
|
c2066b53f1 | |
|
69d3d3b047 | |
|
c236851d2f | |
|
249ba7ad5c | |
|
d8399f8723 | |
|
6e94c72031 | |
|
6e9871dfd8 | |
|
e5ccb9a499 | |
|
ad841f9732 | |
|
47dbe5ba18 | |
|
737b29f7d6 | |
|
8c703fb6d9 | |
|
afe365bd38 | |
|
5fa187bf46 | |
|
44f051dac9 | |
|
94cdb42477 | |
|
cca8fccb55 | |
|
eea379537f | |
|
2c74d107dd | |
|
8f6b54cd12 | |
|
ae5636b702 | |
|
9afb5179f7 | |
|
bba3a2b931 | |
|
15f264681b | |
|
b099bc212b | |
|
3fd4a9fed6 | |
|
ad0d32652e | |
|
8225159d5a | |
|
4a3db88ae6 | |
|
e49ede0b3a | |
|
6fe6cf4600 | |
|
4fb3138acd | |
|
d1dc6c2fab | |
|
9f8f3a29b7 | |
|
063ed40440 | |
|
96176c706a | |
|
81faab5a20 | |
|
a39fd08e0d | |
|
cd954775bc | |
|
5f550208ce | |
|
88c9fb5ae3 | |
|
2aa420ec07 | |
|
6e51e9ef4c | |
|
2f8856772b | |
|
c74fee8aa9 | |
|
e390b1ed7c | |
|
ce5884b2b9 | |
|
7700afdf1c | |
|
771439e008 | |
|
7918b81f33 | |
|
9547fce484 | |
|
d25c3760a1 | |
|
6df845d662 | |
|
0ac5b9cfe4 | |
|
f74fd2ea8f | |
|
1e4033d0a6 | |
|
6ce139bec3 | |
|
d763edc6bf | |
|
11fe5ca457 | |
|
57b1b5fb67 | |
|
349ddbd98a | |
|
700c411ec3 | |
|
4d36d448a7 | |
|
0f1cb01f05 | |
|
7e06713b87 | |
|
41ee3b7f87 | |
|
36910d0052 | |
|
52578d78d6 | |
|
9b563f7b57 | |
|
96187bcd47 | |
|
1ba3f85f7e | |
|
3071983d6b | |
|
836892d909 | |
|
a24ba8f7cd | |
|
d0d68c613b | |
|
9b976675c0 | |
|
2200b122f0 | |
|
190e82efe9 | |
|
1dae48c583 | |
|
5c25433e03 | |
|
f8e94b7c70 | |
|
c64610e9ea | |
|
3f49586c9f | |
|
91b886d632 | |
|
7740031bd8 | |
|
76a41a8e82 | |
|
6843eb57c4 | |
|
20caae8263 | |
|
5e61da038f | |
|
348a34d862 | |
|
2471af867a | |
|
775f65ba7b | |
|
ebd0b1dc2e | |
|
14b5119d94 | |
|
45167225c9 | |
|
e576d41497 | |
|
4c33d6dee9 | |
|
74de3ec0b3 | |
|
5e5ae296e0 | |
|
e81606aa81 | |
|
dc6da9ba2a | |
|
d40e69aa30 | |
|
4cecdb10fb | |
|
ddf02213db | |
|
28dea81036 | |
|
9756f70044 | |
|
88cea199c1 | |
|
684c1ab924 | |
|
cb01b577a5 | |
|
694abf78b3 | |
|
7e38dd8fab | |
|
7afefe1070 | |
|
31a4cb62d8 | |
|
79a2fd32c1 | |
|
ef09497d67 | |
|
51c6d50b38 | |
|
edb74d5790 | |
|
9b7d6d8f12 | |
|
61e178530f | |
|
3632f5f4a6 | |
|
cd11b002ee | |
|
883d7776e1 | |
|
695e9f7546 | |
|
c3cbdc606f | |
|
5cb5650cde | |
|
8d95092b52 | |
|
458b1d144b | |
|
ec0f3d5a9f | |
|
8c21df189f | |
|
a882a62d1c | |
|
d7d2e127d3 | |
|
82f4bb5b65 | |
|
96ba04dc31 | |
|
0263842b70 | |
|
c0443a4e3f | |
|
10b7f9dfcb | |
|
c2cca38f1c | |
|
742cd4ead1 | |
|
5e0ea75fae | |
|
7715d52e33 | |
|
bd72abba10 | |
|
8dc437bc81 | |
|
5d299bd71a | |
|
3d3453e257 | |
|
c196c75985 | |
|
7443f96813 | |
|
ea6c5c8566 | |
|
c4caa464d3 | |
|
648c1cad68 | |
|
095d3b2cfb | |
|
d8a62fc885 | |
|
ccf96b17bc | |
|
ae99d7f909 |
|
@ -78,4 +78,3 @@ cmdb-ui/npm-debug.log*
|
|||
cmdb-ui/yarn-debug.log*
|
||||
cmdb-ui/yarn-error.log*
|
||||
cmdb-ui/package-lock.json
|
||||
start.sh
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# 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/)
|
193
README.md
193
README.md
|
@ -1,119 +1,138 @@
|
|||
|
||||
<p align="center">
|
||||
<a href="https://veops.cn"><img src="docs/images/logo.png" alt="维易CMDB" width="300"/></a>
|
||||
<a href="https://veops.cn">
|
||||
<img src="https://github.com/user-attachments/assets/c5cfb272-899b-418d-9e69-8e1dd07db0f6" alt="维易CMDB"/>
|
||||
</a>
|
||||
</p>
|
||||
<h3 align="center">简单、轻量、通用的运维配置管理数据库</h3>
|
||||
|
||||
<h4 align="center">简单、轻量、通用的运维配置管理数据库</h4>
|
||||
|
||||
<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: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>
|
||||
<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>
|
||||
</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 基础设施和服务。
|
||||
|
||||
<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. 支持定义属性触发器、计算属性
|
||||
- 产品文档:[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) 获取最新稳定版本。
|
||||
|
||||
### 主要功能
|
||||
|
||||
- 模型属性支持索引、多值、默认排序、字体颜色,支持计算属性
|
||||
- 支持自动发现、定时巡检、文件导入
|
||||
- 支持资源、层级、关系视图展示
|
||||
- 支持模型间关系配置和展示
|
||||
- 细粒度访问控制,完备的操作日志
|
||||
- 支持跨模型搜索
|
||||
- **自定义模型和模型关系**:支持模型属性的自定义,包括下拉列表、字体颜色、计算属性等高级功能,满足不同业务需求。
|
||||
- **自动发现资源**:支持计算机、网络设备、存储设备、数据库、中间件、公有云资源等自动发现。
|
||||
- **多维度视图展示**:包括资源视图、层级视图、关系视图等,帮助运维人员全面管理资源。
|
||||
- **细粒度权限控制**:通过精确的访问控制和完备的操作日志保障系统的安全性。
|
||||
- **全面的资源搜索功能**:支持灵活的资源和关系搜索,快速定位和操作资源。
|
||||
- **集成 IP 地址管理(IPAM)和数据中心基础设施管理(DCIM)**:简化网络资源和数据中心设备的管理。
|
||||
|
||||
更多详细功能,请移步 [维易科技官网](https://veops.cn) 进行了解。
|
||||
|
||||
### 系统优势
|
||||
|
||||
- 灵活性
|
||||
+ 无需指定固定运维场景,支持自由配置并内置多种模板
|
||||
+ 支持自动发现和入库 IT 资产,快速搭建资产管理系统
|
||||
- 安全性
|
||||
+ 细粒度的权限控制机制,确保资源管理的安全性
|
||||
+ 完整的操作日志记录,便于审计和问题追踪
|
||||
- 多应用
|
||||
+ 提供多种视图展示方式,满足不同场景的需求
|
||||
+ 强大的 API 接口,支持深度集成
|
||||
+ 支持定义属性触发器和计算属性,增强数据处理能力
|
||||
|
||||
### 技术栈
|
||||
|
||||
### 更多功能
|
||||
+ 后端:Python [3.8-3.11]
|
||||
+ 数据存储:MySQL、Redis
|
||||
+ 前端:Vue.js
|
||||
+ UI组件库:Ant Design Vue
|
||||
|
||||
> 也欢迎移步[维易科技官网](https://veops.cn),发现更多免费运维系统。
|
||||
### 系统概览
|
||||
|
||||
<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
|
||||
|
||||
## 接入公司
|
||||
|
||||
> 欢迎使用开源CMDB的公司,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
+ 欢迎使用开源CMDB的公司和团队,在 [#112](https://github.com/veops/cmdb/issues/112) 登记
|
||||
|
||||
## 安装
|
||||
## 代码贡献
|
||||
我们欢迎所有开发者贡献代码,改善和扩展这个项目。请先阅读我们的[贡献指南](docs/CONTRIBUTING.md)。此外,您还可以通过社交媒体、活动和分享来支持 Veops 的开源。
|
||||
|
||||
### Docker 一键快速构建
|
||||
<a href="https://github.com/veops/cmdb/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=veops/cmdb" />
|
||||
</a>
|
||||
|
||||
[//]: # (> 方法一)
|
||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||
- 第二步: 拷贝项目
|
||||
```shell
|
||||
git clone https://github.com/veops/cmdb.git
|
||||
```
|
||||
- 第三步:进入主目录,执行:
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
## 更多开源
|
||||
|
||||
[//]: # (> 方法二, 该方法适用于linux系统)
|
||||
- [OneTerm](https://github.com/veops/oneterm): 一款简单、轻量、灵活的堡垒机服务。
|
||||
- [messenger](https://github.com/veops/messenger): 一个简单轻量的消息发送服务。
|
||||
- [ACL](https://github.com/veops/acl): 一个简单通用的权限管理系统设计与实践。
|
||||
|
||||
[//]: # (- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2))
|
||||
## 相关文章
|
||||
|
||||
[//]: # (- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`)
|
||||
- <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>
|
||||
|
||||
[//]: # (```shell)
|
||||
更多文章可以在公众号 **维易科技OneOps** 里查看
|
||||
|
||||
[//]: # (curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh)
|
||||
## 与我联系
|
||||
|
||||
[//]: # (sh install.sh install)
|
||||
|
||||
[//]: # (```)
|
||||
|
||||
### [本地开发环境搭建](docs/local.md)
|
||||
|
||||
### [Makefile 安装](docs/makefile.md)
|
||||
|
||||
## 验证
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
- username: demo 或者 admin
|
||||
- password: 123456
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**欢迎关注公众号(维易科技OneOps),关注后可加入微信群,进行产品和技术交流。**_
|
||||
|
||||
|
||||
<p align="center">
|
||||
+ 邮箱: <a href="mailto:bd@veops.cn">bd@veops.cn</a>
|
||||
+ 公众号:**维易科技OneOps**。关注后可以加入微信群,参与产品和技术交流
|
||||
<img src="docs/images/wechat.png" alt="公众号: 维易科技OneOps" />
|
||||
</p>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
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
|
|
@ -228,6 +228,12 @@ 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()
|
||||
|
@ -256,10 +262,10 @@ def cmdb_trigger():
|
|||
trigger2cis[trigger.id] = (trigger, ready_cis)
|
||||
else:
|
||||
cur = trigger2cis[trigger.id]
|
||||
cur_ci_ids = {i.ci_id for i in cur[1]}
|
||||
cur_ci_ids = {_ci.ci_id for _ci in cur[1]}
|
||||
trigger2cis[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, {})])
|
||||
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, {})])
|
||||
|
||||
for tid in trigger2cis:
|
||||
trigger, cis = trigger2cis[tid]
|
||||
|
@ -346,7 +352,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(f'Enter root token', hide_input=True, confirmation_prompt=False)
|
||||
token = click.prompt('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})
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -295,7 +295,7 @@ class CIManager(object):
|
|||
db.session.commit()
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name)):
|
||||
with redis_lock.Lock(rd.r, "auto_inc_id_{}".format(attr.name), expire=10):
|
||||
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:
|
||||
|
@ -393,7 +393,7 @@ class CIManager(object):
|
|||
ci = None
|
||||
record_id = None
|
||||
password_dict = {}
|
||||
with redis_lock.Lock(rd.r, ci_type.name):
|
||||
with redis_lock.Lock(rd.r, ci_type.name, expire=10):
|
||||
db.session.commit()
|
||||
|
||||
if (unique_key.default and unique_key.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID and
|
||||
|
@ -524,10 +524,14 @@ 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()
|
||||
|
@ -550,14 +554,15 @@ 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):
|
||||
with redis_lock.Lock(rd.r, ci_type.name, expire=10):
|
||||
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)
|
||||
ci_attr2type_attr=ci_attr2type_attr,
|
||||
unique_name=unique_name)
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
|
@ -1268,7 +1273,9 @@ 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)):
|
||||
with redis_lock.Lock(rd.r,
|
||||
"ci_relation_add_{}_{}".format(first_ci.type_id, second_ci.type_id),
|
||||
expire=10):
|
||||
|
||||
cls._check_constraint(first_ci_id, first_ci.type_id, second_ci_id, second_ci.type_id, type_relation)
|
||||
|
||||
|
@ -1534,7 +1541,8 @@ 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):
|
||||
def _exec_webhook(cls, operate_type, webhook, ci_dict, trigger_id, trigger_name, record_id,
|
||||
ci_id=None, app=None, record_history=True):
|
||||
app = app or current_app
|
||||
|
||||
with app.app_context():
|
||||
|
@ -1552,7 +1560,7 @@ 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'),
|
||||
|
@ -1564,7 +1572,8 @@ class CITriggerManager(object):
|
|||
return is_ok
|
||||
|
||||
@classmethod
|
||||
def _exec_notify(cls, operate_type, notify, ci_dict, trigger_id, trigger_name, record_id, ci_id=None, app=None):
|
||||
def _exec_notify(cls, operate_type, notify, ci_dict, trigger_id, trigger_name, record_id,
|
||||
ci_id=None, app=None, record_history=True):
|
||||
app = app or current_app
|
||||
|
||||
with app.app_context():
|
||||
|
@ -1588,6 +1597,7 @@ class CITriggerManager(object):
|
|||
response = "{}\n{}".format(response, e)
|
||||
is_ok = False
|
||||
|
||||
if record_history:
|
||||
CITriggerHistoryManager.add(operate_type,
|
||||
record_id,
|
||||
ci_dict.get('_id'),
|
||||
|
@ -1671,25 +1681,47 @@ class CITriggerManager(object):
|
|||
return result
|
||||
|
||||
@classmethod
|
||||
def trigger_notify(cls, trigger, ci):
|
||||
def trigger_notify(cls, trigger, ci, only_test=False):
|
||||
"""
|
||||
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')):
|
||||
not trigger.option.get('notifies', {}).get('notify_at')) or only_test:
|
||||
|
||||
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.ci_id,
|
||||
current_app._get_current_object())).start()
|
||||
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()
|
||||
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.ci_id,
|
||||
current_app._get_current_object())).start()
|
||||
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()
|
||||
|
||||
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)
|
||||
|
|
|
@ -421,7 +421,10 @@ 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']).to_dict()
|
||||
ci_type = CITypeCache.get(t['type_id'])
|
||||
if ci_type is None:
|
||||
continue
|
||||
ci_type = ci_type.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):
|
||||
|
@ -719,9 +722,6 @@ 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,6 +737,9 @@ 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 []):
|
||||
|
@ -859,15 +862,15 @@ class CITypeRelationManager(object):
|
|||
|
||||
graph = nx.DiGraph()
|
||||
|
||||
def get_children(_id):
|
||||
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.add_edge(i.parent_id, i.child_id)
|
||||
get_children(i.child_id, _graph)
|
||||
|
||||
get_children(source_type_id)
|
||||
get_children(source_type_id, graph)
|
||||
|
||||
paths = list(nx.all_simple_paths(graph, source_type_id, target_type_ids))
|
||||
|
||||
|
@ -1142,13 +1145,14 @@ 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])
|
||||
|
|
|
@ -44,7 +44,7 @@ class RackManager(DCIMBase):
|
|||
CIManager().update(_id, _sync=True, **kwargs)
|
||||
|
||||
if RackBuiltinAttributes.U_COUNT in kwargs:
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls._calc_u_free_count(_id)}
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls.calc_u_free_count(_id)}
|
||||
|
||||
CIManager().update(_id, _sync=True, **payload)
|
||||
|
||||
|
@ -57,7 +57,7 @@ class RackManager(DCIMBase):
|
|||
CIManager().update(ci['_id'], **payload)
|
||||
|
||||
@staticmethod
|
||||
def _calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
|
||||
def calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
|
||||
rack = CIManager.get_ci_by_id(rack_id, need_children=False)
|
||||
if not rack.get(RackBuiltinAttributes.U_COUNT):
|
||||
return 0
|
||||
|
@ -122,8 +122,8 @@ class RackManager(DCIMBase):
|
|||
CIManager().update(rack['_id'], **payload)
|
||||
|
||||
def add_device(self, rack_id, device_id, u_start, u_count=None):
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||
self._calc_u_free_count(rack_id, device_id, u_start, u_count)
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), expire=10)):
|
||||
self.calc_u_free_count(rack_id, device_id, u_start, u_count)
|
||||
|
||||
self.add_relation(rack_id, device_id)
|
||||
|
||||
|
@ -133,16 +133,16 @@ class RackManager(DCIMBase):
|
|||
CIManager().update(device_id, _sync=True, **payload)
|
||||
|
||||
payload = {
|
||||
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, u_start, u_count)}
|
||||
RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id, device_id, u_start, u_count)}
|
||||
CIManager().update(rack_id, _sync=True, **payload)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||
|
||||
def remove_device(self, rack_id, device_id):
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||
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)}
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id)}
|
||||
CIManager().update(rack_id, _sync=True, **payload)
|
||||
|
||||
payload = {RackBuiltinAttributes.U_START: None}
|
||||
|
@ -151,8 +151,8 @@ class RackManager(DCIMBase):
|
|||
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||
|
||||
def move_device(self, rack_id, device_id, to_u_start):
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, to_u_start)}
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), 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})
|
||||
|
@ -160,8 +160,8 @@ class RackManager(DCIMBase):
|
|||
OperateHistoryManager().add(operate_type=OperateTypeEnum.MOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||
|
||||
def migrate_device(self, rack_id, device_id, to_rack_id, to_u_start):
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
|
||||
self._calc_u_free_count(to_rack_id, device_id, to_u_start)
|
||||
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id), 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)
|
||||
|
@ -169,15 +169,14 @@ class RackManager(DCIMBase):
|
|||
self.add_relation(to_rack_id, device_id)
|
||||
|
||||
payload = {
|
||||
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(to_rack_id, device_id, to_u_start)}
|
||||
RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(to_rack_id, device_id, to_u_start)}
|
||||
CIManager().update(to_rack_id, _sync=True, **payload)
|
||||
|
||||
CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})
|
||||
|
||||
if rack_id != to_rack_id:
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
|
||||
payload = {RackBuiltinAttributes.FREE_U_COUNT: self.calc_u_free_count(rack_id)}
|
||||
CIManager().update(rack_id, _sync=True, **payload)
|
||||
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
|
||||
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=to_rack_id, ci_id=device_id)
|
||||
|
||||
|
|
|
@ -31,10 +31,11 @@ class IpAddressManager(object):
|
|||
|
||||
return numfound, result
|
||||
|
||||
def _get_cis(self, ips):
|
||||
response, _, _, _, _, _ = SearchFromDB(
|
||||
"_type:{},{}:({})".format(self.type_id, IPAddressBuiltinAttributes.IP, ";".join(ips or [])),
|
||||
count=10000000, parent_node_perm_passed=True).search()
|
||||
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
|
||||
|
||||
|
@ -91,8 +92,8 @@ class IpAddressManager(object):
|
|||
else:
|
||||
return abort(400, ErrFormat.ipam_address_model_not_found)
|
||||
|
||||
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id))):
|
||||
cis = self._get_cis(ips)
|
||||
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 = []
|
||||
|
|
|
@ -43,7 +43,7 @@ class ScanHistoryManager(DBMixin):
|
|||
|
||||
if kwargs.get('ips'):
|
||||
from api.lib.cmdb.ipam.address import IpAddressManager
|
||||
IpAddressManager().assign_ips(kwargs['ips'], None, kwargs.get('cidr'),
|
||||
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)
|
||||
|
|
|
@ -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'])):
|
||||
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
|
||||
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'])):
|
||||
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
|
||||
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'])):
|
||||
with redis_lock.Lock(rd.r, 'CMDB_FILTER_{}_{}'.format(kwargs['type_id'], kwargs['rid']), expire=10):
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
ci_filter=None,
|
||||
|
|
|
@ -16,8 +16,9 @@ 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(
|
||||
|
@ -129,7 +130,8 @@ 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!")
|
||||
adr_plugin_attributes_list_required = _l("The attributes method must return a list") # attributes方法必须返回的是list
|
||||
# attributes方法必须返回的是list
|
||||
adr_plugin_attributes_list_required = _l("The attributes method must return a list")
|
||||
# attributes方法返回的list不能为空!
|
||||
adr_plugin_attributes_list_no_empty = _l("The list returned by the attributes method cannot be empty!")
|
||||
# 只有管理员才可以定义执行机器为: 所有节点!
|
||||
|
|
|
@ -107,3 +107,12 @@ 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
|
||||
"""
|
|
@ -6,6 +6,7 @@ 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
|
||||
|
@ -27,6 +28,7 @@ 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
|
||||
|
@ -141,7 +143,7 @@ class Search(object):
|
|||
if str(ci_type.id) in self.type_id_list:
|
||||
self.type_id_list.remove(str(ci_type.id))
|
||||
type_id_list.remove(str(ci_type.id))
|
||||
sub.extend([i for i in queries[1:] if isinstance(i, six.string_types)])
|
||||
sub.extend([i for i in queries[1:] if isinstance(i, (six.string_types, list))])
|
||||
|
||||
sub.insert(0, "_type:{}".format(ci_type.id))
|
||||
queries.append(dict(operator="|", queries=sub))
|
||||
|
@ -151,7 +153,9 @@ class Search(object):
|
|||
if not self.fl:
|
||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
else:
|
||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
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
|
||||
else:
|
||||
self.fl = self.fl or {}
|
||||
if not self.fl or isinstance(self.fl, dict):
|
||||
|
@ -433,6 +437,9 @@ 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]]))
|
||||
|
@ -525,11 +532,17 @@ class Search(object):
|
|||
query_sql = ""
|
||||
|
||||
for q in queries:
|
||||
# current_app.logger.debug(q)
|
||||
_query_sql = ""
|
||||
if isinstance(q, dict):
|
||||
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias, is_sub=True)
|
||||
# current_app.logger.info(_query_sql)
|
||||
# current_app.logger.info((operator, is_first, alias))
|
||||
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']
|
||||
|
||||
elif ":" in q and not q.startswith("*"):
|
||||
|
@ -537,10 +550,13 @@ class Search(object):
|
|||
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)
|
||||
|
||||
if is_first and _query_sql and not self.only_type_query:
|
||||
query_sql = "SELECT * FROM ({0}) AS {1}".format(_query_sql, alias)
|
||||
|
|
|
@ -64,7 +64,7 @@ class Search(object):
|
|||
|
||||
self.ancestor_ids = ancestor_ids
|
||||
self.descendant_ids = descendant_ids
|
||||
self.root_parent_path = root_parent_path
|
||||
self.root_parent_path = root_parent_path or []
|
||||
self.has_m2m = has_m2m or False
|
||||
if not self.has_m2m:
|
||||
if self.ancestor_ids:
|
||||
|
|
|
@ -55,7 +55,6 @@ 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 imp
|
||||
import importlib.util
|
||||
|
||||
import copy
|
||||
import jinja2
|
||||
|
@ -136,7 +136,7 @@ class AttributeValueManager(object):
|
|||
if not re.compile(expr).match(str(value)):
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None, unique_name=None):
|
||||
if not attr.is_reference:
|
||||
ci = ci or {}
|
||||
v = self._deserialize_value(attr.alias, attr.value_type, value)
|
||||
|
@ -146,7 +146,7 @@ class AttributeValueManager(object):
|
|||
else:
|
||||
v = value or None
|
||||
|
||||
attr.is_unique and self._check_is_unique(
|
||||
(attr.is_unique or attr.name == unique_name) 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 +198,11 @@ class AttributeValueManager(object):
|
|||
|
||||
try:
|
||||
path = script_f.name
|
||||
dir_name, name = os.path.dirname(path), os.path.basename(path)[:-3]
|
||||
name = os.path.basename(path)[:-3]
|
||||
|
||||
fp, path, desc = imp.find_module(name, [dir_name])
|
||||
|
||||
mod = imp.load_module(name, fp, path, desc)
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
if hasattr(mod, 'computed'):
|
||||
return mod.computed()
|
||||
|
@ -237,7 +237,10 @@ 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):
|
||||
def valid_attr_value(self, ci_dict, type_id, ci_id, name2attr,
|
||||
alias2attr=None,
|
||||
ci_attr2type_attr=None,
|
||||
unique_name=None):
|
||||
key2attr = dict()
|
||||
alias2attr = alias2attr or {}
|
||||
ci_attr2type_attr = ci_attr2type_attr or {}
|
||||
|
@ -268,7 +271,8 @@ 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))
|
||||
type_attr=ci_attr2type_attr.get(attr.id),
|
||||
unique_name=unique_name)
|
||||
ci_dict[key] = value
|
||||
except BadRequest as e:
|
||||
raise
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import functools
|
||||
|
||||
from flask import abort, session
|
||||
from flask import abort, session, current_app
|
||||
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,6 +15,7 @@ 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(f"employee_acl_rid == 0")
|
||||
result.append("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:
|
||||
result.append(
|
||||
f"remove_user_from_role employee_acl_rid: {employee.get('e_acl_rid')}, parent_id: {d_acl_rid}, err: {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)
|
||||
|
||||
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(f"employee_acl_rid == 0")
|
||||
result.append("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
|
||||
from flask import abort, current_app
|
||||
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 type(department_id) == list:
|
||||
if isinstance(department_id, list):
|
||||
if len(department_id) == 0:
|
||||
return []
|
||||
else:
|
||||
|
@ -702,6 +702,7 @@ 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()
|
||||
|
@ -712,6 +713,7 @@ 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 CompanyInfo, NoticeConfig
|
||||
from api.models.common_setting import NoticeConfig
|
||||
from wtforms import Form
|
||||
from wtforms import StringField
|
||||
from wtforms import validators
|
||||
|
|
|
@ -48,7 +48,9 @@ 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"],
|
||||
|
|
|
@ -6,7 +6,7 @@ from functools import wraps
|
|||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.perm.acl.cache import AppCache, AppAccessTokenCache
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
from api.lib.perm.acl.resp_format import ErrFormat
|
||||
|
||||
|
||||
|
|
|
@ -138,14 +138,14 @@ class HasResourceRoleCache(object):
|
|||
|
||||
@classmethod
|
||||
def add(cls, rid, app_id):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache', expire=10):
|
||||
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'):
|
||||
with redis_lock.Lock(rd.r, 'HasResourceRoleCache', expire=10):
|
||||
c = cls.get(app_id)
|
||||
c.pop(rid, None)
|
||||
cache.set(cls.PREFIX_KEY.format(app_id), c, timeout=0)
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
|
||||
import redis_lock
|
||||
import six
|
||||
from flask import abort
|
||||
|
@ -145,7 +142,7 @@ class RoleRelationCRUD(object):
|
|||
|
||||
@classmethod
|
||||
def add(cls, role, parent_id, child_ids, app_id):
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
|
||||
with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD", expire=10):
|
||||
db.session.commit()
|
||||
|
||||
result = []
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- 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.utcnow)
|
||||
last_login = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
date_joined = db.Column(db.DateTime, default=datetime.now)
|
||||
last_login = db.Column(db.DateTime, default=datetime.now)
|
||||
block = db.Column(db.Boolean, default=False)
|
||||
has_logined = db.Column(db.Boolean, default=False)
|
||||
wx_id = db.Column(db.String(32))
|
||||
|
|
|
@ -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)):
|
||||
with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id), expire=10):
|
||||
RoleRelationCache.rebuild(rid, app_id)
|
||||
|
||||
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))
|
||||
|
|
|
@ -145,7 +145,7 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
|
|||
@flush_db
|
||||
@reconnect_db
|
||||
def ci_relation_cache(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id), expire=10):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
|
@ -223,7 +223,7 @@ def ci_relation_add(parent_dict, child_id, uid):
|
|||
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
|
||||
@reconnect_db
|
||||
def ci_relation_delete(parent_id, child_id, ancestor_ids):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id)):
|
||||
with redis_lock.Lock(rd.r, "CIRelation_{}".format(parent_id), expire=10):
|
||||
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
|
||||
children = json.loads(children) if children is not None else {}
|
||||
|
||||
|
@ -374,3 +374,25 @@ 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)
|
||||
|
|
|
@ -30,6 +30,7 @@ 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
|
||||
|
@ -296,7 +297,10 @@ class AutoDiscoveryRuleSyncView(APIView):
|
|||
|
||||
rules, last_update_at1 = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at)
|
||||
|
||||
try:
|
||||
subnet_scan_rules, last_update_at2 = SubnetManager().scan_rules(oneagent_id, last_update_at)
|
||||
except AbortException:
|
||||
subnet_scan_rules, last_update_at2 = [], ""
|
||||
|
||||
return self.jsonify(rules=rules,
|
||||
subnet_scan_rules=subnet_scan_rules,
|
||||
|
|
|
@ -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,9 +64,13 @@ 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).to_dict()
|
||||
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 = []
|
||||
else:
|
||||
ci_types = CITypeManager().get_ci_types(q)
|
||||
count = len(ci_types)
|
||||
|
@ -497,6 +501,16 @@ 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"
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
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()
|
||||
|
||||
|
@ -87,3 +89,14 @@ class RackDeviceMigrateView(APIView):
|
|||
device_id=device_id,
|
||||
to_u_start=to_u_start,
|
||||
to_rack_id=to_rack_id)
|
||||
|
||||
|
||||
class RackCalcUFreeCountView(APIView):
|
||||
url_prefix = ("/dcim/rack/calc_u_free_count",)
|
||||
|
||||
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.DCIM,
|
||||
app_cli.op.read, app_cli.admin_name)
|
||||
def post(self):
|
||||
dcim_calc_u_free_count.apply_async(queue=CMDB_QUEUE)
|
||||
|
||||
return self.jsonify(code=200)
|
||||
|
|
|
@ -131,7 +131,7 @@ class EmployeeChangePasswordWithACLID(APIView):
|
|||
if not password:
|
||||
abort(400, ErrFormat.password_is_required)
|
||||
|
||||
data = EmployeeCRUD.change_password_by_uid(_uid, password)
|
||||
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 allowed_file, generate_new_file_name, CommonFileCRUD
|
||||
from api.lib.common_setting.upload_file import generate_new_file_name, CommonFileCRUD
|
||||
from api.resource import APIView
|
||||
|
||||
prefix = '/file'
|
||||
|
|
|
@ -58,3 +58,4 @@ python-magic==0.4.27
|
|||
jsonpath==0.82.2
|
||||
networkx>=3.1
|
||||
ipaddress>=1.0.23
|
||||
ruff==0.8.3
|
||||
|
|
|
@ -54,6 +54,54 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-servicetree</div>
|
||||
<div class="code-name">&#xea0b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-switch (1)</div>
|
||||
<div class="code-name">&#xea0a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-label</div>
|
||||
<div class="code-name">&#xea09;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_acl</div>
|
||||
<div class="code-name">&#xea08;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_ticket</div>
|
||||
<div class="code-name">&#xea06;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_agent</div>
|
||||
<div class="code-name">&#xea07;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-table_download</div>
|
||||
<div class="code-name">&#xea05;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-image_download</div>
|
||||
<div class="code-name">&#xea04;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-rear</div>
|
||||
|
@ -6162,9 +6210,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
|
||||
url('iconfont.woff?t=1732673294759') format('woff'),
|
||||
url('iconfont.ttf?t=1732673294759') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
@ -6190,6 +6238,78 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-servicetree"></span>
|
||||
<div class="name">
|
||||
veops-servicetree
|
||||
</div>
|
||||
<div class="code-name">.veops-servicetree
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-switch1"></span>
|
||||
<div class="name">
|
||||
veops-switch (1)
|
||||
</div>
|
||||
<div class="code-name">.veops-switch1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-label"></span>
|
||||
<div class="name">
|
||||
veops-label
|
||||
</div>
|
||||
<div class="code-name">.veops-label
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_acl"></span>
|
||||
<div class="name">
|
||||
top_acl
|
||||
</div>
|
||||
<div class="code-name">.top_acl
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_ticket"></span>
|
||||
<div class="name">
|
||||
top_ticket
|
||||
</div>
|
||||
<div class="code-name">.top_ticket
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_agent"></span>
|
||||
<div class="name">
|
||||
top_agent
|
||||
</div>
|
||||
<div class="code-name">.top_agent
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-table_download"></span>
|
||||
<div class="name">
|
||||
itsm-table_download
|
||||
</div>
|
||||
<div class="code-name">.itsm-table_download
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-image_download"></span>
|
||||
<div class="name">
|
||||
itsm-image_download
|
||||
</div>
|
||||
<div class="code-name">.itsm-image_download
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-rear"></span>
|
||||
<div class="name">
|
||||
|
@ -15352,6 +15472,70 @@
|
|||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-servicetree"></use>
|
||||
</svg>
|
||||
<div class="name">veops-servicetree</div>
|
||||
<div class="code-name">#veops-servicetree</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-switch1"></use>
|
||||
</svg>
|
||||
<div class="name">veops-switch (1)</div>
|
||||
<div class="code-name">#veops-switch1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-label"></use>
|
||||
</svg>
|
||||
<div class="name">veops-label</div>
|
||||
<div class="code-name">#veops-label</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_acl"></use>
|
||||
</svg>
|
||||
<div class="name">top_acl</div>
|
||||
<div class="code-name">#top_acl</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_ticket"></use>
|
||||
</svg>
|
||||
<div class="name">top_ticket</div>
|
||||
<div class="code-name">#top_ticket</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_agent"></use>
|
||||
</svg>
|
||||
<div class="name">top_agent</div>
|
||||
<div class="code-name">#top_agent</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-table_download"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-table_download</div>
|
||||
<div class="code-name">#itsm-table_download</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-image_download"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-image_download</div>
|
||||
<div class="code-name">#itsm-image_download</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-rear"></use>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
|
||||
url('iconfont.woff?t=1732673294759') format('woff'),
|
||||
url('iconfont.ttf?t=1732673294759') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -13,6 +13,38 @@
|
|||
-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";
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,62 @@
|
|||
"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",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 1.1 MiB |
|
@ -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"
|
||||
class="select-filter-component ops-select-bg"
|
||||
: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"
|
||||
class="select-filter-component ops-select-bg"
|
||||
:style="{ width: '175px' }"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('placeholder2')"
|
||||
|
@ -398,7 +398,6 @@ export default {
|
|||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 24px;
|
||||
background: #f7f8fa;
|
||||
line-height: 24px;
|
||||
border: none;
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ export default {
|
|||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
background-color: @primary-color_5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@ export default {
|
|||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
<style lang="less" scoped>
|
||||
.pop_btn {
|
||||
text-align: right;
|
||||
margin-top: 24px;
|
||||
|
|
|
@ -11,10 +11,19 @@
|
|||
|
||||
.ant-input {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background-color: #F7F8FA;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 30px;
|
||||
|
||||
&:focus {
|
||||
border: solid 1px #B1C9FF;
|
||||
}
|
||||
}
|
||||
|
||||
.cmdb-side-menu-search-focused {
|
||||
.ant-input {
|
||||
border: solid 1px #B1C9FF;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-suffix {
|
||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.$confirm({
|
||||
title: this.$t('alert'),
|
||||
title: this.$t('warning'),
|
||||
content: this.$t('cmdb.preference.confirmcancelSub2', { name: menu.meta.title }),
|
||||
onOk() {
|
||||
const citypeId = menu.meta.typeId
|
||||
|
@ -171,7 +171,6 @@ 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 } }
|
||||
|
@ -205,11 +204,9 @@ 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>
|
||||
)
|
||||
},
|
||||
|
@ -313,10 +310,7 @@ export default {
|
|||
<Item class={styles['cmdb-side-menu-search']}>
|
||||
<a-input
|
||||
ref="cmdbSideMenuSearchInputRef"
|
||||
class={styles['cmdb-side-menu-search-input']}
|
||||
style={{
|
||||
border: this.$route.name === 'cmdb_resource_search' ? 'solid 1px #B1C9FF' : ''
|
||||
}}
|
||||
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)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div :style="{ paddingTop: '10px' }">
|
||||
<img :style="{ width: '140px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<img :style="{ width: '140px', height: '120px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
|
|
|
@ -226,16 +226,16 @@ export default {
|
|||
right: 4px;
|
||||
display: none;
|
||||
&:hover {
|
||||
color: #1f78d1;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&:hover .ant-transfer-list-icon {
|
||||
display: inline;
|
||||
background-color: #c0eaff;
|
||||
background-color: @primary-color_4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #f0faff;
|
||||
background-color: ~'@{primary-color_8}35';
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -195,11 +195,11 @@ export default {
|
|||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
.acl-resource-types-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -358,11 +358,11 @@ export default {
|
|||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
padding: 12px 24px 24px 24px;
|
||||
padding: 8px 20px 20px 20px;
|
||||
.acl-resources-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.ant-switch {
|
||||
|
|
|
@ -58,10 +58,7 @@
|
|||
:title="$t('acl.visualRole')"
|
||||
:width="120"
|
||||
align="center"
|
||||
:filters="[
|
||||
{ label: $t('yes'), value: 1 },
|
||||
{ label: $t('no'), value: 0 },
|
||||
]"
|
||||
:filters="visualRoleFilters"
|
||||
:filterMultiple="false"
|
||||
:filter-method="
|
||||
({ value, row }) => {
|
||||
|
@ -155,6 +152,10 @@ 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: {
|
||||
|
@ -291,11 +292,11 @@ export default {
|
|||
background-color: #fff;
|
||||
height: calc(100vh - 64px);
|
||||
margin-bottom: -24px;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
.acl-roles-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
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: 24px;
|
||||
padding: 20px;
|
||||
.acl-trigger-header {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,6 +207,13 @@ export function deleteTrigger(type_id, id) {
|
|||
})
|
||||
}
|
||||
|
||||
export function testTrigger(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}/test_notify`,
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
||||
// CMDB的模型和实例的授权接口
|
||||
export function grantCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
|
|
|
@ -84,3 +84,10 @@ export function getDCIMHistoryOperate(params) {
|
|||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function calcUnitFreeCount() {
|
||||
return axios({
|
||||
url: `/v0.1/dcim/rack/calc_u_free_count`,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ export default {
|
|||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: @primary-color;
|
||||
border: solid 3px #E2E7FC;
|
||||
border: solid 3px @primary-color_4;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
class="ops-stripe-table"
|
||||
:data="filterTableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
:row-class-name="(params) => getCurrentRowClass(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
|
@ -24,7 +24,7 @@
|
|||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
|
||||
<div v-if="loading()" class="ci-type-grant-loading">
|
||||
<a-icon type="loading" /> {{ $t('loading') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
|
@ -45,7 +45,7 @@ import _ from 'lodash'
|
|||
import { permMap } from './constants.js'
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import ReadCheckbox from './readCheckbox.vue'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
import { getCurrentRowClass } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeGrant',
|
||||
|
@ -100,7 +100,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
getCurrentRowClass,
|
||||
async handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
|
@ -146,5 +146,11 @@ export default {
|
|||
<style lang="less" scoped>
|
||||
.ci-type-grant {
|
||||
padding: 10px 0;
|
||||
|
||||
&-loading {
|
||||
height: 200px;
|
||||
line-height: 200px;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -88,6 +88,7 @@ import ReadGrantModal from './readGrantModal'
|
|||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import TopologyViewGrant from './topologyViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
|
@ -186,6 +187,13 @@ export default {
|
|||
},
|
||||
getFilterPermissions() {
|
||||
ciTypeFilterPermissions(this.CITypeId).then((res) => {
|
||||
Object.keys(res).forEach((key) => {
|
||||
const attr_filter = res?.[key]?.attr_filter
|
||||
if (attr_filter?.length) {
|
||||
res[key].attr_filter = attr_filter.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item))
|
||||
}
|
||||
})
|
||||
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
},
|
||||
|
@ -352,7 +360,6 @@ export default {
|
|||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
padding: 6px 8px;
|
||||
|
@ -363,9 +370,13 @@ export default {
|
|||
margin: 15px 0;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 2px 3px 4px @primary-color_5;
|
||||
z-index: 1;
|
||||
|
||||
.btn-wave-hover(@primary-color_4, -1);
|
||||
}
|
||||
|
||||
.grant-table-row-focus {
|
||||
background-color: @primary-color_8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
:row-class-name="(params) => getCurrentRowClass(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
|
@ -26,7 +26,7 @@
|
|||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantRelationView, revokeRelationView } from '../../api/preference.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
import { getCurrentRowClass } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'RelationViewGrant',
|
||||
|
@ -69,7 +69,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
getCurrentRowClass,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
<a-form-model :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-model-item :label="$t('user')">
|
||||
<EmployeeTreeSelect
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
class="custom-treeselect custom-treeselect-white"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '18px',
|
||||
}"
|
||||
:multiple="true"
|
||||
|
@ -24,12 +22,10 @@
|
|||
v-model="form.roles"
|
||||
:multiple="true"
|
||||
:options="filterAllRoles"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
class="custom-treeselect custom-treeselect-white"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '18px',
|
||||
}"
|
||||
:limit="10"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:scroll-y="{enabled: true}"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
:row-class-name="(params) => getCurrentRowClass(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
|
@ -27,7 +27,7 @@
|
|||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTopologyView, revokeTopologyView } from '@/modules/cmdb/api/topology.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
import { getCurrentRowClass } from './utils'
|
||||
export default {
|
||||
name: 'TopologyViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
|
@ -73,7 +73,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
getCurrentRowClass,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
:row-class-name="(params) => getCurrentRowClass(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
|
@ -26,7 +26,7 @@
|
|||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
import { getCurrentRowClass } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'TypeRelationGrant',
|
||||
|
@ -69,7 +69,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
getCurrentRowClass,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
:disabled="disabled"
|
||||
ref="cmdb_type_select"
|
||||
:disable-branch-nodes="true"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
class="custom-treeselect custom-treeselect-white"
|
||||
:style="{
|
||||
'--custom-height': '30px',
|
||||
lineHeight: '30px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
lineHeight: '30px'
|
||||
}"
|
||||
v-model="currenCiType"
|
||||
:multiple="multiple"
|
||||
|
|
|
@ -16,12 +16,8 @@
|
|||
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
|
||||
<span
|
||||
v-if="item.name.length > 6"
|
||||
class="preference-search-tag"
|
||||
:class="['preference-search-tag', item.id === currentPreferenceSearch ? 'preference-search-tag-focus' : '']"
|
||||
:key="`${item.id}_${index}`"
|
||||
:style="{
|
||||
backgroundColor: item.id === currentPreferenceSearch ? '#2f54eb' : '',
|
||||
color: item.id === currentPreferenceSearch ? '#fff' : '',
|
||||
}"
|
||||
>
|
||||
<a-tooltip :title="item.name">
|
||||
<span @click="clickPreferenceSearch(item)">{{ `${item.name.slice(0, 6)}...` }}</span>
|
||||
|
@ -33,11 +29,7 @@
|
|||
<span
|
||||
v-else
|
||||
:key="`${item.id}_${index}`"
|
||||
class="preference-search-tag"
|
||||
:style="{
|
||||
backgroundColor: item.id === currentPreferenceSearch ? '#2f54eb' : '#fafafa',
|
||||
color: item.id === currentPreferenceSearch ? '#fff' : '#000000a6',
|
||||
}"
|
||||
:class="['preference-search-tag', item.id === currentPreferenceSearch ? 'preference-search-tag-focus' : '']"
|
||||
>
|
||||
<span @click="clickPreferenceSearch(item)">{{ item.name }}</span>
|
||||
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deletePreferenceSearch(item)">
|
||||
|
@ -189,6 +181,15 @@ export default {
|
|||
> i {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&-focus {
|
||||
background-color: @primary-color;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
}
|
||||
.preference-search-delete {
|
||||
color: #a9a9a9;
|
||||
|
|
|
@ -5,13 +5,11 @@
|
|||
<a-space>
|
||||
<treeselect
|
||||
v-if="type === 'resourceSearch'"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
class="custom-treeselect"
|
||||
:style="{
|
||||
width: '200px',
|
||||
marginRight: '10px',
|
||||
'--custom-height': '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '16px',
|
||||
}"
|
||||
v-model="currenCiType"
|
||||
|
@ -55,7 +53,7 @@
|
|||
<a-icon
|
||||
type="search"
|
||||
slot="suffix"
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
|
||||
:class="['search-form-bar-input-icon', fuzzySearch ? 'search-form-bar-input-icon-focus' : '']"
|
||||
@click="emitRefresh"
|
||||
/>
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
|
||||
|
@ -310,6 +308,16 @@ export default {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
|
||||
&-input-icon {
|
||||
cursor: pointer;
|
||||
color: #d9d9d9;
|
||||
|
||||
&-focus {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.search-form-bar-filter {
|
||||
.ops_display_wrapper(transparent);
|
||||
|
||||
|
|
|
@ -135,9 +135,15 @@ export default {
|
|||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.authorization-input {
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<tr v-for="(item, index) in headers" :key="item.id">
|
||||
<td><a-input class="headers-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
|
||||
<td><a-input class="headers-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
|
||||
<td>
|
||||
<td class="headers-delete">
|
||||
<a style="color:red">
|
||||
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||
</a>
|
||||
|
@ -92,10 +92,20 @@ export default {
|
|||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.headers-input {
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.headers-delete {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
<a-input-group compact>
|
||||
<treeselect
|
||||
:disable-branch-nodes="true"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
class="custom-treeselect custom-treeselect-white"
|
||||
:style="{
|
||||
'--custom-height': '30px',
|
||||
lineHeight: '30px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
display: 'inline-block',
|
||||
width: '100px',
|
||||
}"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<tr v-for="(item, index) in parameters" :key="item.id">
|
||||
<td><a-input class="parameters-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
|
||||
<td><a-input class="parameters-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
|
||||
<td>
|
||||
<td class="parameters-delete">
|
||||
<a style="color:red">
|
||||
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||
</a>
|
||||
|
@ -91,10 +91,20 @@ export default {
|
|||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.parameters-input {
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.parameters-delete {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -26,7 +26,8 @@ const cmdb_en = {
|
|||
ad: 'AutoDiscovery',
|
||||
cidetail: 'CI Detail',
|
||||
scene: 'Scene',
|
||||
dcim: 'DCIM'
|
||||
dcim: 'DCIM',
|
||||
serviceTree: 'Service Tree'
|
||||
},
|
||||
ciType: {
|
||||
ciType: 'CIType',
|
||||
|
@ -186,6 +187,9 @@ const cmdb_en = {
|
|||
botSelect: 'Please select a robot',
|
||||
refAttributeTips: 'The title and content can reference the attribute value of the CIType. The reference method is: {{ attr_name }}',
|
||||
webhookRefAttributeTips: 'Request parameters can reference the attribute value of the model. The reference method is: {{ attr_name }}',
|
||||
testSend: 'Test Send',
|
||||
testSendTip: 'Please save the trigger first',
|
||||
testSendSuccess: 'Send Success',
|
||||
newTrigger: 'Add trigger',
|
||||
editTriggerTitle: 'Edit trigger {name}',
|
||||
newTriggerTitle: 'Add trigger {name}',
|
||||
|
@ -311,6 +315,10 @@ const cmdb_en = {
|
|||
enum: 'Enum',
|
||||
ciGrantTip: `Filter conditions can be changed dynamically using {{}} referenced variables, currently user variables are supported, such as {{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: 'Please search for resource keywords',
|
||||
columnSearchInputTip: '192.168.1.1\n192.168.1.2\n192.168.1.3',
|
||||
rowSearchMode: 'Single Row',
|
||||
columnSearchMode: 'Multi Row',
|
||||
columnSearchTip: 'Supports retrieval of short texts only',
|
||||
resourceSearch: 'Resource Search',
|
||||
recentSearch: 'Recent Search',
|
||||
myCollection: 'My Collection',
|
||||
|
@ -894,7 +902,11 @@ if __name__ == "__main__":
|
|||
deviceName: 'Device Name',
|
||||
removeDevice: 'Remove Device',
|
||||
moveDevice: 'Move Device',
|
||||
rackDetail: 'Rack Detail'
|
||||
rackDetail: 'Rack Detail',
|
||||
calcUnitFreeCount: 'Calculate Rack Free Unit Count',
|
||||
calcUnitFreeCountTip: 'Calculating in the background, refresh the page later to see the result',
|
||||
calcUnitFreeCountTip1: 'Calculate Trigger Success, refresh the page later to see the result',
|
||||
calcUnitFreeCountTip2: `Confirm that you want to calculate the number of free Units for all rack?`
|
||||
}
|
||||
}
|
||||
export default cmdb_en
|
||||
|
|
|
@ -26,7 +26,8 @@ const cmdb_zh = {
|
|||
ad: '自动发现',
|
||||
cidetail: 'CI 详情',
|
||||
scene: '场景',
|
||||
dcim: '数据中心'
|
||||
dcim: '数据中心',
|
||||
serviceTree: '服务树'
|
||||
},
|
||||
ciType: {
|
||||
ciType: '模型',
|
||||
|
@ -186,6 +187,9 @@ const cmdb_zh = {
|
|||
botSelect: '请选择机器人',
|
||||
refAttributeTips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}',
|
||||
webhookRefAttributeTips: '请求参数可以引用该模型的属性值,引用方法为: {{ attr_name }}',
|
||||
testSend: '测试发送',
|
||||
testSendTip: '请先保存触发器',
|
||||
testSendSuccess: '发送成功',
|
||||
newTrigger: '新增触发器',
|
||||
editTriggerTitle: '编辑触发器 {name}',
|
||||
newTriggerTitle: '新增触发器 {name}',
|
||||
|
@ -311,6 +315,10 @@ const cmdb_zh = {
|
|||
enum: '枚举',
|
||||
ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: '请搜索资源关键字',
|
||||
columnSearchInputTip: '192.168.1.1\n192.168.1.2\n192.168.1.3',
|
||||
rowSearchMode: '单行',
|
||||
columnSearchMode: '多行',
|
||||
columnSearchTip: '仅支持检索短文本',
|
||||
resourceSearch: '资源搜索',
|
||||
recentSearch: '最近搜索',
|
||||
myCollection: '我的收藏',
|
||||
|
@ -893,7 +901,11 @@ if __name__ == "__main__":
|
|||
deviceName: '设备名',
|
||||
removeDevice: '删除设备',
|
||||
moveDevice: '移动设备',
|
||||
rackDetail: '机柜详情'
|
||||
rackDetail: '机柜详情',
|
||||
calcUnitFreeCount: '计算机柜空闲U数',
|
||||
calcUnitFreeCountTip: '后台计算中,稍后刷新页面查看结果',
|
||||
calcUnitFreeCountTip1: '计算触发成功,稍后刷新页面查看结果',
|
||||
calcUnitFreeCountTip2: '确认要计算所有机柜的空闲U数?'
|
||||
}
|
||||
}
|
||||
export default cmdb_zh
|
||||
|
|
|
@ -27,6 +27,17 @@ const genCmdbRoutes = async () => {
|
|||
name: 'cmdb_disabled1',
|
||||
meta: { title: 'cmdb.menu.resources', disabled: true },
|
||||
},
|
||||
{
|
||||
path: '/cmdb/relationviews/:viewId?',
|
||||
name: 'cmdb_relation_views',
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: {
|
||||
title: 'cmdb.menu.serviceTree',
|
||||
appName: 'cmdb',
|
||||
icon: 'veops-servicetree',
|
||||
keepAlive: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/cmdb/resourceviews',
|
||||
name: 'cmdb_resource_views',
|
||||
|
@ -194,15 +205,14 @@ const genCmdbRoutes = async () => {
|
|||
} else {
|
||||
routes.redirect = '/cmdb/dashboard'
|
||||
}
|
||||
const relationViews = relation.name2id.map(item => {
|
||||
return {
|
||||
path: `/cmdb/relationviews/${item[1]}`,
|
||||
name: `cmdb_relation_views_${item[1]}`,
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
|
||||
|
||||
if (relation?.name2id?.length === 0) {
|
||||
const relationViewRouteIndex = routes.children?.findIndex?.((route) => route.name === 'cmdb_relation_views')
|
||||
if (relationViewRouteIndex >= 0) {
|
||||
routes.children.splice(relationViewRouteIndex, 1)
|
||||
}
|
||||
})
|
||||
routes.children.splice(resourceViewsIndex, 0, ...relationViews)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<p v-html="$t('cmdb.batch.drawTips1')"></p>
|
||||
<p v-html="$t('cmdb.batch.drawTips2')"></p>
|
||||
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
|
||||
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
|
||||
<span><a-icon type="file" class="cmdb-batch-upload-dragger-file-icon"/>{{ item.name }}</span>
|
||||
<a-progress :status="progressStatus" :percent="percent" />
|
||||
</div>
|
||||
</a-upload-dragger>
|
||||
|
@ -92,18 +92,17 @@ export default {
|
|||
}
|
||||
.ant-upload.ant-upload-drag {
|
||||
border: none;
|
||||
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
|
||||
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
|
||||
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
|
||||
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
||||
background: ~'linear-gradient(90deg, @{text-color_5} 50%, transparent 0) repeat-x 0 0 / 15px 1px, linear-gradient(90deg, @{text-color_5} 50%, transparent 0) repeat-x 0 100% / 15px 1px, linear-gradient(0deg, @{text-color_5} 50%, transparent 0) repeat-y 0 0 / 1px 15px, linear-gradient(0deg, @{text-color_5} 50%, transparent 0) repeat-y 100% 0 / 1px 15px';
|
||||
.ant-upload-drag-container > i {
|
||||
font-size: 60px;
|
||||
}
|
||||
.cmdb-batch-upload-tips {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ~'linear-gradient(90deg, @{primary-color_2} 50%, transparent 0) repeat-x 0 0 / 15px 1px, linear-gradient(90deg, @{primary-color_2} 50%, transparent 0) repeat-x 0 100% / 15px 1px, linear-gradient(0deg, @{primary-color_2} 50%, transparent 0) repeat-y 0 0 / 1px 15px, linear-gradient(0deg, @{primary-color_2} 50%, transparent 0) repeat-y 100% 0 / 1px 15px, @{primary-color_7}';
|
||||
}
|
||||
}
|
||||
.ant-upload.ant-upload-drag .ant-upload-drag-container {
|
||||
vertical-align: baseline;
|
||||
|
@ -129,6 +128,11 @@ export default {
|
|||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
color: @primary-color;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.cmdb-batch-upload-tips {
|
||||
width: 50%;
|
||||
|
|
|
@ -9,13 +9,11 @@
|
|||
"
|
||||
:title="$t('cmdb.ci.attributeDesc')"
|
||||
width="72%"
|
||||
:bodyStyle="{ height: '100vh' }"
|
||||
:bodyStyle="{ height: '100vh', paddingTop: '16px' }"
|
||||
>
|
||||
<vxe-toolbar>
|
||||
<template #buttons>
|
||||
<a-input
|
||||
v-model="searchKey"
|
||||
:style="{ display: 'inline-block', width: '244px' }"
|
||||
:style="{ display: 'inline-block', width: '244px', marginBottom: '16px' }"
|
||||
class="ops-input ops-input-radius"
|
||||
type="search"
|
||||
:placeholder="$t('cmdb.ci.tips5')"
|
||||
|
@ -23,8 +21,6 @@
|
|||
>
|
||||
<a-icon type="search" slot="suffix" />
|
||||
</a-input>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<vxe-table
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ id }}
|
||||
{{ getReferenceName(id, column) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
|
@ -102,7 +102,7 @@
|
|||
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ id }}
|
||||
{{ getReferenceName(id, column) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #operation_default="{ row }">
|
||||
|
@ -133,9 +133,11 @@
|
|||
import _ from 'lodash'
|
||||
import { getCITypeChildren, getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { searchCIRelation, deleteCIRelationView } from '@/modules/cmdb/api/CIRelation'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailRelationTopo from './ciDetailRelationTopo/index.vue'
|
||||
import Node from './ciDetailRelationTopo/node.js'
|
||||
import AddTableModal from '../../relation_views/modules/AddTableModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'CiDetailRelation',
|
||||
components: { CiDetailRelationTopo, AddTableModal },
|
||||
|
@ -172,7 +174,8 @@ export default {
|
|||
topoData: {
|
||||
nodes: {},
|
||||
edges: []
|
||||
}
|
||||
},
|
||||
referenceCINameMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -211,9 +214,14 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async init(isFirst) {
|
||||
const ci_types_list = this.ci_types()
|
||||
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
|
||||
if (!_findCiType) {
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
|
||||
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
|
||||
const ci_types_list = this.ci_types()
|
||||
this.handleTopoData()
|
||||
if (
|
||||
isFirst &&
|
||||
|
@ -223,6 +231,8 @@ export default {
|
|||
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
|
||||
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
|
||||
}
|
||||
|
||||
this.handleReferenceCINameMap()
|
||||
})
|
||||
},
|
||||
async getFirstCIs() {
|
||||
|
@ -394,6 +404,98 @@ export default {
|
|||
this.secondCIColumns = secondCIColumns
|
||||
this.secondCIJsonAttr = secondCIJsonAttr
|
||||
},
|
||||
|
||||
async handleReferenceCINameMap() {
|
||||
const CITypes = _.unionBy(
|
||||
[
|
||||
...this.parentCITypes,
|
||||
...this.childCITypes
|
||||
],
|
||||
'id'
|
||||
)
|
||||
const CIList = _.unionBy(
|
||||
_.flatten(
|
||||
[
|
||||
...Object.values(this.firstCIs),
|
||||
...Object.values(this.secondCIs)
|
||||
]
|
||||
),
|
||||
'_id'
|
||||
)
|
||||
|
||||
const CIMap = {}
|
||||
CIList.forEach((ci) => {
|
||||
if (!CIMap[ci._type]) {
|
||||
CIMap[ci._type] = []
|
||||
}
|
||||
CIMap[ci._type].push(ci)
|
||||
})
|
||||
|
||||
const referenceCINameMap = {}
|
||||
CITypes.forEach((CIType) => {
|
||||
CIType.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
const currentCIList = CIMap[CIType.id]
|
||||
if (currentCIList?.length) {
|
||||
currentCIList.forEach((ci) => {
|
||||
const ids = Array.isArray(ci[attr.name]) ? ci[attr.name] : ci[attr.name] ? [ci[attr.name]] : []
|
||||
|
||||
if (ids.length) {
|
||||
if (!referenceCINameMap?.[attr.reference_type_id]) {
|
||||
referenceCINameMap[attr.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
referenceCINameMap[attr.reference_type_id][id] = ''
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(referenceCINameMap).length) {
|
||||
return
|
||||
}
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(referenceCINameMap).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(referenceCINameMap[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
const CITypeList = this.ci_types()
|
||||
const showNameMap = {}
|
||||
|
||||
Object.keys(referenceCINameMap).forEach((id) => {
|
||||
const CIType = CITypeList.find((CIType) => Number(CIType.id) === Number(id))
|
||||
|
||||
showNameMap[id] = {
|
||||
show_name: CIType?.show_name,
|
||||
unique_key: CIType?.unique_key
|
||||
}
|
||||
})
|
||||
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
if (referenceCINameMap?.[item._type]?.[item._id] === '') {
|
||||
const showName = showNameMap?.[item._type]
|
||||
|
||||
referenceCINameMap[item._type][item._id] = item?.[showName?.show_name] ?? item?.[showName?.unique_key] ?? ''
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.referenceCINameMap = referenceCINameMap
|
||||
},
|
||||
|
||||
getReferenceName(id, column) {
|
||||
const typeId = column?.params?.attr?.reference_type_id
|
||||
return this.referenceCINameMap?.[typeId]?.[id] || id
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.init()
|
||||
},
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
|
||||
<div :style="{ padding: '16px 24px 24px', height: '100%' }">
|
||||
<a-space :style="{ marginBottom: '16px', display: 'flex' }">
|
||||
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
|
||||
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
|
||||
</a-button>
|
||||
|
@ -180,7 +180,7 @@ export default {
|
|||
ci_types: [],
|
||||
hasPermission: true,
|
||||
itsmInstalled: true,
|
||||
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
|
||||
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 130),
|
||||
initQueryLoading: true,
|
||||
}
|
||||
},
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
attr.name,
|
||||
{
|
||||
rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
|
||||
initialValue: attr.default && attr.default.default ? attr.default.default : null,
|
||||
initialValue: attr.default && attr.default.default !== undefined && attr.default.default !== null ? attr.default.default : null,
|
||||
},
|
||||
]"
|
||||
style="width: 100%"
|
||||
|
@ -148,6 +148,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
@ -210,7 +211,7 @@ export default {
|
|||
},
|
||||
|
||||
getChoiceDefault(attr) {
|
||||
if (!attr?.default?.default) {
|
||||
if (_.isNil(attr?.default?.default)) {
|
||||
return attr.is_list ? [] : null
|
||||
}
|
||||
|
||||
|
|
|
@ -61,16 +61,20 @@ export default {
|
|||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
background-color: #F7F8FA;
|
||||
color: @text-color_2;
|
||||
background-color: @primary-color_7;
|
||||
width: 105px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
&-active {
|
||||
border: solid 1px #B1C9FF;
|
||||
background-color: #E1EFFF;
|
||||
color: #2F54EB;
|
||||
border: solid 1px @primary-color_8;
|
||||
background-color: @primary-color_4;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,17 @@ export default {
|
|||
margin-left: 6px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @primary-color_5;
|
||||
|
||||
.attr-ad-tab-edit {
|
||||
display: inline-block;
|
||||
}
|
||||
.attr-ad-tab-delete {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&_active {
|
||||
border: solid 1px @primary-color_8;
|
||||
background-color: @primary-color_6;
|
||||
|
@ -161,14 +172,9 @@ export default {
|
|||
.attr-ad-tab-name {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.attr-ad-tab-edit {
|
||||
display: inline-block;
|
||||
}
|
||||
.attr-ad-tab-delete {
|
||||
display: inline-block;
|
||||
background-color: @primary-color_6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +184,11 @@ export default {
|
|||
background-color: @primary-color_7;
|
||||
font-size: 12px;
|
||||
color: @text-color_4;
|
||||
|
||||
&:hover {
|
||||
background-color: @primary-color_5;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
<div class="attr-ad-header attr-ad-header-margin">{{ $t('cmdb.ciType.configCheckTitle') }}</div>
|
||||
<div class="attr-ad-content">
|
||||
<div class="ad-test-title-info">{{ $t('cmdb.ciType.checkTestTip') }}</div>
|
||||
<div
|
||||
class="ad-test-btn"
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ops-button-ghost ad-test-btn"
|
||||
ghost
|
||||
@click="showCheckModal"
|
||||
>
|
||||
{{ $t('cmdb.ciType.checkTestBtn') }}
|
||||
</div>
|
||||
</a-button>
|
||||
<div class="ad-test-btn-info">{{ $t('cmdb.ciType.checkTestTip2') }}</div>
|
||||
<!-- <div
|
||||
class="ad-test-btn"
|
||||
|
@ -140,15 +142,6 @@ export default {
|
|||
|
||||
.ad-test-btn {
|
||||
margin-top: 30px;
|
||||
padding: 5px 12px;
|
||||
background-color: #F4F9FF;
|
||||
border: solid 1px @primary-color_8;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
|
||||
color: @link-color;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ad-test-btn-info {
|
||||
|
|
|
@ -47,8 +47,8 @@
|
|||
<a-descriptions layout="horizontal" bordered size="small" :column="2">
|
||||
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
|
||||
<ops-icon
|
||||
:style="{ color: property[item.property] ? '#7f97fa' : '', fontSize: '10px' }"
|
||||
:type="`ops-${item.property}-disabled`"
|
||||
:class="['attribute-card-footer-icon', property[item.property] ? 'attribute-card-footer-icon-mark' : '']"
|
||||
/>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label></a-descriptions-item>
|
||||
|
@ -58,7 +58,7 @@
|
|||
<ops-icon
|
||||
v-for="item in propertyList.filter((p) => property[p.property])"
|
||||
:key="item.property"
|
||||
:style="{ color: '#7f97fa', fontSize: '10px' }"
|
||||
class="attribute-card-footer-icon attribute-card-footer-icon-mark"
|
||||
:type="`ops-${item.property}-disabled`"
|
||||
/>
|
||||
</a-space>
|
||||
|
@ -247,13 +247,13 @@ export default {
|
|||
.attribute-card {
|
||||
width: 172px;
|
||||
height: 75px;
|
||||
background: @primary-color_6;
|
||||
background-color: @primary-color_6;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px #4e5ea066;
|
||||
box-shadow: 0 4px 12px @primary-color_8;
|
||||
.attribute-card-operation {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
@ -342,6 +342,15 @@ export default {
|
|||
padding: 2px 0 2px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-card-footer-icon {
|
||||
font-size: 10px;
|
||||
|
||||
&-mark {
|
||||
color: @primary-color_2;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-card-inherited {
|
||||
background: @primary-color_7;
|
||||
.attribute-card-footer {
|
||||
|
@ -356,10 +365,10 @@ export default {
|
|||
justify-content: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: inherit;
|
||||
background-color: inherit !important;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background-color: @primary-color_6;
|
||||
box-shadow: none !important;
|
||||
background-color: @primary-color_6 !important;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
|
|
|
@ -659,7 +659,7 @@ export default {
|
|||
} else {
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
default_value: _record.default && _record.default.default ? _record.default.default : null,
|
||||
default_value: _record?.default?.default ?? null,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
:type="ci.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ci.name[0].toUpperCase() }}</span>
|
||||
<span class="primary-color" v-else>{{ ci.name[0].toUpperCase() }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
|
||||
|
@ -1194,7 +1194,7 @@ export default {
|
|||
.selected {
|
||||
background-color: @primary-color_3;
|
||||
.ci-types-left-detail-title {
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
:title="$t('cmdb.ciType.choiceWebhookTips')"
|
||||
>
|
||||
<a-icon
|
||||
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
|
||||
class="tab-webhook-filter-icon"
|
||||
type="question-circle"
|
||||
theme="filled"
|
||||
/>
|
||||
|
@ -61,13 +61,11 @@
|
|||
:disable-branch-nodes="true"
|
||||
:class="{
|
||||
'custom-treeselect': true,
|
||||
'custom-treeselect-bgcAndBorder': true,
|
||||
'custom-treeselect-white': true,
|
||||
}"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '14px',
|
||||
}"
|
||||
v-model="choice_other.type_ids"
|
||||
|
@ -555,7 +553,7 @@ export default {
|
|||
|
||||
&-tag {
|
||||
background-color: #E1EFFF;
|
||||
color: #2F54EB;
|
||||
color: @primary-color;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
padding: 0 3px;
|
||||
|
@ -577,6 +575,13 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.tab-webhook-filter-icon {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: -17px;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
.script-tip {
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
|
|
|
@ -363,7 +363,7 @@ export default {
|
|||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: @primary-color;
|
||||
border: solid 3px #E2E7FC;
|
||||
border: solid 3px @primary-color_4;
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<vxe-column field="source_ci_type_name" :title="$t('cmdb.ciType.sourceCIType')"></vxe-column>
|
||||
<vxe-column field="relation_type" :title="$t('cmdb.ciType.relationType')">
|
||||
<template #default="{row}">
|
||||
<span style="color:#2f54eb" v-if="row.isParent">{{ $t('cmdb.ciType.isParent') }}</span>
|
||||
<span class="primary-color" v-if="row.isParent">{{ $t('cmdb.ciType.isParent') }}</span>
|
||||
{{ row.relation_type }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
@ -700,7 +700,7 @@ export default {
|
|||
}
|
||||
|
||||
/deep/ .relation-table-parent {
|
||||
background-color: #f5f8ff !important;
|
||||
background-color: @primary-color_5 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -198,13 +198,11 @@
|
|||
:disable-branch-nodes="true"
|
||||
:class="{
|
||||
'custom-treeselect': true,
|
||||
'custom-treeselect-bgcAndBorder': true,
|
||||
'custom-treeselect-white': true,
|
||||
}"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '14px',
|
||||
}"
|
||||
v-model="selectedBot"
|
||||
|
@ -242,6 +240,18 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
|
||||
<a-row v-if="category === 2">
|
||||
<a-button
|
||||
@click="clickTestSend"
|
||||
:disabled="!dateForm.attr_id"
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost"
|
||||
>
|
||||
{{ $t('cmdb.ciType.testSend') }}
|
||||
</a-button>
|
||||
</a-row>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
<div class="auto-complete-wrapper" v-if="triggerAction === '3'">
|
||||
|
@ -273,7 +283,7 @@
|
|||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { addTrigger, updateTrigger, deleteTrigger } from '../../api/CIType'
|
||||
import { addTrigger, updateTrigger, deleteTrigger, testTrigger } from '../../api/CIType'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vue'
|
||||
import Webhook from '../../components/webhook'
|
||||
|
@ -569,10 +579,13 @@ export default {
|
|||
}
|
||||
if (this.triggerId) {
|
||||
await updateTrigger(this.CITypeId, this.triggerId, params)
|
||||
this.$message.success(this.$t('editSuccess'))
|
||||
} else {
|
||||
await addTrigger(this.CITypeId, params)
|
||||
const res = await addTrigger(this.CITypeId, params)
|
||||
this.triggerId = res.id
|
||||
this.$message.success(this.$t('createSuccess'))
|
||||
}
|
||||
this.handleCancel()
|
||||
|
||||
if (this.refresh) {
|
||||
this.refresh()
|
||||
}
|
||||
|
@ -614,6 +627,15 @@ export default {
|
|||
this.searchValue = item.label
|
||||
this.dag_id = item.id
|
||||
},
|
||||
async clickTestSend() {
|
||||
if (!this.triggerId) {
|
||||
this.$message.warning(this.$t('cmdb.ciType.testSendTip'))
|
||||
return
|
||||
}
|
||||
|
||||
await testTrigger(this.CITypeId, this.triggerId)
|
||||
this.$message.success(this.$t('cmdb.ciType.testSendSuccess'))
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<vxe-column field="attr_ids" :title="$t('cmdb.ciType.attributes')" :edit-render="{}">
|
||||
<template #default="{ row }">
|
||||
<template v-for="(attr, index) in row.attr_ids">
|
||||
<span :key="attr" :style="{ color: '#2f54eb' }">{{ getDisplayName(attr) }}</span>
|
||||
<span :key="attr" class="primary-color">{{ getDisplayName(attr) }}</span>
|
||||
<span :key="`_${attr}`" v-if="index !== row.attr_ids.length - 1"> + </span>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
<span class="primary-color" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
<span :style="{ ...options.fontConfig }">{{ toThousands(data) }}</span>
|
||||
</div>
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
<span class="primary-color" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</template>
|
||||
<span :style="{ color: '#000' }"> {{ form.name }}</span>
|
||||
</div>
|
||||
|
@ -195,12 +195,14 @@
|
|||
</template>
|
||||
</div>
|
||||
<a-form-model-item
|
||||
:label="$t('cmdb.custom_dashboard.showIcon')"
|
||||
prop="showIcon"
|
||||
:label-col="{ span: 5 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
:label-col="{ span: 0 }"
|
||||
:wrapper-col="{ span: 23 }"
|
||||
>
|
||||
<div class="chart-left-show-icon">
|
||||
<span class="chart-left-show-icon-label">{{ $t('cmdb.custom_dashboard.showIcon') }}:</span>
|
||||
<a-switch v-model="form.showIcon"></a-switch>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
|
@ -733,6 +735,9 @@ export default {
|
|||
width: 92%;
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
margin-top: 4px;
|
||||
display: inline-block;
|
||||
|
||||
.chart-left-preview-operation {
|
||||
color: #86909c;
|
||||
position: absolute;
|
||||
|
@ -753,12 +758,26 @@ export default {
|
|||
background-position-x: center;
|
||||
background-position-y: center;
|
||||
}
|
||||
|
||||
&-show-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-label {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-right {
|
||||
width: 50%;
|
||||
h4 {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
.chart-right-type {
|
||||
display: flex;
|
||||
|
@ -781,7 +800,7 @@ export default {
|
|||
}
|
||||
}
|
||||
.chart-right-type-box-selected {
|
||||
background-color: #e5f1ff;
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
.chart-width {
|
||||
|
@ -797,7 +816,7 @@ export default {
|
|||
<style lang="less">
|
||||
.chart-wrapper {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
:type="getCiType(item).icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ getCiType(item).name[0].toUpperCase() }}</span>
|
||||
<span class="primary-color" v-else>{{ getCiType(item).name[0].toUpperCase() }}</span>
|
||||
</template>
|
||||
<span :style="{ color: item.options.chartType === 'count' ? item.options.fontColor : '#000' }">{{
|
||||
item.options.name
|
||||
|
|
|
@ -268,18 +268,18 @@ export default {
|
|||
margin-right: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #3F75FF;
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
color: #3F75FF;
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0px 22px 33px 0px rgba(41, 65, 126, 0.25);
|
||||
box-shadow: ~'0px 22px 33px 0px @{primary-color}15';
|
||||
z-index: 2;
|
||||
|
||||
.rack-grid-item-name {
|
||||
|
|
|
@ -21,11 +21,24 @@
|
|||
<a>
|
||||
<a-icon
|
||||
type="plus-circle"
|
||||
class="dcim-tree-header-add-icon"
|
||||
class="dcim-tree-header-menu-icon"
|
||||
/>
|
||||
{{ $t(addActionTitle[type]) }}
|
||||
</a>
|
||||
</a-menu-item>
|
||||
|
||||
<a-menu-item
|
||||
class="dcim-tree-header-calc"
|
||||
@click="calcUnitFreeCount"
|
||||
>
|
||||
<a>
|
||||
<ops-icon
|
||||
type="veops-refresh"
|
||||
class="dcim-tree-header-menu-icon"
|
||||
/>
|
||||
{{ $t('cmdb.dcim.calcUnitFreeCount') }}
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
|
@ -45,15 +58,11 @@
|
|||
>
|
||||
<ops-icon
|
||||
:type="treeNodeData.icon"
|
||||
class="dcim-tree-node-icon"
|
||||
:style="{ color: treeNodeData.iconColor }"
|
||||
:class="['dcim-tree-node-icon', treeNodeData.dcimType === DCIM_TYPE.REGION ? 'primary-color' : '']"
|
||||
/>
|
||||
<a-tooltip :title="treeNodeData.title">
|
||||
<span
|
||||
class="dcim-tree-node-title"
|
||||
:style="{
|
||||
color: treeKey === treeNodeData.key ? '#2F54EB' : ''
|
||||
}"
|
||||
:class="['dcim-tree-node-title', treeKey === treeNodeData.key ? 'primary-color' : '']"
|
||||
>
|
||||
{{ treeNodeData.title }}
|
||||
</span>
|
||||
|
@ -121,7 +130,7 @@
|
|||
<script>
|
||||
import _ from 'lodash'
|
||||
import { DCIM_TYPE, DCIM_TYPE_NAME_MAP } from '../constants.js'
|
||||
import { deleteDCIM } from '@/modules/cmdb/api/dcim.js'
|
||||
import { deleteDCIM, calcUnitFreeCount } from '@/modules/cmdb/api/dcim.js'
|
||||
import CIDetailDrawer from '@/modules/cmdb/views/ci/modules/ciDetailDrawer.vue'
|
||||
|
||||
export default {
|
||||
|
@ -151,9 +160,12 @@ export default {
|
|||
DCIM_TYPE.REGION,
|
||||
DCIM_TYPE.IDC
|
||||
],
|
||||
DCIM_TYPE,
|
||||
|
||||
viewDetailCITypeId: 0,
|
||||
viewDetailAttrObj: {}
|
||||
viewDetailAttrObj: {},
|
||||
|
||||
calculatedFreeUnitCount: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -259,6 +271,23 @@ export default {
|
|||
this.$refs.CIdetailRef.create(node._id)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
calcUnitFreeCount() {
|
||||
if (this.calculatedFreeUnitCount) {
|
||||
this.$message.info(this.$t('cmdb.dcim.calcUnitFreeCountTip'))
|
||||
} else {
|
||||
this.$confirm({
|
||||
title: this.$t('tip'),
|
||||
content: this.$t('cmdb.dcim.calcUnitFreeCountTip2'),
|
||||
onOk: () => {
|
||||
calcUnitFreeCount().then(() => {
|
||||
this.calculatedFreeUnitCount = true
|
||||
this.$message.success(this.$t('cmdb.dcim.calcUnitFreeCountTip1'))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,7 +310,11 @@ export default {
|
|||
padding: 0px;
|
||||
}
|
||||
|
||||
&-add-icon {
|
||||
&-calc {
|
||||
border-top: dashed 1px #e8e8e8;
|
||||
}
|
||||
|
||||
&-menu-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
@ -329,6 +362,7 @@ export default {
|
|||
&-icon {
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
color: #A5A9BC;
|
||||
}
|
||||
|
||||
&-title {
|
||||
|
|
|
@ -117,8 +117,8 @@
|
|||
}"
|
||||
@click="addDevice(index)"
|
||||
>
|
||||
<ops-icon
|
||||
type="monitor-add"
|
||||
<a-icon
|
||||
type="plus-circle"
|
||||
class="rack-container-main-list-gap-icon"
|
||||
/>
|
||||
<span
|
||||
|
@ -492,12 +492,13 @@ export default {
|
|||
&-icon {
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 87, 255, 0.80);
|
||||
color: @primary-color;
|
||||
margin-left: 6px;
|
||||
display: none;
|
||||
}
|
||||
|
@ -508,7 +509,7 @@ export default {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #D5DDEE;
|
||||
background-color: @primary-color_4;
|
||||
|
||||
.rack-container-main-list-gap-icon {
|
||||
display: inline-block;
|
||||
|
|
|
@ -165,11 +165,12 @@ export default {
|
|||
display: inline-block;
|
||||
width: 180px;
|
||||
height: 105px;
|
||||
box-shadow: 0px 2px 8px rgba(122, 140, 204, 0.25);
|
||||
box-shadow: 0px 2px 8px @primary-color_3;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
margin-bottom: 40px;
|
||||
margin-right: 40px;
|
||||
cursor: pointer;
|
||||
|
||||
&-inner {
|
||||
position: absolute;
|
||||
|
@ -294,6 +295,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&, &.discovery-card-small {
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 20px 0px @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
&-http {
|
||||
width: 263px;
|
||||
height: 142px;
|
||||
|
@ -305,6 +312,10 @@ export default {
|
|||
max-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 28px 0px @primary-color_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
.discovery-card-small {
|
||||
|
@ -312,7 +323,7 @@ export default {
|
|||
height: 80px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.discovery-card-small:hover,
|
||||
|
||||
.discovery-card-small-selected {
|
||||
.discovery-top {
|
||||
background-color: #f0f1f5;
|
||||
|
|
|
@ -25,18 +25,24 @@
|
|||
:fileList="[]"
|
||||
:beforeUpload="beforeUpload"
|
||||
>
|
||||
<a class="setting-discovery-header-action-btn">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
>
|
||||
<a-icon type="upload" />
|
||||
{{ $t('cmdb.ad.upload') }}
|
||||
</a>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ops-button-ghost"
|
||||
ghost
|
||||
@click="download"
|
||||
class="setting-discovery-header-action-btn"
|
||||
>
|
||||
<a-icon type="download" />
|
||||
{{ $t('cmdb.ad.download') }}
|
||||
</a>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -64,7 +70,7 @@
|
|||
class="setting-discovery-add"
|
||||
@click="handleOpenEditDrawer(null, 'add', DISCOVERY_CATEGORY_TYPE.PLUGIN)"
|
||||
>
|
||||
<a-icon type="plus-circle" theme="twoTone" />
|
||||
<a-icon class="setting-discovery-add-icon" type="plus-circle" />
|
||||
<span class="setting-discovery-add-text">
|
||||
{{ $t('cmdb.ad.addPlugin') }}
|
||||
</span>
|
||||
|
@ -374,6 +380,10 @@ export default {
|
|||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&-icon {
|
||||
color: @primary-color_9;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: @text-color_3;
|
||||
font-size: 12px;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
:type="ciType.icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
<span class="primary-color" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||||
</span>
|
||||
<span :title="ciType.alias || ciType.name" class="cmdb-adc-side-name">{{ ciType.alias || ciType.name }}</span>
|
||||
</div>
|
||||
|
@ -46,10 +46,15 @@
|
|||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedCount }) }}</span>
|
||||
</span>
|
||||
<div @click="clickLog" class="discovery-ci-log">
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
class="ops-button-ghost discovery-ci-log"
|
||||
@click="clickLog"
|
||||
>
|
||||
<ops-icon type="a-cmdb-log1" />
|
||||
<span>{{ $t('cmdb.ad.log') }}</span>
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
<ops-table
|
||||
show-overflow
|
||||
|
@ -458,16 +463,7 @@ export default {
|
|||
}
|
||||
|
||||
.discovery-ci-log {
|
||||
cursor: pointer;
|
||||
background-color: #F4F9FF;
|
||||
border: solid 1px @primary-color_8;
|
||||
color: @primary-color;
|
||||
font-size: 12px;
|
||||
padding: 5px 12px;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.checkbox-hover-table {
|
||||
|
|
|
@ -26,10 +26,7 @@
|
|||
/>
|
||||
<a-tooltip :title="treeNodeData.title">
|
||||
<span
|
||||
class="ipam-tree-node-title"
|
||||
:style="{
|
||||
color: treeKey === treeNodeData.key ? '#2F54EB' : ''
|
||||
}"
|
||||
:class="['ipam-tree-node-title', treeKey === treeNodeData.key ? 'primary-color' : '']"
|
||||
>
|
||||
{{ treeNodeData.title }}
|
||||
</span>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue