mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 16:42:34 +00:00
Compare commits
610 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f61b36bff | ||
|
|
55ff82545a | ||
|
|
a99d5609fa | ||
|
|
dbc2779b34 | ||
|
|
33b1de0d85 | ||
|
|
bcfe82f162 | ||
|
|
82af5f8f16 | ||
|
|
84f52eb337 | ||
|
|
f00a650366 | ||
|
|
2fedeb9834 | ||
|
|
daadcedc21 | ||
|
|
951e8261ad | ||
|
|
9c0f00f625 | ||
|
|
6a23983331 | ||
|
|
18e590effd | ||
|
|
9d1f5d3184 | ||
|
|
b54d389c7c | ||
|
|
31356e825f | ||
|
|
76aadad6fe | ||
|
|
c7510bcf19 | ||
|
|
52e5487e7d | ||
|
|
4a0d6842de | ||
|
|
d6b22e9ff8 | ||
|
|
2833f343b2 | ||
|
|
3d13f3a17d | ||
|
|
d91599ffab | ||
|
|
e22e832d49 | ||
|
|
8f479e364b | ||
|
|
0b0fdbfc82 | ||
|
|
24fe3ade9c | ||
|
|
9499a16a8b | ||
|
|
f380d82b55 | ||
|
|
060248d1ca | ||
|
|
da8fec77bb | ||
|
|
67f52888f6 | ||
|
|
c4d6f32528 | ||
|
|
2661bbb70a | ||
|
|
3e3ab556d3 | ||
|
|
646a29108c | ||
|
|
c7f86cdde9 | ||
|
|
79208a95c1 | ||
|
|
091cf39e4e | ||
|
|
0df7c6909e | ||
|
|
f6def0b43f | ||
|
|
9cfcadc2f6 | ||
|
|
363985ee7a | ||
|
|
16cc4a0f4e | ||
|
|
5931c5a032 | ||
|
|
742200e462 | ||
|
|
9d7b82085e | ||
|
|
dda367a956 | ||
|
|
c0d51e22d7 | ||
|
|
2348c8c335 | ||
|
|
87abe63a20 | ||
|
|
4d26fd8b56 | ||
|
|
c9aab608a9 | ||
|
|
641567be10 | ||
|
|
5f68f6cb69 | ||
|
|
d50ad66b78 | ||
|
|
69ab3e3542 | ||
|
|
ff1b902b2e | ||
|
|
52647da79b | ||
|
|
28b6144189 | ||
|
|
f7daf26a03 | ||
|
|
e1673334af | ||
|
|
fd6e561d4b | ||
|
|
4cdddaa493 | ||
|
|
9491827e01 | ||
|
|
5459d1114f | ||
|
|
6acda27d67 | ||
|
|
0f9326bd8f | ||
|
|
e09f3ca4fd | ||
|
|
1fcb272ddc | ||
|
|
b577c626f7 | ||
|
|
2e4e5503cc | ||
|
|
4212cb3600 | ||
|
|
b8874e1855 | ||
|
|
9bb498f7b3 | ||
|
|
e38d089056 | ||
|
|
f9e9bf0b2d | ||
|
|
1e5387ef47 | ||
|
|
f87e08efff | ||
|
|
82d866db7d | ||
|
|
ba0d822734 | ||
|
|
c8568eb244 | ||
|
|
6e19b9d5bc | ||
|
|
354b728f75 | ||
|
|
ce553710ba | ||
|
|
4f806f11f2 | ||
|
|
0a94a346a0 | ||
|
|
d8afe72d4c | ||
|
|
e2072a1e02 | ||
|
|
cc387bf511 | ||
|
|
5c002e91ee | ||
|
|
41a8831034 | ||
|
|
ebd92c79c7 | ||
|
|
6278900201 | ||
|
|
4f580e0df8 | ||
|
|
1f502e02c7 | ||
|
|
cdf8398169 | ||
|
|
1bfef829f3 | ||
|
|
cc0cf8ed1c | ||
|
|
2791213844 | ||
|
|
284e8be45c | ||
|
|
76109f1808 | ||
|
|
54b6e06d1f | ||
|
|
5c30c76ea3 | ||
|
|
94b5eb8685 | ||
|
|
c9f4b104c7 | ||
|
|
3bf1c036c5 | ||
|
|
09fbd3a5ab | ||
|
|
ebecd00581 | ||
|
|
143fa060d1 | ||
|
|
2c18a27e3a | ||
|
|
910dd4e593 | ||
|
|
11aefa479b | ||
|
|
abc56016f2 | ||
|
|
f44db2a25b | ||
|
|
3fa6807837 | ||
|
|
2c4195d619 | ||
|
|
265ef0c8ac | ||
|
|
0b0b06a5c2 | ||
|
|
d77ba1d5ea | ||
|
|
a3bd7cee80 | ||
|
|
8d73cd43e1 | ||
|
|
7e3fd73ae5 | ||
|
|
70960d2ae4 | ||
|
|
d2c574fe9d | ||
|
|
a70fcf057b | ||
|
|
f37582ec53 | ||
|
|
29b87c40fe | ||
|
|
1ec77c5bb9 | ||
|
|
21c71aba93 | ||
|
|
f8db9f480e | ||
|
|
0665644fd0 | ||
|
|
7bafa546b5 | ||
|
|
666815b324 | ||
|
|
532abb86b5 | ||
|
|
76d4e4ad55 | ||
|
|
70fa43adaa | ||
|
|
44bf01d4ed | ||
|
|
1341983fd3 | ||
|
|
78936bf9f2 | ||
|
|
9a5d3cb475 | ||
|
|
9bddc29da4 | ||
|
|
d68a4d9cae | ||
|
|
5457118fb6 | ||
|
|
7ee68f7eeb | ||
|
|
2063f2f257 | ||
|
|
2637c608a6 | ||
|
|
32519ea326 | ||
|
|
3ce9d01b6d | ||
|
|
310bc6ad0b | ||
|
|
b54afbe7bb | ||
|
|
ab848afdb9 | ||
|
|
5bb867d10d | ||
|
|
0eda8865e6 | ||
|
|
2a37107abc | ||
|
|
c78107f62f | ||
|
|
b022bf36ba | ||
|
|
6dc2272a26 | ||
|
|
1d462aea1b | ||
|
|
88a29c0a93 | ||
|
|
9ffae722f3 | ||
|
|
9ab2f4bc56 | ||
|
|
41e7f45c20 | ||
|
|
9945ac172b | ||
|
|
67ddd42b3d | ||
|
|
03adddefa3 | ||
|
|
60b7ccddc0 | ||
|
|
1194932bc0 | ||
|
|
5c8fd91cf9 | ||
|
|
bb13003a10 | ||
|
|
9a18817dbb | ||
|
|
2c4966c678 | ||
|
|
d1390a1cd7 | ||
|
|
fe45d839fb | ||
|
|
9f96f1c537 | ||
|
|
dc918c031c | ||
|
|
6b047ca702 | ||
|
|
47d31005b5 | ||
|
|
57e1ca93f0 | ||
|
|
483a7617ce | ||
|
|
5470ab752e | ||
|
|
2dbd6b6f6e | ||
|
|
504d9242c6 | ||
|
|
14b1e3fa13 | ||
|
|
7eeca511f1 | ||
|
|
670c8a6d0b | ||
|
|
a2aa923abe | ||
|
|
2ac5786ba1 | ||
|
|
5b93a1a0a5 | ||
|
|
00928dd46d | ||
|
|
7ddf7f2a79 | ||
|
|
3533bf588b | ||
|
|
dea007f27b | ||
|
|
cd2b88caee | ||
|
|
1877511acf | ||
|
|
1c5ce61ed0 | ||
|
|
b1132bfc37 | ||
|
|
75e67410cf | ||
|
|
c9d137bc20 | ||
|
|
d97e606503 | ||
|
|
e59b95e97a | ||
|
|
bb6394150d | ||
|
|
2354f0c970 | ||
|
|
ae564ed0d4 | ||
|
|
05ecd7497a | ||
|
|
6b86b8b485 | ||
|
|
fa0bd85fd4 | ||
|
|
7da46354ca | ||
|
|
e41aad1576 | ||
|
|
3f049440b7 | ||
|
|
4f532f588b | ||
|
|
a792781b98 | ||
|
|
3a4c7846bf | ||
|
|
ccc292d9a9 | ||
|
|
337338ebf3 | ||
|
|
aa3bc7b53a | ||
|
|
d5451a482a | ||
|
|
534734881c | ||
|
|
8236c7baa0 | ||
|
|
96ec5fac99 | ||
|
|
b7fcf80fc5 | ||
|
|
de3695bf97 | ||
|
|
7c814080b2 | ||
|
|
f5531b6065 | ||
|
|
b0aa9f197a | ||
|
|
fc156e23f3 | ||
|
|
1a05dab572 | ||
|
|
b8ecb6f81d | ||
|
|
c01936facc | ||
|
|
90c629c837 | ||
|
|
c9d192eefc | ||
|
|
9c4ebf9c75 | ||
|
|
37d89b4ea2 | ||
|
|
87e0e1f2c4 | ||
|
|
183ff09530 | ||
|
|
485a178c0a | ||
|
|
227cc4e965 | ||
|
|
01bef95e6e | ||
|
|
672dd66023 | ||
|
|
c032294b14 | ||
|
|
6ce813faf8 | ||
|
|
aefc18d73b | ||
|
|
23815f87c5 | ||
|
|
206e037cf2 | ||
|
|
492fd98882 | ||
|
|
d92d462dab | ||
|
|
8afd5ef90a | ||
|
|
d3dca5d077 | ||
|
|
9166a26f80 | ||
|
|
3039284666 | ||
|
|
2f395794ef | ||
|
|
c6d50802db | ||
|
|
a10e47f72c | ||
|
|
3dc214d1fa | ||
|
|
f7fb36a176 | ||
|
|
0d7295b60e | ||
|
|
8f654c37a9 | ||
|
|
b29a541aa6 | ||
|
|
9fd52f6665 | ||
|
|
f4c86718dc | ||
|
|
4ff7a1f066 | ||
|
|
eca245fdd5 | ||
|
|
7e3cf908a1 | ||
|
|
dded4e10fb | ||
|
|
45a354f848 | ||
|
|
8386f107c6 | ||
|
|
5ce3dd4079 | ||
|
|
a48fb9de8d | ||
|
|
04e7f54c69 | ||
|
|
d649aacfd6 | ||
|
|
7e65e44a3c | ||
|
|
74c3f12275 | ||
|
|
8c12c382a5 | ||
|
|
2ecfecb06f | ||
|
|
ac238aa36e | ||
|
|
2abb9efe96 | ||
|
|
f17727deb9 | ||
|
|
36f1165d1b | ||
|
|
e7c530d8e6 | ||
|
|
b156f4ad16 | ||
|
|
16b23a37fe | ||
|
|
e41add6126 | ||
|
|
f4c31d8e86 | ||
|
|
28e8f204ec | ||
|
|
80f147cf13 | ||
|
|
c816875f28 | ||
|
|
1c56ba5a11 | ||
|
|
2208d6d51e | ||
|
|
e3aa18ff2d | ||
|
|
b5f6f80ae6 | ||
|
|
bbe4080008 | ||
|
|
cd797b18fb | ||
|
|
b6523da603 | ||
|
|
c24f1a0517 | ||
|
|
83f220d7de | ||
|
|
8e42a65736 | ||
|
|
e1fff18ce3 | ||
|
|
3052744203 | ||
|
|
7924b094f8 | ||
|
|
3d34b06203 | ||
|
|
c94d018d7e | ||
|
|
53086a8977 | ||
|
|
ce1fc0f3e2 | ||
|
|
c215278978 | ||
|
|
061963a316 | ||
|
|
97b240cfdd | ||
|
|
fd5f562cbf | ||
|
|
722bf786f1 | ||
|
|
2cb5876d1a | ||
|
|
8883e0090f | ||
|
|
790652ff4d | ||
|
|
ff3f74abe6 | ||
|
|
1a49cf4d9c | ||
|
|
4d1da56872 | ||
|
|
ff9e109a2c | ||
|
|
09e636495e | ||
|
|
14076d8fe1 | ||
|
|
812078331e | ||
|
|
696589a3cf | ||
|
|
7eba46b303 | ||
|
|
5d63d2369f | ||
|
|
3e17e94245 | ||
|
|
5648dcd7e7 | ||
|
|
28e47f33c1 | ||
|
|
91b3b7ce69 | ||
|
|
8c587a1376 | ||
|
|
1182313c1a | ||
|
|
7412bdcba7 | ||
|
|
d6ec92d82d | ||
|
|
ad3214641d | ||
|
|
9004351ad1 | ||
|
|
d3e22a2a90 | ||
|
|
fd3df81a64 | ||
|
|
72517a2c72 | ||
|
|
01185a2d07 | ||
|
|
8bfd2be21f | ||
|
|
b03ac46df9 | ||
|
|
76be054fcb | ||
|
|
df95c93bb1 | ||
|
|
eaefb5c669 | ||
|
|
0ddb9476ba | ||
|
|
75a9deebdd | ||
|
|
b6cd4a20c5 | ||
|
|
435acafccd | ||
|
|
86e4bc5e9a | ||
|
|
315609bc45 | ||
|
|
814e6a7df8 | ||
|
|
7a7c6d40df | ||
|
|
5d800fa629 | ||
|
|
bd14266abd | ||
|
|
88f36c6f02 | ||
|
|
512fc8f8f0 | ||
|
|
37bb344166 | ||
|
|
f8c2a445f7 | ||
|
|
ff9b1a887f | ||
|
|
43370c547a | ||
|
|
2eda58eadd | ||
|
|
e1be867913 | ||
|
|
01afcf701c | ||
|
|
4002289974 | ||
|
|
ef9e03c7ed | ||
|
|
442d4e727a | ||
|
|
3993797527 | ||
|
|
f1f06491d6 | ||
|
|
eb95a0a912 | ||
|
|
401a7f88a8 | ||
|
|
0a2ff83ca1 | ||
|
|
7276bd0b2a | ||
|
|
2950613b69 | ||
|
|
6fa0562d7a | ||
|
|
ef22d33afa | ||
|
|
86404db6c7 | ||
|
|
fdf2807d9b | ||
|
|
2e6d238c76 | ||
|
|
f5a4370b80 | ||
|
|
db2273ef27 | ||
|
|
dd07fa678f | ||
|
|
e7a731fae9 | ||
|
|
73f9f54620 | ||
|
|
a2f23e9681 | ||
|
|
dbc471c195 | ||
|
|
4c8eb4a94b | ||
|
|
9946c4612f | ||
|
|
8a5e1b8223 | ||
|
|
06ce098e00 | ||
|
|
5579d3f0de | ||
|
|
1ef582e9ac | ||
|
|
221fae5875 | ||
|
|
f4084c800a | ||
|
|
5464ac8167 | ||
|
|
c6a8967376 | ||
|
|
5f2c31e42c | ||
|
|
64db02c3f8 | ||
|
|
842841128f | ||
|
|
b026e86741 | ||
|
|
283b1c1d64 | ||
|
|
c8c0479ce5 | ||
|
|
2a30204c4e | ||
|
|
30afcecf59 | ||
|
|
80d11bbaab | ||
|
|
58e36b5f63 | ||
|
|
2c413e8d51 | ||
|
|
17de014ee9 | ||
|
|
6b2a38c78d | ||
|
|
fcd17460d7 | ||
|
|
4c4430661b | ||
|
|
ee35ca3643 | ||
|
|
99c4875dd7 | ||
|
|
482d1bb27f | ||
|
|
e7c7c3a7a8 | ||
|
|
54efc88799 | ||
|
|
a9d1538135 | ||
|
|
f8ff223f90 | ||
|
|
a3f1622a50 | ||
|
|
0021f2e5e1 | ||
|
|
dbcad47214 | ||
|
|
6c384b49fe | ||
|
|
526943a041 | ||
|
|
e9d0104a69 | ||
|
|
7c694c6885 | ||
|
|
69e5ab438a | ||
|
|
757a31a52f | ||
|
|
5b53cfb4dd | ||
|
|
b0710c42b0 | ||
|
|
d9d82cea5e | ||
|
|
4f521e5a94 | ||
|
|
3a2973023c | ||
|
|
4f28f85410 | ||
|
|
a1905ecfdb | ||
|
|
47397d2308 | ||
|
|
7b57d24dc9 | ||
|
|
f2216274c5 | ||
|
|
ffabef0040 | ||
|
|
0b4df78393 | ||
|
|
2fab69ca61 | ||
|
|
7987056b12 | ||
|
|
f9ab0abc37 | ||
|
|
0bc86543b5 | ||
|
|
fc2a44621b | ||
|
|
9b4b9e6900 | ||
|
|
62c114d9c4 | ||
|
|
22a84d57ca | ||
|
|
3f4b5ad465 | ||
|
|
a96bda8ca9 | ||
|
|
bff3868b8f | ||
|
|
8470dce805 | ||
|
|
b9d0d89f66 | ||
|
|
5a7192e035 | ||
|
|
de2416b173 | ||
|
|
486793ddcd | ||
|
|
c40c5ac543 | ||
|
|
c529061ee0 | ||
|
|
fe52c57a11 | ||
|
|
f8384973a1 | ||
|
|
dc1d228e07 | ||
|
|
092b33d4d1 | ||
|
|
d615eb80b5 | ||
|
|
46520287d9 | ||
|
|
4b7af1457d | ||
|
|
c1db33713f | ||
|
|
c3101dba29 | ||
|
|
e66cfc2e13 | ||
|
|
ac67c231fc | ||
|
|
599431f402 | ||
|
|
ed18cb317f | ||
|
|
cb4afabc91 | ||
|
|
718715cc6d | ||
|
|
38f8c5bb72 | ||
|
|
ebc1b4975a | ||
|
|
2da87151ed | ||
|
|
ab9d457ce0 | ||
|
|
cbc4a0a97b | ||
|
|
0031d025aa | ||
|
|
4c53eebdbe | ||
|
|
2583c0b26c | ||
|
|
ebef4f254a | ||
|
|
84d3fa6db0 | ||
|
|
e2fa492987 | ||
|
|
116a04da68 | ||
|
|
7de6af89ad | ||
|
|
8e74a04282 | ||
|
|
3af01d6a31 | ||
|
|
5bfc34a9ea | ||
|
|
869a84964d | ||
|
|
2a8358b1aa | ||
|
|
0a9af98729 | ||
|
|
dfd98f8aea | ||
|
|
e9b86ca668 | ||
|
|
d2b0aba620 | ||
|
|
7f670ab709 | ||
|
|
941e55bdec | ||
|
|
e630321e55 | ||
|
|
01d136cf1e | ||
|
|
4ed9e11090 | ||
|
|
cde2e7adb0 | ||
|
|
7a27021d3d | ||
|
|
966123e4c6 | ||
|
|
e291ca9057 | ||
|
|
84003b777c | ||
|
|
1edfb1cec4 | ||
|
|
0b89ff17fd | ||
|
|
9fa6b3e387 | ||
|
|
c0bbda9769 | ||
|
|
0d8a600277 | ||
|
|
d7a32120ba | ||
|
|
55096f9ad5 | ||
|
|
494cd760d7 | ||
|
|
6f494ef09c | ||
|
|
e476cab2a1 | ||
|
|
cc67fcb53b | ||
|
|
b074bd8fbd | ||
|
|
627582233b | ||
|
|
5103dab72e | ||
|
|
43c13355f2 | ||
|
|
59eb1f8e3e | ||
|
|
16aa42a861 | ||
|
|
0962a16b22 | ||
|
|
7c35e75586 | ||
|
|
787be3ff7a | ||
|
|
d5debc375e | ||
|
|
40a0c4597b | ||
|
|
5c17b1a7f7 | ||
|
|
ea2863a51b | ||
|
|
102e1ca97c | ||
|
|
2823d02763 | ||
|
|
c37414045b | ||
|
|
784bec42ff | ||
|
|
9a3d0732bc | ||
|
|
20a7247b16 | ||
|
|
df60981eb4 | ||
|
|
7aa2bb06e8 | ||
|
|
536be1175a | ||
|
|
b5fc76d6a5 | ||
|
|
941dd627e3 | ||
|
|
8389c85054 | ||
|
|
02ca8c3139 | ||
|
|
abd20f31b8 | ||
|
|
5c7acae018 | ||
|
|
1c623f71e0 | ||
|
|
5b52b907c0 | ||
|
|
8447c6f487 | ||
|
|
e865484a56 | ||
|
|
ad6e22cd42 | ||
|
|
7a219e1710 | ||
|
|
a0a8419c5e | ||
|
|
0c24310510 | ||
|
|
967491fba5 | ||
|
|
9ac7f26c74 | ||
|
|
910f3cdddc | ||
|
|
f73fe1f315 | ||
|
|
28acc6cc63 | ||
|
|
763cf0d981 | ||
|
|
611289a5ec | ||
|
|
95a8bf0988 | ||
|
|
947f7d206a | ||
|
|
12c8cf6b76 | ||
|
|
33bc73aba7 | ||
|
|
53c532a6ad | ||
|
|
035dd16b36 | ||
|
|
f450accbf8 | ||
|
|
0bbfc7433d | ||
|
|
48e8785725 | ||
|
|
b90d3306c5 | ||
|
|
7f7d634c38 | ||
|
|
45b13abed3 | ||
|
|
72cd7a3be2 | ||
|
|
3ccd54680e | ||
|
|
071d14c639 | ||
|
|
823e879432 | ||
|
|
739932b005 | ||
|
|
24f144fdc3 | ||
|
|
967800391e | ||
|
|
3ccb6637d7 | ||
|
|
8dfdefd428 | ||
|
|
ab2c58b626 | ||
|
|
ee4f5a8194 | ||
|
|
084a76b215 | ||
|
|
2398e9acbd | ||
|
|
5ad8b3cc70 | ||
|
|
7d14e1f248 | ||
|
|
819f8f469d | ||
|
|
a31b7a8800 | ||
|
|
24bdaecab4 | ||
|
|
8b3b517bab | ||
|
|
7fc2ef00ee | ||
|
|
cbd6c3ee69 | ||
|
|
3835adafb8 | ||
|
|
bbaa35c773 | ||
|
|
0fa8287811 | ||
|
|
78f4e5a89a | ||
|
|
3193c5549d | ||
|
|
ed71e7d2d9 | ||
|
|
33c299566a | ||
|
|
84634eb8c0 | ||
|
|
a4ff2181c5 | ||
|
|
fffa0def9e | ||
|
|
d0ede246e7 | ||
|
|
b8b78ffeb2 | ||
|
|
d2d10b59ac | ||
|
|
cb8e59edf2 | ||
|
|
1c0d783eec | ||
|
|
4fa72400be | ||
|
|
37c0062fae | ||
|
|
8504c3d2fd | ||
|
|
b63999f385 | ||
|
|
4fd83bd5be | ||
|
|
6035d1f130 |
3
.github/ISSUE_TEMPLATE.md
vendored
3
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,8 @@
|
||||
[简述你的问题]
|
||||
|
||||
|
||||
##### 使用版本
|
||||
[请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
|
||||
[请提供你使用的Jumpserver版本 1.x.x 注: 0.3.x不再提供支持]
|
||||
|
||||
##### 问题复现步骤
|
||||
1. [步骤1]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,7 +17,6 @@ dump.rdb
|
||||
.idea/
|
||||
db.sqlite3
|
||||
config.py
|
||||
migrations/
|
||||
*.log
|
||||
host_rsa_key
|
||||
*.bat
|
||||
@@ -32,3 +31,5 @@ django.db
|
||||
celerybeat-schedule.db
|
||||
data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
logs/*
|
||||
|
||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM registry.fit2cloud.com/public/python:v3
|
||||
MAINTAINER Jumpserver Team <ibuler@qq.com>
|
||||
|
||||
WORKDIR /opt/jumpserver
|
||||
RUN useradd jumpserver
|
||||
|
||||
COPY ./requirements /tmp/requirements
|
||||
|
||||
RUN yum -y install epel-release && cd /tmp/requirements && \
|
||||
yum -y install $(cat rpm_requirements.txt)
|
||||
|
||||
RUN cd /tmp/requirements && pip install -r requirements.txt
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
COPY config_docker.py /opt/jumpserver/config.py
|
||||
VOLUME /opt/jumpserver/data
|
||||
VOLUME /opt/jumpserver/logs
|
||||
|
||||
ENV LANG=zh_CN.UTF-8
|
||||
ENV LC_ALL=zh_CN.UTF-8
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
24
README.md
24
README.md
@@ -1,9 +1,9 @@
|
||||
## Jumpserver
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
|
||||
----
|
||||
@@ -19,31 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
|
||||
----
|
||||
|
||||
### 功能
|
||||
- 统一认证
|
||||
- 资产管理
|
||||
- 统一授权
|
||||
- 审计
|
||||
- 支持LDAP认证
|
||||
- Web terminal
|
||||
- SSH Server
|
||||
- 支持Windows RDP
|
||||
|
||||

|
||||
|
||||
### 开始使用
|
||||
|
||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
|
||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
|
||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
|
||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
||||
|
||||
### Demo 和 截图
|
||||
### Demo 和 截图
|
||||
|
||||
我们提供了DEMO和截图可以让你快速了解Jumpserver
|
||||
|
||||
[DEMO](http://demo.jumpserver.org)
|
||||
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
### SDK
|
||||
|
||||
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.4.5"
|
||||
|
||||
@@ -4,3 +4,4 @@ from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -17,10 +17,11 @@ from django.db import transaction
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectability_manual
|
||||
@@ -37,21 +38,29 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
||||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
|
||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserAuthSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
@@ -75,7 +84,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import random
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
@@ -11,9 +13,8 @@ from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
||||
NodePermissionUtil
|
||||
from ..models import Asset, SystemUser, AdminUser, Node
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectability_manual
|
||||
@@ -22,8 +23,9 @@ from ..utils import LabelFilter
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
|
||||
'AssetViewSet', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi'
|
||||
]
|
||||
|
||||
|
||||
@@ -37,34 +39,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
def filter_node(self):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
if not node_id:
|
||||
return
|
||||
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
|
||||
|
||||
if node.is_root():
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
)
|
||||
return
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(nodes=node)
|
||||
else:
|
||||
self.queryset = self.queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
)
|
||||
|
||||
def filter_admin_user_id(self):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
if node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if not node.is_root():
|
||||
queryset = queryset.filter(nodes__key__startswith=node.key).distinct()
|
||||
return queryset
|
||||
|
||||
|
||||
class UserAssetListView(generics.ListAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
self.queryset = self.queryset.filter(admin_user=admin_user)
|
||||
|
||||
def get_queryset(self):
|
||||
assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
|
||||
queryset = self.queryset.filter(
|
||||
id__in=[asset.id for asset in assets_granted]
|
||||
)
|
||||
return queryset
|
||||
self.queryset = super().get_queryset()\
|
||||
.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
self.filter_admin_user_id()
|
||||
self.filter_node()
|
||||
return self.queryset.distinct()
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
@@ -73,7 +83,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
@@ -82,7 +92,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
@@ -96,10 +106,27 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
task = test_asset_connectability_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
if asset.domain and \
|
||||
asset.domain.gateways.filter(protocol=asset.protocol).exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
|
||||
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
return Response({"msg": "Not have gateway"}, status=404)
|
||||
39
apps/assets/api/cmd_filter.py
Normal file
39
apps/assets/api/cmd_filter.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||
|
||||
|
||||
class CommandFilterViewSet(BulkModelViewSet):
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
queryset = CommandFilter.objects.all()
|
||||
serializer_class = serializers.CommandFilterSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class CommandFilterRuleViewSet(BulkModelViewSet):
|
||||
filter_fields = ("content",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.CommandFilterRuleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
fpk = self.kwargs.get('filter_pk')
|
||||
if not fpk:
|
||||
return CommandFilterRule.objects.none()
|
||||
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
|
||||
return cmd_filter.rules.all()
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||
from ..models import Domain, Gateway
|
||||
from ..utils import test_gateway_connectability
|
||||
from .. import serializers
|
||||
|
||||
|
||||
@@ -19,8 +18,13 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
|
||||
class DomainViewSet(BulkModelViewSet):
|
||||
queryset = Domain.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.DomainSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
@@ -29,26 +33,27 @@ class DomainViewSet(BulkModelViewSet):
|
||||
|
||||
def get_permissions(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
self.permission_classes = (IsSuperUserOrAppUser,)
|
||||
self.permission_classes = (IsOrgAdminOrAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class GatewayViewSet(BulkModelViewSet):
|
||||
filter_fields = ("domain",)
|
||||
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||
search_fields = filter_fields
|
||||
queryset = Gateway.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.GatewaySerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Gateway
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(Gateway.objects.all())
|
||||
ok, e = test_gateway_connectability(self.object)
|
||||
ok, e = self.object.test_connective()
|
||||
if ok:
|
||||
return Response("ok")
|
||||
else:
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
@@ -27,12 +28,18 @@ __all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(BulkModelViewSet):
|
||||
queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
permission_classes = (IsSuperUser,)
|
||||
filter_fields = ("name", "value")
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.LabelSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if request.query_params.get("distinct"):
|
||||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
return self.queryset
|
||||
|
||||
@@ -13,15 +13,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics, mixins
|
||||
from rest_framework import generics, mixins, viewsets
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||
from .. import serializers
|
||||
@@ -29,16 +31,16 @@ from .. import serializers
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi'
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(BulkModelViewSet):
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
@@ -46,42 +48,113 @@ class NodeViewSet(BulkModelViewSet):
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.is_root():
|
||||
node_value = node.value
|
||||
post_value = request.data.get('value')
|
||||
if node_value != post_value:
|
||||
return Response(
|
||||
{"msg": _("You can't update the root node name")},
|
||||
status=400
|
||||
)
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
def counter(self):
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_object().get_children()
|
||||
if child.value.startswith("新节点 ")
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values)+1 if values else 1
|
||||
return count
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not request.data.get("value"):
|
||||
request.data["value"] = _("New node {}").format(
|
||||
Node.root().get_next_child_key().split(":")[-1]
|
||||
)
|
||||
request.data["value"] = _("New node {}").format(self.counter())
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
value = request.data.get("value")
|
||||
values = [child.value for child in instance.get_children()]
|
||||
if value in values:
|
||||
raise ValidationError(
|
||||
'The same level node name cannot be the same'
|
||||
)
|
||||
node = instance.create_child(value=value)
|
||||
return Response(
|
||||
{"id": node.id, "key": node.key, "value": node.value},
|
||||
status=201,
|
||||
)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if self.request.query_params.get("all"):
|
||||
children = instance.get_all_children()
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
||||
if not pk:
|
||||
node = None
|
||||
else:
|
||||
children = instance.get_children()
|
||||
response = [{"id": node.id, "key": node.key, "value": node.value} for node in children]
|
||||
return Response(response, status=200)
|
||||
node = get_object_or_404(Node, pk=pk)
|
||||
return node
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = []
|
||||
query_all = self.request.query_params.get("all")
|
||||
query_assets = self.request.query_params.get('assets')
|
||||
node = self.get_object()
|
||||
|
||||
if node is None:
|
||||
node = Node.root()
|
||||
node.assets__count = node.get_all_assets().count()
|
||||
queryset.append(node)
|
||||
|
||||
if query_all:
|
||||
children = node.get_all_children()
|
||||
else:
|
||||
children = node.get_children()
|
||||
queryset.extend(list(children))
|
||||
|
||||
if query_assets:
|
||||
assets = node.get_assets()
|
||||
for asset in assets:
|
||||
node_fake = Node()
|
||||
node_fake.assets__count = 0
|
||||
node_fake.id = asset.id
|
||||
node_fake.is_node = False
|
||||
node_fake.key = node.key + ':0'
|
||||
node_fake.value = asset.hostname
|
||||
queryset.append(node_fake)
|
||||
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||
return queryset
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeAssetsApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
node_id = self.kwargs.get('pk')
|
||||
query_all = self.request.query_params.get('all')
|
||||
instance = get_object_or_404(Node, pk=node_id)
|
||||
if query_all:
|
||||
return instance.get_all_assets()
|
||||
else:
|
||||
return instance.get_assets()
|
||||
|
||||
|
||||
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeAddChildrenSerializer
|
||||
instance = None
|
||||
|
||||
@@ -93,14 +166,13 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
if not node:
|
||||
continue
|
||||
node.parent = instance
|
||||
node.save()
|
||||
return Response("OK")
|
||||
|
||||
|
||||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -112,7 +184,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -120,30 +192,47 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
instance = self.get_object()
|
||||
if instance != Node.root():
|
||||
instance.assets.remove(*tuple(assets))
|
||||
else:
|
||||
assets = [asset for asset in assets if asset.nodes.count() > 1]
|
||||
instance.assets.remove(*tuple(assets))
|
||||
|
||||
|
||||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
for asset in assets:
|
||||
asset.nodes.set([instance])
|
||||
|
||||
|
||||
class RefreshNodeHardwareInfoApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class TestNodeConnectiveApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectability_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
@@ -13,21 +13,28 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from ..models import SystemUser
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
test_system_user_connectability_manual
|
||||
test_system_user_connectability_manual, push_system_user_a_asset_manual, \
|
||||
test_system_user_connectability_a_asset
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -35,29 +42,44 @@ class SystemUserViewSet(BulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
Get system user auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.clear_auth()
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
nodes = system_user.nodes.all()
|
||||
for node in nodes:
|
||||
system_user.assets.add(*tuple(node.get_all_assets()))
|
||||
task = push_system_user_to_assets_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
@@ -67,9 +89,62 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
task = test_system_user_connectability_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(SystemUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
system_user = self.get_object()
|
||||
return system_user.assets.all()
|
||||
|
||||
|
||||
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = push_system_user_a_asset_manual.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = test_system_user_connectability_a_asset.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
from ..serializers import CommandFilterRuleSerializer
|
||||
return CommandFilterRuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk', None)
|
||||
system_user = get_object_or_404(SystemUser, pk=pk)
|
||||
return system_user.cmd_filter_rules
|
||||
|
||||
@@ -4,3 +4,4 @@ from .asset import *
|
||||
from .label import *
|
||||
from .user import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -3,20 +3,23 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
'domain', 'protocol',
|
||||
|
||||
]
|
||||
widgets = {
|
||||
@@ -27,13 +30,16 @@ class AssetCreateForm(forms.ModelForm):
|
||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||
}),
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Labels')
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
'nodes': _("Node"),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
@@ -42,34 +48,37 @@ class AssetCreateForm(forms.ModelForm):
|
||||
'root or other NOPASSWD sudo privilege user existed in asset,'
|
||||
'If asset is windows or other set any one, more see admin user left menu'
|
||||
),
|
||||
'platform': _("* required Must set exact system platform, Windows, Linux ..."),
|
||||
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
|
||||
'domain': _("If your have some network not connect with each other, you can set domain")
|
||||
}
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class AssetUpdateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
'domain',
|
||||
'domain', 'protocol',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Nodes')
|
||||
'class': 'select2', 'data-placeholder': _('Node')
|
||||
}),
|
||||
'admin_user': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Admin user')
|
||||
}),
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Labels')
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
'nodes': _("Node"),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
@@ -79,12 +88,12 @@ class AssetUpdateForm(forms.ModelForm):
|
||||
'root or other NOPASSWD sudo privilege user existed in asset,'
|
||||
'If asset is windows or other set any one, more see admin user left menu'
|
||||
),
|
||||
'platform': _("* required Must set exact system platform, Windows, Linux ..."),
|
||||
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
|
||||
'domain': _("If your have some network not connect with each other, you can set domain")
|
||||
}
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
class AssetBulkUpdateForm(OrgModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True, help_text='* required',
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
@@ -99,7 +108,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects.all(),
|
||||
required=False, queryset=AdminUser.objects,
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
@@ -116,10 +125,10 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||
]
|
||||
widgets = {
|
||||
'labels': forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select labels')}
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Label')}
|
||||
),
|
||||
'nodes': forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select nodes')}
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Node')}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -133,14 +142,14 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||
if k in changed_fields}
|
||||
assets = cleaned_data.pop('assets')
|
||||
labels = cleaned_data.pop('labels', [])
|
||||
nodes = cleaned_data.pop('nodes')
|
||||
nodes = cleaned_data.pop('nodes', None)
|
||||
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
|
||||
assets.update(**cleaned_data)
|
||||
|
||||
if labels:
|
||||
for label in labels:
|
||||
label.assets.add(*tuple(assets))
|
||||
for asset in assets:
|
||||
asset.labels.set(labels)
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.assets.add(*tuple(assets))
|
||||
for asset in assets:
|
||||
asset.nodes.set(nodes)
|
||||
return assets
|
||||
|
||||
27
apps/assets/forms/cmd_filter.py
Normal file
27
apps/assets/forms/cmd_filter.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
|
||||
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
|
||||
|
||||
|
||||
class CommandFilterForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = CommandFilter
|
||||
fields = ['name', 'comment']
|
||||
|
||||
|
||||
class CommandFilterRuleForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = [
|
||||
'filter', 'type', 'content', 'priority', 'action', 'comment'
|
||||
]
|
||||
widgets = {
|
||||
'content': forms.Textarea(attrs={
|
||||
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
|
||||
}),
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import Domain, Asset, Gateway
|
||||
from .user import PasswordAndKeyAuthForm
|
||||
|
||||
@@ -34,7 +35,15 @@ class DomainForm(forms.ModelForm):
|
||||
return instance
|
||||
|
||||
|
||||
class GatewayForm(PasswordAndKeyAuthForm):
|
||||
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||
protocol = forms.ChoiceField(
|
||||
choices=[Gateway.PROTOCOL_CHOICES[0]],
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
password_field = self.fields.get('password')
|
||||
password_field.help_text = _('Password should not contain special characters')
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import AdminUser, SystemUser
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import AdminUser, SystemUser
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
@@ -85,7 +86,7 @@ class AdminUserForm(PasswordAndKeyAuthForm):
|
||||
}
|
||||
|
||||
|
||||
class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
|
||||
@@ -93,14 +94,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super().save()
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
login_mode = self.cleaned_data.get('login_mode', '') or None
|
||||
protocol = self.cleaned_data.get('protocol') or None
|
||||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.MANUAL_LOGIN or \
|
||||
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
system_user.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
return system_user
|
||||
|
||||
def clean(self):
|
||||
@@ -109,20 +120,38 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
if not self.instance and not auto_generate:
|
||||
super().validate_password_key()
|
||||
|
||||
def is_valid(self):
|
||||
validated = super().is_valid()
|
||||
username = self.cleaned_data.get('username')
|
||||
login_mode = self.cleaned_data.get('login_mode')
|
||||
if login_mode == SystemUser.AUTO_LOGIN and not username:
|
||||
self.add_error(
|
||||
"username", _('* Automatic login mode,'
|
||||
' must fill in the username.')
|
||||
)
|
||||
return False
|
||||
return validated
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'priority',
|
||||
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'cmd_filters': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Command filter')
|
||||
}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'auto_push': _('Auto push system user to asset'),
|
||||
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
|
||||
}
|
||||
'priority': _('1-100, High level will be using login asset as default, '
|
||||
'if user was granted more than 2 system user'),
|
||||
'login_mode': _('If you choose manual login mode, you do not '
|
||||
'need to fill in the username and password.')
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"""
|
||||
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
from perms.utils import NodePermissionUtil
|
||||
|
||||
35
apps/assets/migrations/0002_auto_20180105_1807.py
Normal file
35
apps/assets/migrations/0002_auto_20180105_1807.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-05 10:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
]
|
||||
22
apps/assets/migrations/0003_auto_20180109_2331.py
Normal file
22
apps/assets/migrations/0003_auto_20180109_2331.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-09 15:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0002_auto_20180105_1807'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||
),
|
||||
]
|
||||
20
apps/assets/migrations/0004_auto_20180125_1218.py
Normal file
20
apps/assets/migrations/0004_auto_20180125_1218.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-25 04:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0003_auto_20180109_2331'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
]
|
||||
40
apps/assets/migrations/0005_auto_20180126_1637.py
Normal file
40
apps/assets/migrations/0005_auto_20180126_1637.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-26 08:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0004_auto_20180125_1218'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together=set([('name', 'value')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
]
|
||||
39
apps/assets/migrations/0006_auto_20180130_1502.py
Normal file
39
apps/assets/migrations/0006_auto_20180130_1502.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-30 07:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0005_auto_20180126_1637'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
]
|
||||
60
apps/assets/migrations/0007_auto_20180225_1815.py
Normal file
60
apps/assets/migrations/0007_auto_20180225_1815.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-02-25 10:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0006_auto_20180130_1502'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
]
|
||||
40
apps/assets/migrations/0008_auto_20180306_1804.py
Normal file
40
apps/assets/migrations/0008_auto_20180306_1804.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-06 10:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0007_auto_20180225_1815'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
20
apps/assets/migrations/0009_auto_20180307_1212.py
Normal file
20
apps/assets/migrations/0009_auto_20180307_1212.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 04:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0008_auto_20180306_1804'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
20
apps/assets/migrations/0010_auto_20180307_1749.py
Normal file
20
apps/assets/migrations/0010_auto_20180307_1749.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 09:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
55
apps/assets/migrations/0011_auto_20180326_0957.py
Normal file
55
apps/assets/migrations/0011_auto_20180326_0957.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-26 01:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.utils
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0010_auto_20180307_1749'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
||||
21
apps/assets/migrations/0012_auto_20180404_1302.py
Normal file
21
apps/assets/migrations/0012_auto_20180404_1302.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-04 05:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0011_auto_20180326_0957'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
||||
25
apps/assets/migrations/0013_auto_20180411_1135.py
Normal file
25
apps/assets/migrations/0013_auto_20180411_1135.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-11 03:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0012_auto_20180404_1302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
]
|
||||
31
apps/assets/migrations/0014_auto_20180427_1245.py
Normal file
31
apps/assets/migrations/0014_auto_20180427_1245.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-27 04:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0013_auto_20180411_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
31
apps/assets/migrations/0015_auto_20180510_1235.py
Normal file
31
apps/assets/migrations/0015_auto_20180510_1235.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-10 04:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0014_auto_20180427_1245'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
20
apps/assets/migrations/0016_auto_20180511_1203.py
Normal file
20
apps/assets/migrations/0016_auto_20180511_1203.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-11 04:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0015_auto_20180510_1235'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
58
apps/assets/migrations/0017_auto_20180702_1415.py
Normal file
58
apps/assets/migrations/0017_auto_20180702_1415.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-07-02 06:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0016_auto_20180511_1203'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(migrate_win_to_ssh_protocol),
|
||||
]
|
||||
84
apps/assets/migrations/0018_auto_20180807_1116.py
Normal file
84
apps/assets/migrations/0018_auto_20180807_1116.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0017_auto_20180702_1415'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
]
|
||||
22
apps/assets/migrations/0019_auto_20180816_1320.py
Normal file
22
apps/assets/migrations/0019_auto_20180816_1320.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-16 05:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0018_auto_20180807_1116'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
||||
48
apps/assets/migrations/0020_auto_20180816_1652.py
Normal file
48
apps/assets/migrations/0020_auto_20180816_1652.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-16 08:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0019_auto_20180816_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
25
apps/assets/migrations/0021_auto_20180903_1132.py
Normal file
25
apps/assets/migrations/0021_auto_20180903_1132.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 2.1 on 2018-09-03 03:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0020_auto_20180816_1652'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='domain',
|
||||
options={'verbose_name': 'Domain'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='gateway',
|
||||
options={'verbose_name': 'Gateway'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='node',
|
||||
options={'verbose_name': 'Node'},
|
||||
),
|
||||
]
|
||||
56
apps/assets/migrations/0022_auto_20181012_1717.py
Normal file
56
apps/assets/migrations/0022_auto_20181012_1717.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generated by Django 2.1.1 on 2018-10-12 09:17
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0021_auto_20180903_1132'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CommandFilter',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64, verbose_name='Name')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CommandFilterRule',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
|
||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||
('content', models.TextField(help_text='One line one command', max_length=1024, verbose_name='Content')),
|
||||
('action', models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow')], default=0, verbose_name='Action')),
|
||||
('comment', models.CharField(blank=True, default='', max_length=64, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='assets.CommandFilter', verbose_name='Filter')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('priority', 'action'),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='cmd_filters',
|
||||
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.CommandFilter', verbose_name='Command filter'),
|
||||
),
|
||||
]
|
||||
28
apps/assets/migrations/0023_auto_20181016_1650.py
Normal file
28
apps/assets/migrations/0023_auto_20181016_1650.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.1.1 on 2018-10-16 08:50
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0022_auto_20181012_1717'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilterrule',
|
||||
options={'ordering': ('-priority', 'action')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandfilterrule',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=50, help_text='1-100, the higher will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||
),
|
||||
]
|
||||
@@ -1,11 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .user import AdminUser, SystemUser
|
||||
from .user import *
|
||||
from .label import Label
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .asset import *
|
||||
from .cmd_filter import *
|
||||
from .utils import *
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
import uuid
|
||||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -30,12 +34,21 @@ def default_cluster():
|
||||
def default_node():
|
||||
try:
|
||||
from .node import Node
|
||||
return Node.root()
|
||||
root = Node.root()
|
||||
return root
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
class AssetQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
||||
def valid(self):
|
||||
return self.active()
|
||||
|
||||
|
||||
class Asset(OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
@@ -43,48 +56,85 @@ class Asset(models.Model):
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
TELNET_PROTOCOL = 'telnet'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
|
||||
related_name='assets', verbose_name=_("Domain"),
|
||||
on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node,
|
||||
related_name='assets',
|
||||
verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT,
|
||||
null=True, verbose_name=_("Admin user"))
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True,
|
||||
verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True,
|
||||
verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True,
|
||||
verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True,
|
||||
verbose_name=_('Disk info'))
|
||||
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
os = models.CharField(max_length=128, null=True, blank=True,
|
||||
verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True,
|
||||
verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True,
|
||||
verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True,
|
||||
verbose_name=_('Hostname raw'))
|
||||
|
||||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
labels = models.ManyToManyField('assets.Label', blank=True,
|
||||
related_name='assets',
|
||||
verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True,
|
||||
verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
blank=True,
|
||||
verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True,
|
||||
verbose_name=_('Comment'))
|
||||
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
|
||||
def __str__(self):
|
||||
return self.hostname
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
@@ -95,17 +145,52 @@ class Asset(models.Model):
|
||||
return True, ''
|
||||
return False, warning
|
||||
|
||||
def support_ansible(self):
|
||||
if self.platform in ("Windows", "Windows2016", "Other"):
|
||||
return False
|
||||
if self.protocol != 'ssh':
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows",):
|
||||
if self.platform not in ("Windows", "Windows2016"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
nodes = self.nodes.all() or [Node.root()]
|
||||
return nodes
|
||||
|
||||
def get_all_nodes(self, flat=False):
|
||||
nodes = []
|
||||
for node in self.get_nodes():
|
||||
_nodes = node.get_ancestor(with_self=True)
|
||||
_nodes.append(_nodes)
|
||||
if flat:
|
||||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def get_queryset_by_fullname_list(cls, fullname_list):
|
||||
org_fullname_map = defaultdict(list)
|
||||
for fullname in fullname_list:
|
||||
hostname, org = cls.split_fullname(fullname)
|
||||
org_fullname_map[org].append(hostname)
|
||||
filter_arg = Q()
|
||||
for org, hosts in org_fullname_map.items():
|
||||
if org.is_real():
|
||||
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
|
||||
else:
|
||||
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
|
||||
return Asset.objects.filter(filter_arg)
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
return '{} Core {} {}'.format(
|
||||
self.cpu_count * self.cpu_cores,
|
||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||
self.memory, self.disk_total
|
||||
)
|
||||
else:
|
||||
@@ -141,6 +226,16 @@ class Asset(models.Model):
|
||||
'become': self.admin_user.become_info,
|
||||
}
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
fake_node = Node()
|
||||
fake_node.id = self.id
|
||||
fake_node.key = self.id
|
||||
fake_node.value = self.hostname
|
||||
fake_node.asset = self
|
||||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory, First using asset user,
|
||||
@@ -161,7 +256,7 @@ class Asset(models.Model):
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
unique_together = ('ip', 'port')
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
||||
@classmethod
|
||||
@@ -169,16 +264,23 @@ class Asset(models.Model):
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
from .node import Node
|
||||
nodes = list(Node.objects.all())
|
||||
seed()
|
||||
for i in range(count):
|
||||
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
||||
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||
asset = cls(ip='.'.join(ip),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
port=22,
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
if nodes and len(nodes) > 3:
|
||||
_nodes = random.sample(nodes, 3)
|
||||
else:
|
||||
_nodes = [Node.default_node()]
|
||||
asset.nodes.set(_nodes)
|
||||
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
|
||||
logger.debug('Generate fake asset : %s' % asset.ip)
|
||||
except IntegrityError:
|
||||
|
||||
@@ -10,15 +10,17 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||
from common.validators import alphanumeric
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
|
||||
class AssetUser(models.Model):
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
@@ -103,10 +105,19 @@ class AssetUser(models.Model):
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def get_auth(self, asset=None):
|
||||
pass
|
||||
|
||||
def clear_auth(self):
|
||||
self._password = ''
|
||||
self._private_key = ''
|
||||
self._public_key = ''
|
||||
self.save()
|
||||
|
||||
def auto_gen_auth(self):
|
||||
password = str(uuid.uuid4())
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=self.name, password=password
|
||||
username=self.username
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
|
||||
@@ -52,7 +52,8 @@ class Cluster(models.Model):
|
||||
contact=forgery_py.name.full_name(),
|
||||
phone=forgery_py.address.phone(),
|
||||
address=forgery_py.address.city() + forgery_py.address.street_address(),
|
||||
operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
# operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
|
||||
87
apps/assets/models/cmd_filter.py
Normal file
87
apps/assets/models/cmd_filter.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CommandFilter', 'CommandFilterRule'
|
||||
]
|
||||
|
||||
|
||||
class CommandFilter(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, verbose_name=_("Name"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class CommandFilterRule(OrgModelMixin):
|
||||
TYPE_REGEX = 'regex'
|
||||
TYPE_COMMAND = 'command'
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_REGEX, _('Regex')),
|
||||
(TYPE_COMMAND, _('Command')),
|
||||
)
|
||||
|
||||
ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3)
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_DENY, _('Deny')),
|
||||
(ACTION_ALLOW, _('Allow')),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
|
||||
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
|
||||
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the higher will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
__pattern = None
|
||||
|
||||
class Meta:
|
||||
ordering = ('-priority', 'action')
|
||||
|
||||
@property
|
||||
def _pattern(self):
|
||||
if self.__pattern:
|
||||
return self.__pattern
|
||||
if self.type == 'command':
|
||||
regex = []
|
||||
for cmd in self.content.split('\r\n'):
|
||||
cmd = cmd.replace(' ', '\s+')
|
||||
regex.append(r'\b{0}\b'.format(cmd))
|
||||
self.__pattern = re.compile(r'{}'.format('|'.join(regex)))
|
||||
else:
|
||||
self.__pattern = re.compile(r'{0}'.format(self.content))
|
||||
return self.__pattern
|
||||
|
||||
def match(self, data):
|
||||
found = self._pattern.search(data)
|
||||
if not found:
|
||||
return self.ACTION_UNKNOWN, ''
|
||||
|
||||
if self.action == self.ACTION_ALLOW:
|
||||
return self.ACTION_ALLOW, found.group()
|
||||
else:
|
||||
return self.ACTION_DENY, found.group()
|
||||
|
||||
def __str__(self):
|
||||
return '{} % {}'.format(self.type, self.content)
|
||||
@@ -4,21 +4,27 @@
|
||||
import uuid
|
||||
import random
|
||||
|
||||
import paramiko
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .base import AssetUser
|
||||
|
||||
__all__ = ['Domain', 'Gateway']
|
||||
|
||||
|
||||
class Domain(models.Model):
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
verbose_name=_('Date created'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Domain")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -43,10 +49,47 @@ class Gateway(AssetUser):
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol"))
|
||||
domain = models.ForeignKey(Domain, verbose_name=_("Domain"))
|
||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Gateway")
|
||||
|
||||
def test_connective(self):
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
proxy = paramiko.SSHClient()
|
||||
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
try:
|
||||
proxy.connect(self.ip, port=self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
pkey=self.private_key_obj)
|
||||
except(paramiko.AuthenticationException,
|
||||
paramiko.BadAuthenticationType,
|
||||
paramiko.SSHException) as e:
|
||||
return False, str(e)
|
||||
|
||||
sock = proxy.get_transport().open_channel(
|
||||
'direct-tcpip', ('127.0.0.1', self.port), ('127.0.0.1', 0)
|
||||
)
|
||||
|
||||
try:
|
||||
client.connect("127.0.0.1", port=self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
key_filename=self.private_key_file,
|
||||
sock=sock,
|
||||
timeout=5)
|
||||
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
|
||||
paramiko.AuthenticationException, TimeoutError) as e:
|
||||
return False, str(e)
|
||||
finally:
|
||||
client.close()
|
||||
return True, None
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
|
||||
class Label(models.Model):
|
||||
class Label(OrgModelMixin):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
@@ -16,7 +17,8 @@ class Label(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
||||
default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(
|
||||
@@ -34,4 +36,4 @@ class Label(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = ('name', 'value')
|
||||
unique_together = [('name', 'value', 'org_id')]
|
||||
|
||||
@@ -2,22 +2,48 @@
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from orgs.utils import set_current_org, get_current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
class Node(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
is_node = True
|
||||
_full_value_cache_key_prefix = '_NODE_VALUE_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.key == other.key
|
||||
|
||||
def __gt__(self, other):
|
||||
if self.is_root():
|
||||
return True
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
return self_key.__lt__(other_key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -25,10 +51,29 @@ class Node(models.Model):
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
if self == self.__class__.root():
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
value = self.get_full_value()
|
||||
self.cache_full_value(value)
|
||||
return value
|
||||
|
||||
def get_full_value(self):
|
||||
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
||||
if self.is_root():
|
||||
return self.value
|
||||
else:
|
||||
return '{}/{}'.format(self.value, self.parent.full_value)
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
return value
|
||||
|
||||
def cache_full_value(self, value):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
@@ -41,79 +86,156 @@ class Node(models.Model):
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def create_child(self, value):
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(key=child_key, value=value)
|
||||
return child
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(key=child_key, value=value)
|
||||
return child
|
||||
|
||||
def get_children(self):
|
||||
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
|
||||
def get_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_all_children(self):
|
||||
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
|
||||
def get_all_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_sibling(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
children = list(self.get_all_children())
|
||||
children.append(self)
|
||||
return children
|
||||
ancestor = self.get_ancestor()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets
|
||||
if self.is_default_node():
|
||||
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
|
||||
else:
|
||||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets.distinct()
|
||||
|
||||
def get_active_assets(self):
|
||||
return self.get_assets().filter(is_active=True)
|
||||
def get_valid_assets(self):
|
||||
return self.get_assets().valid()
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
args = []
|
||||
kwargs = {}
|
||||
if self.is_default_node():
|
||||
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
|
||||
else:
|
||||
nodes = self.get_family()
|
||||
assets = Asset.objects.filter(nodes__in=nodes)
|
||||
kwargs['nodes__key__regex'] = pattern
|
||||
assets = Asset.objects.filter(*args, **kwargs).distinct()
|
||||
return assets
|
||||
|
||||
def get_all_active_assets(self):
|
||||
return self.get_all_assets().filter(is_active=True)
|
||||
def get_all_valid_assets(self):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.is_root() and self.key == '0'
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.key == "0":
|
||||
return self.__class__.root()
|
||||
elif not self.key.startswith("0"):
|
||||
return self.__class__.root()
|
||||
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=parent_key)
|
||||
parent = self.__class__.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
else:
|
||||
return parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
self.key = parent.get_next_child_key()
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def ancestor(self):
|
||||
if self.parent == self.__class__.root():
|
||||
return [self.__class__.root()]
|
||||
else:
|
||||
return [self.parent, *tuple(self.parent.ancestor)]
|
||||
|
||||
@property
|
||||
def ancestor_with_node(self):
|
||||
ancestor = self.ancestor
|
||||
ancestor.insert(0, self)
|
||||
def get_ancestor(self, with_self=False):
|
||||
if self.is_root():
|
||||
root = self.__class__.root()
|
||||
return [root]
|
||||
_key = self.key.split(':')
|
||||
if not with_self:
|
||||
_key.pop()
|
||||
ancestor_keys = []
|
||||
for i in range(len(_key)):
|
||||
ancestor_keys.append(':'.join(_key))
|
||||
_key.pop()
|
||||
ancestor = self.__class__.objects.filter(
|
||||
key__in=ancestor_keys
|
||||
).order_by('key')
|
||||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def create_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
_current_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if not _current_org.is_real():
|
||||
return cls.default_node()
|
||||
set_current_org(Organization.root())
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1']
|
||||
key = max([int(k) for k in org_nodes_roots_keys])
|
||||
key = str(key + 1) if key != 0 else '2'
|
||||
set_current_org(_current_org)
|
||||
root = cls.objects.create(key=key, value=_current_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
obj, created = cls.objects.get_or_create(
|
||||
key='0', defaults={"key": '0', 'value': "ROOT"}
|
||||
)
|
||||
return obj
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_root_node()
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
defaults = {'value': 'Default'}
|
||||
return cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
|
||||
@classmethod
|
||||
def get_tree_name_ref(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
for i in range(count):
|
||||
node = random.choice(cls.objects.all())
|
||||
node.create_child('Node {}'.format(i))
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import get_signer
|
||||
from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
||||
@@ -68,6 +69,7 @@ class AdminUser(AssetUser):
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Admin user")
|
||||
|
||||
@classmethod
|
||||
@@ -94,20 +96,35 @@ class AdminUser(AssetUser):
|
||||
class SystemUser(AssetUser):
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
TELNET_PROTOCOL = 'telnet'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||
)
|
||||
|
||||
AUTO_LOGIN = 'auto'
|
||||
MANUAL_LOGIN = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(AUTO_LOGIN, _('Automatic login')),
|
||||
(MANUAL_LOGIN, _('Manually login'))
|
||||
)
|
||||
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
priority = models.IntegerField(default=20, verbose_name=_("Priority"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
|
||||
cache_key = "__SYSTEM_USER_CACHED_{}"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
@@ -119,11 +136,8 @@ class SystemUser(AssetUser):
|
||||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
@property
|
||||
def assets(self):
|
||||
assets = set()
|
||||
for node in self.nodes.all():
|
||||
assets.update(set(node.get_all_assets()))
|
||||
def get_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
return assets
|
||||
|
||||
@property
|
||||
@@ -145,8 +159,44 @@ class SystemUser(AssetUser):
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.cache_key.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.cache_key.format(self.id))
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
rules = CommandFilterRule.objects.filter(
|
||||
filter__in=self.cmd_filters.all()
|
||||
).distinct()
|
||||
return rules
|
||||
|
||||
def is_command_can_run(self, command):
|
||||
for rule in self.cmd_filter_rules:
|
||||
action, matched_cmd = rule.match(command)
|
||||
if action == rule.ACTION_ALLOW:
|
||||
return True, None
|
||||
elif action == rule.ACTION_DENY:
|
||||
return False, matched_cmd
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def get_system_user_by_id_or_cached(cls, sid):
|
||||
cached = cache.get(cls.cache_key.format(sid))
|
||||
if cached:
|
||||
return cached
|
||||
try:
|
||||
system_user = cls.objects.get(id=sid)
|
||||
system_user.set_cache()
|
||||
return system_user
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("System user")
|
||||
|
||||
@classmethod
|
||||
@@ -168,6 +218,3 @@ class SystemUser(AssetUser):
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
||||
管理用户更新关联到的集群
|
||||
"""
|
||||
nodes = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=Node.objects.all()
|
||||
many=True, queryset = Node.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -7,22 +7,25 @@ from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
|
||||
]
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = [] # If not set to [], partial bulk update will be error
|
||||
validators = []
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'is_connective',
|
||||
'hardware_info', 'is_connective', 'org_name'
|
||||
])
|
||||
return fields
|
||||
|
||||
@@ -33,13 +36,14 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment"
|
||||
"platform", "comment", "protocol", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -57,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
|
||||
23
apps/assets/serializers/cmd_filter.py
Normal file
23
apps/assets/serializers/cmd_filter.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import ChoiceDisplayField
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
|
||||
|
||||
class CommandFilterSerializer(serializers.ModelSerializer):
|
||||
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
|
||||
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
|
||||
|
||||
class Meta:
|
||||
model = CommandFilter
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CommandFilterRuleSerializer(serializers.ModelSerializer):
|
||||
serializer_choice_field = ChoiceDisplayField
|
||||
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = '__all__'
|
||||
@@ -23,7 +23,6 @@ class DomainSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class GatewaySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Gateway
|
||||
fields = [
|
||||
|
||||
@@ -7,6 +7,12 @@ from ..models import Asset, Node
|
||||
from .asset import AssetGrantedSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
|
||||
"NodeAssetsSerializer",
|
||||
]
|
||||
|
||||
|
||||
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
@@ -20,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount',
|
||||
'assets_granted', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@@ -37,22 +43,43 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
parent = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
tree_id = serializers.SerializerMethodField()
|
||||
tree_parent = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount']
|
||||
fields = [
|
||||
'id', 'key', 'value', 'assets_amount',
|
||||
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
||||
]
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
def validate(self, data):
|
||||
value = data.get('value')
|
||||
instance = self.instance if self.instance else Node.root()
|
||||
children = instance.parent.get_children().exclude(key=instance.key)
|
||||
values = [child.value for child in children]
|
||||
if value in values:
|
||||
raise serializers.ValidationError(
|
||||
'The same level node name cannot be the same'
|
||||
)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
if hasattr(obj, 'assets_amount'):
|
||||
return obj.assets_amount
|
||||
return obj.get_all_assets().count()
|
||||
|
||||
@staticmethod
|
||||
def get_tree_id(obj):
|
||||
return obj.key
|
||||
|
||||
@staticmethod
|
||||
def get_tree_parent(obj):
|
||||
return obj.parent_key
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
field = fields["key"]
|
||||
|
||||
@@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'get_login_mode_display',
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.unreachable_assets
|
||||
@@ -34,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets)
|
||||
return len(obj.get_assets())
|
||||
|
||||
|
||||
class SystemUserAuthSerializer(AuthSerializer):
|
||||
@@ -46,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
|
||||
model = SystemUser
|
||||
fields = [
|
||||
"id", "name", "username", "protocol",
|
||||
"password", "private_key",
|
||||
"login_mode", "password", "private_key",
|
||||
]
|
||||
|
||||
|
||||
@@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||
fields = (
|
||||
'id', 'name', 'username', 'priority',
|
||||
'protocol', 'comment', 'login_mode'
|
||||
)
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from collections import defaultdict
|
||||
from django.db.models.signals import post_save, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import Asset, SystemUser, Node
|
||||
from .tasks import update_assets_hardware_info_util, \
|
||||
test_asset_connectability_util, push_system_user_to_node, \
|
||||
push_node_system_users_to_asset
|
||||
test_asset_connectability_util, push_system_user_to_assets
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
@@ -31,7 +30,6 @@ def set_asset_root_node(asset):
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
set_asset_root_node(instance)
|
||||
if created:
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
@@ -41,31 +39,56 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||
if instance and not created:
|
||||
for node in instance.nodes.all():
|
||||
push_system_user_to_node(instance, node)
|
||||
logger.info("System user `{}` update signal received".format(instance))
|
||||
assets = instance.assets.all()
|
||||
push_system_user_to_assets.delay(instance, assets)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||
def on_system_user_node_change(sender, instance=None, **kwargs):
|
||||
def on_system_user_nodes_change(sender, instance=None, **kwargs):
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
for pk in kwargs['pk_set']:
|
||||
node = kwargs['model'].objects.get(pk=pk)
|
||||
push_system_user_to_node(instance, node)
|
||||
assets = set()
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
for node in nodes:
|
||||
assets.update(set(node.get_all_assets()))
|
||||
instance.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
||||
def on_system_user_assets_change(sender, instance=None, **kwargs):
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
push_system_user_to_assets.delay(instance, assets)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
|
||||
logger.debug("Asset node change signal received")
|
||||
for pk in kwargs['pk_set']:
|
||||
node = kwargs['model'].objects.get(pk=pk)
|
||||
push_node_system_users_to_asset(node, [instance])
|
||||
if isinstance(instance, Asset):
|
||||
if kwargs['action'] == 'post_add':
|
||||
logger.debug("Asset node change signal received")
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
system_users_assets = defaultdict(set)
|
||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||
# 清理节点缓存
|
||||
for system_user in system_users:
|
||||
system_users_assets[system_user].update({instance})
|
||||
for system_user, assets in system_users_assets.items():
|
||||
system_user.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
|
||||
logger.debug("Node assets change signal received")
|
||||
if isinstance(instance, Node):
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
push_node_system_users_to_asset(instance, assets)
|
||||
if kwargs['action'] == 'post_add':
|
||||
logger.debug("Node assets change signal received")
|
||||
# 重新关联系统用户和资产的关系
|
||||
system_users = SystemUser.objects.filter(nodes=instance)
|
||||
for system_user in system_users:
|
||||
system_user.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
@receiver(post_save, sender=Node)
|
||||
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
|
||||
if instance and not created:
|
||||
instance.expire_full_value()
|
||||
|
||||
@@ -4,14 +4,15 @@ import re
|
||||
import os
|
||||
|
||||
from celery import shared_task
|
||||
from ops.celery import app as celery_app
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_object_or_none, capacity_convert, \
|
||||
from common.utils import capacity_convert, \
|
||||
sum_capacity, encrypt_password, get_logger
|
||||
from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \
|
||||
after_app_ready_start
|
||||
from ops.celery import app as celery_app
|
||||
from orgs.utils import set_to_root_org
|
||||
|
||||
from .models import SystemUser, AdminUser, Asset
|
||||
from . import const
|
||||
@@ -20,34 +21,34 @@ from . import const
|
||||
FORKS = 10
|
||||
TIMEOUT = 60
|
||||
logger = get_logger(__file__)
|
||||
CACHE_MAX_TIME = 60*60*60
|
||||
CACHE_MAX_TIME = 60*60*2
|
||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
|
||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_assets_hardware_info(result, **kwargs):
|
||||
def set_assets_hardware_info(assets, result, **kwargs):
|
||||
"""
|
||||
Using ops task run result, to update asset info
|
||||
|
||||
@shared_task must be exit, because we using it as a task callback, is must
|
||||
be a celery task also
|
||||
:param assets:
|
||||
:param result:
|
||||
:param kwargs: {task_name: ""}
|
||||
:return:
|
||||
"""
|
||||
result_raw = result[0]
|
||||
assets_updated = []
|
||||
for hostname, info in result_raw.get('ok', {}).items():
|
||||
success_result = result_raw.get('ok', {})
|
||||
|
||||
for asset in assets:
|
||||
hostname = asset.hostname
|
||||
info = success_result.get(hostname, {})
|
||||
info = info.get('setup', {}).get('ansible_facts', {})
|
||||
if not info:
|
||||
logger.error("Get asset info failed: {}".format(hostname))
|
||||
logger.error(_("Get asset info failed: {}").format(hostname))
|
||||
continue
|
||||
|
||||
asset = get_object_or_none(Asset, hostname=hostname)
|
||||
if not asset:
|
||||
continue
|
||||
|
||||
___vendor = info.get('ansible_system_vendor', 'Unknown')
|
||||
___model = info.get('ansible_product_name', 'Unknown')
|
||||
___sn = info.get('ansible_product_serial', 'Unknown')
|
||||
@@ -60,6 +61,7 @@ def set_assets_hardware_info(result, **kwargs):
|
||||
___cpu_model = ___cpu_model[:64]
|
||||
___cpu_count = info.get('ansible_processor_count', 0)
|
||||
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', []))
|
||||
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
|
||||
disk_info = {}
|
||||
for dev, dev_info in info.get('ansible_devices', {}).items():
|
||||
@@ -92,32 +94,44 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if task_name is None:
|
||||
# task_name = _("Update some assets hardware info")
|
||||
task_name = _("更新资产硬件信息")
|
||||
task_name = _("Update some assets hardware info")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
created_by = str(assets[0].org_id)
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
|
||||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
)
|
||||
result = task.run()
|
||||
# Todo: may be somewhere using
|
||||
# Manual run callback function
|
||||
set_assets_hardware_info(result)
|
||||
set_assets_hardware_info(assets, result)
|
||||
return result
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
# task_name = _("Update asset hardware info")
|
||||
task_name = _("更新资产硬件信息")
|
||||
return update_assets_hardware_info_util([asset], task_name=task_name)
|
||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||
# task_name = _("更新资产硬件信息")
|
||||
return update_assets_hardware_info_util(
|
||||
[asset], task_name=task_name
|
||||
)
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
@shared_task
|
||||
def update_assets_hardware_info_period():
|
||||
"""
|
||||
Update asset hardware period task
|
||||
@@ -128,25 +142,28 @@ def update_assets_hardware_info_period():
|
||||
return
|
||||
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
# task_name = _("Update assets hardware info period")
|
||||
task_name = _("定期更新资产硬件信息")
|
||||
hostname_list = [
|
||||
asset.hostname for asset in Asset.objects.all()
|
||||
if asset.is_active and asset.is_unixlike()
|
||||
]
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
|
||||
# Only create, schedule by celery beat
|
||||
update_or_create_ansible_task(
|
||||
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
|
||||
)
|
||||
from orgs.models import Organization
|
||||
orgs = Organization.objects.all().values_list('id', flat=True)
|
||||
orgs.append('')
|
||||
task_name = _("Update assets hardware info period")
|
||||
# for org_id in orgs:
|
||||
# org_id = str(org_id)
|
||||
# hostname_list = [
|
||||
# asset for asset in Asset.objects.all()
|
||||
# if asset.is_active and asset.is_unixlike()
|
||||
# ]
|
||||
# tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
#
|
||||
# # Only create, schedule by celery beat
|
||||
# update_or_create_ansible_task(
|
||||
# task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||
# options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
# interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
|
||||
# )
|
||||
|
||||
|
||||
## ADMIN USER CONNECTIVE ##
|
||||
|
||||
@shared_task
|
||||
def set_admin_user_connectability_info(result, **kwargs):
|
||||
admin_user = kwargs.get("admin_user")
|
||||
task_name = kwargs.get("task_name")
|
||||
@@ -178,43 +195,46 @@ def test_admin_user_connectability_util(admin_user, task_name):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = [asset.hostname for asset in assets
|
||||
if asset.is_active and asset.is_unixlike()]
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
return
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
set_admin_user_connectability_info(result, admin_user=admin_user.name)
|
||||
return result
|
||||
|
||||
|
||||
@celery_app.task
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def test_admin_user_connectability_period():
|
||||
"""
|
||||
A period task that update the ansible task period
|
||||
"""
|
||||
if PERIOD_TASK != "on":
|
||||
logger.debug("Period task disabled, test admin user connectability pass")
|
||||
return
|
||||
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
# task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
||||
task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
|
||||
task_name = _("Test admin user connectability period: {}").format(admin_user.name)
|
||||
test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectability_manual(admin_user):
|
||||
# task_name = _("Test admin user connectability: {}").format(admin_user.name)
|
||||
task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
|
||||
task_name = _("Test admin user connectability: {}").format(admin_user.name)
|
||||
# task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
|
||||
return test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@@ -223,16 +243,27 @@ def test_asset_connectability_util(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if task_name is None:
|
||||
# task_name = _("Test assets connectability")
|
||||
task_name = _("测试资产可连接性")
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
task_name = _("Test assets connectability")
|
||||
# task_name = _("测试资产可连接性")
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
logger.info("No hosts, passed")
|
||||
logger.info(_("No assets, task stop"))
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
created_by = assets[0].org_id
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
@@ -246,7 +277,8 @@ def test_asset_connectability_util(assets, task_name=None):
|
||||
|
||||
@shared_task
|
||||
def test_asset_connectability_manual(asset):
|
||||
summary = test_asset_connectability_util([asset])
|
||||
task_name = _("Test assets connectability: {}").format(asset)
|
||||
summary = test_asset_connectability_util([asset], task_name=task_name)
|
||||
|
||||
if summary.get('dark'):
|
||||
return False, summary['dark']
|
||||
@@ -263,54 +295,69 @@ def set_system_user_connectablity_info(result, **kwargs):
|
||||
system_user = kwargs.get("system_user")
|
||||
if system_user is None:
|
||||
system_user = task_name.split(":")[-1]
|
||||
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(system_user)
|
||||
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id))
|
||||
cache.set(cache_key, summary, CACHE_MAX_TIME)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_util(system_user, task_name):
|
||||
def test_system_user_connectability_util(system_user, assets, task_name):
|
||||
"""
|
||||
Test system cant connect his assets or not.
|
||||
:param system_user:
|
||||
:param assets:
|
||||
:param task_name:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
assets = system_user.assets
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
hosts = []
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
logger.info("No hosts, passed")
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
run_as=system_user.name, created_by="System",
|
||||
run_as=system_user, created_by=system_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
set_system_user_connectablity_info(result, system_user=system_user.name)
|
||||
set_system_user_connectablity_info(result, system_user=system_user)
|
||||
return result
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_manual(system_user):
|
||||
task_name = _("Test system user connectability: {}").format(system_user)
|
||||
return test_system_user_connectability_util(system_user, task_name)
|
||||
assets = system_user.get_assets()
|
||||
return test_system_user_connectability_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_a_asset(system_user, asset):
|
||||
task_name = _("Test system user connectability: {} => {}").format(
|
||||
system_user, asset
|
||||
)
|
||||
return test_system_user_connectability_util(system_user, [asset], task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def test_system_user_connectability_period():
|
||||
if PERIOD_TASK != "on":
|
||||
logger.debug("Period task disabled, test system user connectability pass")
|
||||
return
|
||||
|
||||
system_users = SystemUser.objects.all()
|
||||
for system_user in system_users:
|
||||
# task_name = _("Test system user connectability period: {}".format(system_user))
|
||||
task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||
task_name = _("Test system user connectability period: {}").format(system_user)
|
||||
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||
test_system_user_connectability_util(system_user, task_name)
|
||||
|
||||
|
||||
@@ -360,78 +407,56 @@ def get_push_system_user_tasks(system_user):
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_util(system_users, assets, task_name):
|
||||
def push_system_user_util(system_user, assets, task_name):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = []
|
||||
for system_user in system_users:
|
||||
if not system_user.is_need_push():
|
||||
msg = "push system user `{}` passed, may be not auto push or ssh " \
|
||||
"protocol is not ssh".format(system_user.name)
|
||||
if not system_user.is_need_push():
|
||||
msg = _("Push system user task skip, auto push not enable or "
|
||||
"protocol is not ssh: {}").format(system_user.name)
|
||||
logger.info(msg)
|
||||
return
|
||||
|
||||
tasks = get_push_system_user_tasks(system_user)
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
tasks.extend(get_push_system_user_tasks(system_user))
|
||||
|
||||
if not tasks:
|
||||
logger.info("Not tasks, passed")
|
||||
return {}
|
||||
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
logger.info("Not hosts, passed")
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System'
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=system_user.org_id,
|
||||
)
|
||||
return task.run()
|
||||
|
||||
|
||||
def get_node_push_system_user_task_name(system_user, node):
|
||||
|
||||
# return _("Push system user to node: {} => {}").format(
|
||||
return _("推送系统用户到节点资产: {} => {}").format(
|
||||
system_user.name,
|
||||
node.value
|
||||
)
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_to_node(system_user, node):
|
||||
logger.info("Start push system user node: {} => {}".format(system_user.name, node.value))
|
||||
assets = node.get_all_assets()
|
||||
task_name = get_node_push_system_user_task_name(system_user, node)
|
||||
push_system_user_util([system_user], assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_related_nodes(system_user):
|
||||
if not system_user.is_need_push():
|
||||
msg = "push system user `{}` passed, may be not auto push or ssh " \
|
||||
"protocol is not ssh".format(system_user.name)
|
||||
logger.info(msg)
|
||||
return
|
||||
|
||||
nodes = system_user.nodes.all()
|
||||
for node in nodes:
|
||||
push_system_user_to_node(system_user, node)
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_to_assets_manual(system_user):
|
||||
push_system_user_related_nodes(system_user)
|
||||
assets = system_user.get_assets()
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name=task_name)
|
||||
|
||||
|
||||
def push_node_system_users_to_asset(node, assets):
|
||||
system_users = []
|
||||
nodes = node.ancestor_with_node
|
||||
# 获取该节点所有父节点有的系统用户, 然后推送
|
||||
for n in nodes:
|
||||
system_users.extend(list(n.systemuser_set.all()))
|
||||
@shared_task
|
||||
def push_system_user_a_asset_manual(system_user, asset):
|
||||
task_name = _("Push system users to asset: {} => {}").format(
|
||||
system_user.name, asset
|
||||
)
|
||||
return push_system_user_util(system_user, [asset], task_name=task_name)
|
||||
|
||||
if system_users:
|
||||
# task_name = _("Push system users to node: {}").format(node.value)
|
||||
task_name = _("推送节点系统用户到新加入资产中: {}").format(node.value)
|
||||
push_system_user_util.delay(system_users, assets, task_name)
|
||||
|
||||
@shared_task
|
||||
def push_system_user_to_assets(system_user, assets):
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
# @shared_task
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
|
||||
<div class="checkbox checkbox-success">
|
||||
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
|
||||
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-MFA' %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,132 +1,131 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block modal_class %}modal-lg{% endblock %}
|
||||
{% block modal_id %}asset_list_modal{% endblock %}
|
||||
{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#}
|
||||
{% block modal_title%}{% trans "Asset list" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
{#<div class="btn-group" style="float: right">#}
|
||||
{# <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#}
|
||||
{# <ul class="dropdown-menu labels">#}
|
||||
{# {% for label in labels %}#}
|
||||
{# <li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>#}
|
||||
{# {% endfor %}#}
|
||||
{# </ul>#}
|
||||
{#</div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_modal_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style>
|
||||
.inmodal .modal-header {
|
||||
padding: 10px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#assetTree2.ztree * {
|
||||
background-color: #f8fafb;
|
||||
}
|
||||
#assetTree2.ztree {
|
||||
background-color: #f8fafb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree2" class="ztree">
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="mail-box-header">
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
var modal_table;
|
||||
|
||||
function initModalTable() {
|
||||
var zTree2, asset_table2 = 0;
|
||||
function initTable2() {
|
||||
var options = {
|
||||
ele: $('#asset_modal_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.hardware_info)
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData === 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
ele: $('#asset_list_modal_table'),
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
pageLength: 10
|
||||
};
|
||||
modal_table = jumpserver.initServerSideDataTable(options);
|
||||
return modal_table;
|
||||
asset_table2 = jumpserver.initServerSideDataTable(options);
|
||||
return asset_table2
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initModalTable();
|
||||
}).on('click', '#btn_select_assets', function () {
|
||||
var data_table = $('#asset_modal_table').DataTable();
|
||||
var id_list = [];
|
||||
data_table.rows({selected: true}).every(function(){
|
||||
id_list.push(this.data().id);
|
||||
function onSelected2(event, treeNode) {
|
||||
var url = asset_table2.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table2.ajax.url(url);
|
||||
asset_table2.ajax.reload();
|
||||
}
|
||||
|
||||
|
||||
function initTree2() {
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onSelected: onSelected2
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
value["pId"] = value["tree_parent"];
|
||||
{#value["open"] = true;#}
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
|
||||
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
|
||||
var root = zTree2.getNodes()[0];
|
||||
zTree2.expandNode(root);
|
||||
});
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
|
||||
var success = function () {
|
||||
modal_table.ajax.reload()
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
$(document).ready(function(){
|
||||
initTable2();
|
||||
initTree2();
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_select_assets{% endblock %}
|
||||
|
||||
{% block modal_button %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
{% endif %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.login_mode layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.priority layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
|
||||
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
<div class="auto-generate">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||
@@ -55,12 +56,16 @@
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||
<label for="{{ form.auto_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_push}}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<div id="command-filter-block">
|
||||
<h3>{% trans 'Command filter' %}</h3>
|
||||
{% bootstrap_field form.cmd_filters layout="horizontal" %}
|
||||
</div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.sudo layout="horizontal" %}
|
||||
{% bootstrap_field form.shell layout="horizontal" %}
|
||||
@@ -79,43 +84,87 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
<script>
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
|
||||
|
||||
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
|
||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
|
||||
function authFieldsDisplay() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
} else {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
var need_change_field = [
|
||||
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
|
||||
];
|
||||
var need_change_field_login_mode = [
|
||||
auto_generate_key, private_key_id, auto_push_id, password_id
|
||||
];
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
}
|
||||
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
}
|
||||
else {
|
||||
if($(login_mode_id).val() === 'manual'){
|
||||
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||
return
|
||||
}
|
||||
authFieldsDisplay();
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').removeClass('hidden')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id).attr('value') === 'rdp') {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).addClass('hidden')
|
||||
});
|
||||
$(password_id).removeClass('hidden')
|
||||
} else {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).removeClass('hidden')
|
||||
});
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
$(auto_generate_key).change(function () {
|
||||
authFieldsDisplay();
|
||||
});
|
||||
function authFieldsDisplay() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
} else {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
function loginModeChange(){
|
||||
if ($(login_mode_id).val() === 'manual'){
|
||||
$('#auth_title_id').addClass('hidden');
|
||||
$.each(need_change_field_login_mode, function(index, value){
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
})
|
||||
</script>
|
||||
}
|
||||
else if($(login_mode_id).val() === 'auto'){
|
||||
$('#auth_title_id').removeClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden')
|
||||
protocolChange();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
loginModeChange();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
protocolChange();
|
||||
})
|
||||
.on('change', auto_generate_key, function(){
|
||||
authFieldsDisplay();
|
||||
})
|
||||
.on('change', login_mode_id, function(){
|
||||
loginModeChange();
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
24
apps/assets/templates/assets/_user_asset_detail_modal.html
Normal file
24
apps/assets/templates/assets/_user_asset_detail_modal.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
<style>
|
||||
.modal-body {
|
||||
background-color: white !important;
|
||||
}
|
||||
</style>
|
||||
{% block modal_id %}user_asset_detail_modal{% endblock %}
|
||||
|
||||
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
|
||||
|
||||
{% block modal_body %}
|
||||
<div class="ibox-content" style="background-color: inherit">
|
||||
<table class="table">
|
||||
<tbody id="asset_detail_tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_button %}
|
||||
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
|
||||
{% endblock %}
|
||||
@@ -124,7 +124,7 @@ $(document).ready(function () {
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option>
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
||||
Windows或其它硬件可以随意设置一个
|
||||
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
|
||||
{# Windows或其它硬件可以随意设置一个#}
|
||||
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
|
||||
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||
{% trans 'You can set any one for Windows or other hardware.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -90,7 +93,7 @@ $(document).ready(function(){
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options)
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
@@ -107,6 +110,3 @@ $(document).ready(function(){
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
{% block form %}
|
||||
<div class="ydxbd" id="formlists" style="display: block;">
|
||||
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p>
|
||||
<p id="tags_p" class="mgl-5 c02">{% trans 'Select properties that need to be modified' %}</p>
|
||||
<div class="tagBtnList">
|
||||
<a class="label label-primary" id="change_all" value="1">全选</a>
|
||||
<a class="label label-primary" id="change_all" value="1">{% trans 'Select all' %}</a>
|
||||
{% for field in form %}
|
||||
{% if field.name != 'assets' %}
|
||||
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
@@ -34,7 +35,7 @@
|
||||
<div class="form-group {% if form.errors.labels %} has-error {% endif %}">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
@@ -85,6 +86,20 @@ $(document).ready(function () {
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
$('#id_nodes.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
$("#id_protocol").change(function (){
|
||||
var protocol = $("#id_protocol option:selected").text();
|
||||
var port = 22;
|
||||
if(protocol === 'rdp'){
|
||||
port = 3389;
|
||||
}
|
||||
if(protocol === 'telnet (beta)'){
|
||||
port = 23;
|
||||
}
|
||||
$("#id_port").val(port);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -69,10 +69,18 @@
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
<td><b>{{ asset.port }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ asset.protocol }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
<td><b>{{ asset.admin_user }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Domain' %}:</td>
|
||||
<td><b>{{ asset.domain|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Vendor' %}:</td>
|
||||
<td><b>{{ asset.vendor|default:"" }}</b></td>
|
||||
@@ -130,7 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if user.is_superuser %}
|
||||
{% if user.is_superuser or user.is_org_admin %}
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
@@ -155,7 +163,7 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if asset.is_unixlike %}
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
<tr>
|
||||
<td>{% trans 'Refresh hardware' %}:</td>
|
||||
<td>
|
||||
@@ -190,7 +198,7 @@
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
@@ -204,7 +212,7 @@
|
||||
|
||||
{% for node in asset.nodes.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
@@ -305,9 +313,9 @@ $(document).ready(function () {
|
||||
success_message: success
|
||||
});
|
||||
if (status === "False") {
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
||||
}else{
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
||||
}
|
||||
}).on('click', '#btn-update-nodes', function () {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||
{# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
|
||||
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
@@ -17,20 +19,25 @@
|
||||
position:absolute;
|
||||
visibility:hidden;
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
{#top: 100%;#}
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
padding: 5px 0;
|
||||
{#float: left;#}
|
||||
padding: 0 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
{#list-style: none outside none;#}
|
||||
}
|
||||
list-style: none outside none;
|
||||
}
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
@@ -41,13 +48,12 @@
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,57 +65,57 @@
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header">
|
||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="mail-box-header">
|
||||
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="remove">{% trans 'Remove from this node' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,15 +123,19 @@
|
||||
|
||||
<div id="rMenu">
|
||||
<ul class="dropdown-menu">
|
||||
<li id="menu_asset_create" class="btn-create-asset" tabindex="-1"><a>{% trans 'Create asset' %}</a></li>
|
||||
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a>{% trans 'Add asset' %}</a></li>
|
||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a>{% trans 'Refresh node hardware info' %}</a></li>
|
||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a>{% trans 'Test node connective' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a>{% trans 'Add node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a>{% trans 'Rename node' %}</a></li>
|
||||
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>{% trans 'Delete node' %}</a></li>
|
||||
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-copy"></i> {% trans 'Add assets to node' %}</a></li>
|
||||
<li id="menu_asset_move" class="btn-move-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-cut"></i> {% trans 'Move assets to node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -136,6 +146,7 @@
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var zTree, rMenu, asset_table, show = 0;
|
||||
var update_node_action = "";
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
@@ -155,16 +166,8 @@ function initTable() {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData === 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
@@ -174,7 +177,7 @@ function initTable() {
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
||||
{data: "id", orderable: false }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
@@ -182,23 +185,25 @@ function initTable() {
|
||||
return asset_table
|
||||
}
|
||||
|
||||
|
||||
function addTreeNode() {
|
||||
hideRMenu();
|
||||
var parentNode = zTree.getSelectedNodes()[0];
|
||||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
id: data["key"],
|
||||
name: data["value"],
|
||||
id: data["id"],
|
||||
node_id: data["id"],
|
||||
pId: parentNode.id
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
}
|
||||
@@ -211,11 +216,12 @@ function removeTreeNode() {
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
alert("{% trans 'Have child node, cancel' %}")
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
|
||||
toastr.error("{% trans 'Have child node, cancel' %}");
|
||||
} else if (current_node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id );
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
@@ -228,9 +234,9 @@ function removeTreeNode() {
|
||||
|
||||
function editTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.value) {
|
||||
current_node.name = current_node.value;
|
||||
@@ -238,7 +244,6 @@ function editTreeNode() {
|
||||
zTree.editName(current_node);
|
||||
}
|
||||
|
||||
|
||||
function OnRightClick(event, treeId, treeNode) {
|
||||
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
|
||||
zTree.cancelSelectedNode();
|
||||
@@ -251,14 +256,9 @@ function OnRightClick(event, treeId, treeNode) {
|
||||
|
||||
function showRMenu(type, x, y) {
|
||||
$("#rMenu ul").show();
|
||||
{#if (type === "root") {#}
|
||||
{# return#}
|
||||
{# } else {#}
|
||||
{# $("#m_del").show();#}
|
||||
{# $("#m_check").show();#}
|
||||
{# $("#m_unCheck").show();#}
|
||||
{# }#}
|
||||
x -= 220;
|
||||
x += document.body.scrollLeft;
|
||||
y += document.body.scrollTop+document.documentElement.scrollTop;
|
||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||
|
||||
$("body").bind("mousedown", onBodyMouseDown);
|
||||
@@ -281,7 +281,7 @@ function onBodyMouseDown(event){
|
||||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
@@ -289,14 +289,17 @@ function onRename(event, treeId, treeNode, isCancel){
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: "PATCH"
|
||||
method: "PATCH",
|
||||
success_message: "{% trans 'Rename success' %}",
|
||||
fail_message: "{% trans 'Rename failed, do not change the root node name' %}"
|
||||
})
|
||||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
@@ -313,7 +316,7 @@ function selectQueryNode() {
|
||||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("id", node_id, null);
|
||||
node = zTree.getNodesByParam("node_id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
@@ -330,11 +333,7 @@ function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
|
||||
if (confirm(msg)){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return confirm(msg);
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
@@ -343,10 +342,10 @@ function onDrag(event, treeId, treeNodes) {
|
||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.id);
|
||||
treeNodesIds.push(value.node_id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
@@ -390,16 +389,21 @@ function initTree() {
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
{#if (value["key"] === "0") {#}
|
||||
value["open"] = true;
|
||||
{# }#}
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]){
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
});
|
||||
@@ -423,6 +427,13 @@ function toggle() {
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
initTree();
|
||||
|
||||
if(getCookie('show_current_asset') === '1'){
|
||||
$('#show_all_asset').css('display', 'inline-block');
|
||||
}
|
||||
else{
|
||||
$('#show_current_asset').css('display', 'inline-block');
|
||||
}
|
||||
})
|
||||
.on('click', '.labels li', function () {
|
||||
var val = $(this).text();
|
||||
@@ -432,14 +443,20 @@ $(document).ready(function(){
|
||||
.on('click', '.btn_export', function () {
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var rows = $data_table.rows('.selected').data();
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0];
|
||||
}
|
||||
var assets = [];
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
var _node_id = current_node ? current_node.node_id : null;
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets}),
|
||||
data: JSON.stringify({assets_id: assets, node_id: _node_id}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
@@ -456,7 +473,8 @@ $(document).ready(function(){
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
action += "?node_id=" + current_node.id;
|
||||
action = setUrlParam(action, 'node_id', current_node.node_id);
|
||||
{#action += "?node_id=" + current_node.node_id;#}
|
||||
$form.attr("action", action)
|
||||
}
|
||||
$form.find('.help-block').remove();
|
||||
@@ -482,7 +500,7 @@ $(document).ready(function(){
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
url += "?node_id=" + current_node.node_id;
|
||||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
@@ -496,7 +514,7 @@ $(document).ready(function(){
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
@@ -521,7 +539,7 @@ $(document).ready(function(){
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
@@ -535,6 +553,20 @@ $(document).ready(function(){
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-show-current-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_all_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '1');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn-show-all-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_current_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
@@ -596,6 +628,7 @@ $(document).ready(function(){
|
||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "{% trans 'Cancel' %}",
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
@@ -644,7 +677,7 @@ $(document).ready(function(){
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
|
||||
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
@@ -670,7 +703,43 @@ $(document).ready(function(){
|
||||
break;
|
||||
}
|
||||
$(".ipt_check_all").prop("checked", false)
|
||||
});
|
||||
})
|
||||
.on('click', '#btn_asset_modal_confirm', function () {
|
||||
var assets_selected = asset_table2.selected;
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
var data = {'assets': assets_selected};
|
||||
var success = function () {
|
||||
asset_table2.selected = [];
|
||||
asset_table2.ajax.reload()
|
||||
};
|
||||
|
||||
var url = '';
|
||||
if (update_node_action === "move") {
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
} else {
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': url,
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
})
|
||||
}).on('hidden.bs.modal', '#asset_list_modal', function () {
|
||||
window.location.reload();
|
||||
}).on('click', '#menu_asset_add', function () {
|
||||
update_node_action = "add"
|
||||
}).on('click', '#menu_asset_move', function () {
|
||||
update_node_action = "move"
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.no_field_errors %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
@@ -21,6 +21,7 @@
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
@@ -39,7 +40,7 @@
|
||||
<div class="form-group">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
|
||||
20
apps/assets/templates/assets/cmd_filter_create_update.html
Normal file
20
apps/assets/templates/assets/cmd_filter_create_update.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
168
apps/assets/templates/assets/cmd_filter_detail.html
Normal file
168
apps/assets/templates/assets/cmd_filter_detail.html
Normal file
@@ -0,0 +1,168 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li>
|
||||
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Rules' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:cmd-filter-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ object.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Name' %}:</td>
|
||||
<td><b>{{ object.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ object.comment }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ object.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date updated' %}:</td>
|
||||
<td><b>{{ object.date_updated }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ object.created_by }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'System users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="table-clusters">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Binding to system user' %}" id="system_users_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for system_user in system_users_remain %}
|
||||
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn-binding-system-users">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for system_user in object.system_users.all %}
|
||||
<tr>
|
||||
<td><b class="bdg-system-users" data-gid={{ system_user.id }}>{{ system_user }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-unbound-system-user" data-gid={{ system_user.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function updateCMDFilterSystemUsers(system_users) {
|
||||
var the_url = "{% url 'api-assets:cmd-filter-detail' pk=object.id %}";
|
||||
var body = {
|
||||
system_users: Object.assign([], system_users)
|
||||
};
|
||||
var success = function(data) {
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$(".select2").select2();
|
||||
}).on('click', '#btn-binding-system-users', function () {
|
||||
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var new_selected_system_users_id = $.map($("#system_users_selected").select2('data'), function (s) {
|
||||
return s.id;
|
||||
});
|
||||
var system_users = origin_system_users.concat(new_selected_system_users_id);
|
||||
updateCMDFilterSystemUsers(system_users)
|
||||
}).on('click', '.btn-unbound-system-user', function () {
|
||||
var unbound_system_user = $(this).data('gid');
|
||||
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var system_users = $.grep(origin_system_users, function (n, i) {
|
||||
return n !== unbound_system_user
|
||||
});
|
||||
updateCMDFilterSystemUsers(system_users)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
89
apps/assets/templates/assets/cmd_filter_list.html
Normal file
89
apps/assets/templates/assets/cmd_filter_list.html
Normal file
@@ -0,0 +1,89 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{% trans 'System user bound some command filter, each command filter has some rules,'%}
|
||||
{% trans 'When user login asset with this system user, then run a command,' %}
|
||||
{% trans 'The command will be filter by rules, higher priority rule run first,' %}
|
||||
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
|
||||
{% trans 'else if action is deny, then command with be deny,' %}
|
||||
{% trans 'else match next rule, if none matched, allowed' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:cmd-filter-create' %}" class="btn btn-sm btn-primary"> {% trans "Create command filter" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cmd_filter_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Rules' %}</th>
|
||||
<th class="text-center">{% trans 'System users' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#cmd_filter_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var filters_list_btn = '<a href="{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
|
||||
filters_list_btn = filters_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
|
||||
$(td).html(filters_list_btn);
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var system_users_list_btn = '<a href="{% url "assets:cmd-filter-detail" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
|
||||
system_users_list_btn = system_users_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
|
||||
$(td).html(system_users_list_btn);
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:cmd-filter-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "name" }, {data: "rules" },
|
||||
{data: "system_users" }, {data: "comment"}, {data: "id"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cmd_filter_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:cmd-filter-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var content_origin_placeholder = '';
|
||||
var content_origin_help_text = '';
|
||||
var content_ref = '';
|
||||
var content_help_ref = '';
|
||||
|
||||
$(document).ready(function(){
|
||||
content_ref = $('#id_content');
|
||||
content_help_ref = content_ref.next();
|
||||
content_origin_placeholder = content_ref.attr('placeholder');
|
||||
content_origin_help_text = content_help_ref.html();
|
||||
}).on('change', '#id_type', function () {
|
||||
if ($('#id_type :selected').val() === 'regex') {
|
||||
content_ref.attr('placeholder', 'rm.*|reboot|shutdown');
|
||||
content_help_ref.html("");
|
||||
} else {
|
||||
content_ref.attr('placeholder', content_origin_placeholder);
|
||||
content_help_ref.html(content_origin_help_text);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
115
apps/assets/templates/assets/cmd_filter_rule_list.html
Normal file
115
apps/assets/templates/assets/cmd_filter_rule_list.html
Normal file
@@ -0,0 +1,115 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Rules' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left: 0;">
|
||||
<div class="" id="content_start">
|
||||
</div>
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left"><b>{% trans 'Command filter rule list' %}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:cmd-filter-rule-create' filter_pk=object.id %}" class="btn btn-sm btn-primary"> {% trans "Create rule" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cmd_filter_rule_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Content' %}</th>
|
||||
<th class="text-center">{% trans 'Priority' %}</th>
|
||||
<th class="text-center">{% trans 'Strategy' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#cmd_filter_rule_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:cmd-filter-rule-update" filter_pk=object.id pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:cmd-filter-rule-list" filter_pk=object.id %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "type.display" }, {data: 'content'}, {data: 'priority'},
|
||||
{data: 'action.display'}, {data: "comment" }, {data: "id"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cmd_filter_rule_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -18,14 +18,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
});
|
||||
$(document).ready(function () {
|
||||
console.log($.fn.select2.defaults);
|
||||
$('.select2').select2().off("select2:open");
|
||||
}).on('click', '.select2-selection__rendered', function (e) {
|
||||
e.preventDefault();
|
||||
$("#asset_list_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_modal_confirm', function () {
|
||||
var assets = asset_table2.selected;
|
||||
$('.select2').val(assets).trigger('change');
|
||||
$("#asset_list_modal").modal('hide');
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -81,15 +81,13 @@ function initTable() {
|
||||
var options = {
|
||||
ele: $('#domain_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
if(rowData.protocol === 'rdp'){
|
||||
test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" disabled data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
}
|
||||
$(td).html(update_btn + test_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
@@ -100,7 +98,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
@@ -122,10 +120,9 @@ $(document).ready(function(){
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success_message: "可连接",
|
||||
fail_message: "连接失败"
|
||||
success_message: "{% trans 'Can be connected' %}",
|
||||
fail_message: "{% trans 'The connection fails' %}"
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{# 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>#}
|
||||
{# JMS => 网域网关 => 目标资产#}
|
||||
{% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %}
|
||||
<br>
|
||||
{% trans 'JMS => Domain gateway => Target assets' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
|
||||
@@ -51,7 +62,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
@@ -69,6 +80,3 @@ $(document).ready(function(){
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
<h3 id="auth_title">{% trans 'Auth' %}</h3>
|
||||
<div class="auth-fields">
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
@@ -66,3 +66,37 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var port = '#' + '{{ form.port.id_for_label }}';
|
||||
var username = '#' + '{{ form.username.id_for_label }}';
|
||||
var password = '#' + '{{ form.password.id_for_label }}';
|
||||
var auth_title = '#auth_title';
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||
{#$(port).val(3389);#}
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(username).closest('.form-group').addClass('hidden');
|
||||
$(password).closest('.form-group').addClass('hidden');
|
||||
$(auth_title).addClass('hidden');
|
||||
} else {
|
||||
{#$(port).val(22);#}
|
||||
$(private_key_id).closest('.form-group').removeClass('hidden');
|
||||
$(username).closest('.form-group').removeClass('hidden');
|
||||
$(password).closest('.form-group').removeClass('hidden');
|
||||
$(auth_title).removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
protocolChange();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
protocolChange();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -3,6 +3,8 @@
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
|
||||
{% block form %}
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
@@ -18,14 +20,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
});
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: false
|
||||
})
|
||||
}).on('click', '.select2-selection__rendered', function (e) {
|
||||
e.preventDefault();
|
||||
$("#asset_list_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_modal_confirm', function () {
|
||||
var assets = asset_table2.selected;
|
||||
$('#id_assets').val(assets).trigger('change');
|
||||
$("#asset_list_modal").modal('hide');
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -47,7 +47,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -50,6 +51,7 @@
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -67,14 +69,6 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
@@ -82,6 +76,52 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if system_user.auto_push %}
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table node_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for node in system_user.nodes.all|sort %}
|
||||
<tr>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -96,7 +136,7 @@
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetsTable() {
|
||||
var unreachable = {{ system_user.unreachable_assets|safe}};
|
||||
var unreachable = {{ system_user.unreachable_assets|safe }};
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
@@ -112,27 +152,64 @@ function initAssetsTable() {
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var push_btn = '';
|
||||
{% if system_user.auto_push %}
|
||||
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{% endif %}
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
||||
$(td).html(push_btn + test_btn);
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
|
||||
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function updateSystemUserNode(nodes) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
var body = {
|
||||
nodes: Object.assign([], nodes)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#node_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.node_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.nodes_selected
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
jumpserver.asset_groups_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
delete jumpserver.asset_groups_selected[data.id];
|
||||
});
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
initAssetsTable();
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
@@ -140,11 +217,16 @@ $(document).ready(function () {
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}"
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
@@ -152,15 +234,85 @@ $(document).ready(function () {
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, seen left assets status" %}"
|
||||
success: success
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var node_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var nodes = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '#btn-add-to-node', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-push-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">#}
|
||||
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
@@ -63,15 +63,19 @@
|
||||
<td><b>{{ system_user.username }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ system_user.protocol }}</b></td>
|
||||
<td>{% trans 'Login mode' %}:</td>
|
||||
<td><b>{{ system_user.get_login_mode_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
|
||||
</tr>
|
||||
<tr class="only-ssh">
|
||||
<td>{% trans 'Sudo' %}:</td>
|
||||
<td><b>{{ system_user.sudo }}</b></td>
|
||||
</tr>
|
||||
{% if system_user.shell %}
|
||||
<tr>
|
||||
<tr class="only-ssh">
|
||||
<td>{% trans 'Shell' %}:</td>
|
||||
<td><b>{{ system_user.shell }}</b></td>
|
||||
</tr>
|
||||
@@ -107,14 +111,14 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel panel-primary ">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
@@ -130,8 +134,8 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
{% if system_user.auto_push %}
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
@@ -139,8 +143,8 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
{% endif %}
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
@@ -148,57 +152,51 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{# <tr>#}
|
||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||
{# <td>#}
|
||||
{# <span style="float: right">#}
|
||||
{# <button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>#}
|
||||
{# </span>#}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table node_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for node in system_user.nodes.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if system_user.protocol != 'rdp' %}
|
||||
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Command filter' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Binding command filters' %}" id="command_filters_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for cf in cmd_filters_remain %}
|
||||
<option value="{{ cf.id }}" id="opt_{{ cf.id }}" >{{ cf }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-binding-command-filters">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for cf in object.cmd_filters.all %}
|
||||
<tr>
|
||||
<td><b class="bdg-command-filters" data-gid={{ cf.id }}>{{ cf }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-unbound-command-filter" data-gid={{ cf.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,27 +204,13 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function updateSystemUserNode(nodes) {
|
||||
function updateCommandFilters(command_filters) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
var body = {
|
||||
nodes: Object.assign([], nodes)
|
||||
cmd_filters: Object.assign([], command_filters)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#node_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.node_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.nodes_selected = {};
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
@@ -234,17 +218,12 @@ function updateSystemUserNode(nodes) {
|
||||
success: success
|
||||
});
|
||||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
if($('#id_protocol_type').text() === 'rdp'){
|
||||
$('.only-ssh').addClass('hidden')
|
||||
}
|
||||
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-auto-push', function () {
|
||||
var checked = $(this).prop('checked');
|
||||
@@ -256,33 +235,6 @@ $(document).ready(function () {
|
||||
url: the_url,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-add-to-node', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var node_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var nodes = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserNode(nodes);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ system_user.name}}";
|
||||
@@ -296,7 +248,7 @@ $(document).ready(function () {
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
@@ -318,6 +270,24 @@ $(document).ready(function () {
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
}).on('click', '#btn-binding-command-filters', function () {
|
||||
var new_selected_cmd_filters = $.map($('#command_filters_selected').select2('data'), function (i) {
|
||||
return i.id;
|
||||
});
|
||||
var origin_cmd_filters = $.map($(".bdg-command-filters"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var command_filters = new_selected_cmd_filters.concat(origin_cmd_filters);
|
||||
updateCommandFilters(command_filters)
|
||||
}).on('click', '.btn-unbound-command-filter', function () {
|
||||
var unbound_command_filter = $(this).data('gid');
|
||||
var origin_command_filters = $.map($(".bdg-command-filters"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var command_filters = $.grep(origin_command_filters, function (n, i) {
|
||||
return n !== unbound_command_filter
|
||||
});
|
||||
updateCommandFilters(command_filters)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);
|
||||
简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。
|
||||
系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。
|
||||
目前还不支持Windows的自动推送
|
||||
{# 系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
|
||||
{# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
|
||||
{# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
|
||||
{# 目前还不支持Windows的自动推送#}
|
||||
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
|
||||
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
|
||||
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -26,6 +29,7 @@
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Protocol' %}</th>
|
||||
<th class="text-center">{% trans 'Login mode' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Unreachable' %}</th>
|
||||
@@ -48,7 +52,7 @@ function initTable() {
|
||||
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
||||
@@ -57,7 +61,7 @@ function initTable() {
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
||||
@@ -66,7 +70,7 @@ function initTable() {
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
var val = 0;
|
||||
var innerHtml = "";
|
||||
var total = rowData.assets_amount;
|
||||
@@ -84,19 +88,19 @@ function initTable() {
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 10, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" },
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
@@ -134,6 +138,7 @@ $(document).ready(function(){
|
||||
text: "{% trans 'This will delete the selected System Users !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "{% trans 'Cancel' %}",
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
@@ -15,10 +14,3 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,81 +1,192 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Connective' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header">
|
||||
<div class="btn-group" style="float: right">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu labels">
|
||||
{% for label in labels %}
|
||||
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="user_assets_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'System users' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_user_asset_detail_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
<script>
|
||||
var zTree, asset_table, show=0;
|
||||
var inited = false;
|
||||
var url;
|
||||
function initTable() {
|
||||
if (inited){
|
||||
return
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
url = "{% url 'api-perms:my-assets' %}";
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
ele: $('#user_assets_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
|
||||
$(td).html(detail_btn.replace("rowData_id", rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (cellData == 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{# {targets: 9, createdCell: function (td, cellData, rowData) {#}
|
||||
{# var conn_btn = '<a href="{% url "terminal:web-terminal" %}?id={{ DEFAULT_PK }}" class="btn btn-xs btn-info">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);#}
|
||||
{# $(td).html(conn_btn)#}
|
||||
{# }}#}
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var users = [];
|
||||
$.each(cellData, function (id, data) {
|
||||
users.push(data.name);
|
||||
});
|
||||
$(td).html(users.join(', '))
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:user-asset-list" %}',
|
||||
ajax_url: url,
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "hardware_info"}, {data: "is_active" }, {data: "is_connective"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "is_active", orderable: false },
|
||||
{data: "system_users_granted", orderable: false}
|
||||
]
|
||||
};
|
||||
return jumpserver.initDataTable(options);
|
||||
asset_table = jumpserver.initServerSideDataTable(options);
|
||||
return asset_table
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
function onSelected(event, treeNode) {
|
||||
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
|
||||
var node_id = treeNode.meta.node.id;
|
||||
url = url.replace("{{ DEFAULT_PK }}", node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onSelected: onSelected
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0", function(data, status){
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initTree();
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.asset_detail', function() {
|
||||
var data = asset_table.ajax.json();
|
||||
var asset_id = this.getAttribute("asset-id");
|
||||
var trs = '';
|
||||
var desc = {
|
||||
'hostname': "{% trans 'Hostname' %}",
|
||||
'ip': "{% trans 'IP' %}",
|
||||
'port': "{% trans 'Port' %}",
|
||||
'protocol': "{% trans 'Protocol' %}",
|
||||
'platform': "{% trans 'Platform' %}",
|
||||
'os': "{% trans 'OS' %}",
|
||||
'system_users_join': "{% trans 'System user' %}",
|
||||
'domain': "{% trans 'Domain' %}",
|
||||
'is_active': "{% trans 'Is active' %}",
|
||||
'comment': "{% trans 'Comment' %}"
|
||||
{#'date_joined': "{% trans 'Date joined' %}",#}
|
||||
};
|
||||
$.each(data, function(index, value){
|
||||
if(value.id === asset_id){
|
||||
for(var i in desc){
|
||||
trs += "<tr class='no-borders-tr'>\n" +
|
||||
"<td>"+ desc[i] + ":</td>"+
|
||||
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
|
||||
"</tr>";
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#asset_detail_tbody').html(trs)
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,49 +1,79 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import api
|
||||
from django.urls import path
|
||||
from rest_framework_nested import routers
|
||||
# from rest_framework.routers import DefaultRouter
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'v1/labels', api.LabelViewSet, 'label')
|
||||
router.register(r'v1/nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'v1/domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'v1/gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'labels', api.LabelViewSet, 'label')
|
||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
||||
name='system-user-auth-info'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/refresh/$',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
url(r'^v1/assets/user-assets/$',
|
||||
api.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
path('system-user/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-user/<uuid:pk>/assets/',
|
||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('assets/<uuid:pk>/refresh/',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
path('assets/<uuid:pk>/alive/',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/gateway/',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
path('admin-user/<uuid:pk>/nodes/',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-user/<uuid:pk>/auth/',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-user/<uuid:pk>/connective/',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
|
||||
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
path('system-user/<uuid:pk>/push/',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
|
||||
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
|
||||
api.SystemUserTestAssetConnectabilityApi.as_view(), name='system-user-test-to-asset'),
|
||||
path('system-user/<uuid:pk>/connective/',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
|
||||
path('system-user/<uuid:pk>/cmd-filter-rules/',
|
||||
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
|
||||
path('nodes/<uuid:pk>/children/',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
path('nodes/<uuid:pk>/children/add/',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
path('nodes/<uuid:pk>/assets/',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
path('nodes/<uuid:pk>/assets/add/',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
path('nodes/<uuid:pk>/assets/replace/',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
path('nodes/<uuid:pk>/assets/remove/',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
path('nodes/<uuid:pk>/refresh-hardware-info/',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
path('nodes/<uuid:pk>/test-connective/',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
|
||||
path('gateway/<uuid:pk>/test-connective/',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
urlpatterns += router.urls + cmd_filter_router.urls
|
||||
|
||||
|
||||
@@ -1,53 +1,60 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
|
||||
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
|
||||
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
|
||||
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
path('', views.AssetListView.as_view(), name='asset-index'),
|
||||
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
||||
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
|
||||
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
|
||||
# User asset view
|
||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
path('admin-user/<uuid:pk>/', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
path('admin-user/<uuid:pk>/update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
path('admin-user/<uuid:pk>/delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
path('admin-user/<uuid:pk>/assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
path('system-user/<uuid:pk>/', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
path('system-user/<uuid:pk>/update/', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
path('system-user/<uuid:pk>/delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
path('system-user/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
|
||||
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
path('label/', views.LabelListView.as_view(), name='label-list'),
|
||||
path('label/create/', views.LabelCreateView.as_view(), name='label-create'),
|
||||
path('label/<uuid:pk>/update/', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
path('label/<uuid:pk>/delete/', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
|
||||
url(r'^domain/$', views.DomainListView.as_view(), name='domain-list'),
|
||||
url(r'^domain/create/$', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
path('domain/', views.DomainListView.as_view(), name='domain-list'),
|
||||
path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
path('domain/<uuid:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
path('domain/<uuid:pk>/update/', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
path('domain/<uuid:pk>/delete/', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
path('domain/<uuid:pk>/gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
url(r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
|
||||
path('cmd-filter/', views.CommandFilterListView.as_view(), name='cmd-filter-list'),
|
||||
path('cmd-filter/create/', views.CommandFilterCreateView.as_view(), name='cmd-filter-create'),
|
||||
path('cmd-filter/<uuid:pk>/update/', views.CommandFilterUpdateView.as_view(), name='cmd-filter-update'),
|
||||
path('cmd-filter/<uuid:pk>/', views.CommandFilterDetailView.as_view(), name='cmd-filter-detail'),
|
||||
path('cmd-filter/<uuid:pk>/rule/', views.CommandFilterRuleListView.as_view(), name='cmd-filter-rule-list'),
|
||||
path('cmd-filter/<uuid:filter_pk>/rule/create/', views.CommandFilterRuleCreateView.as_view(), name='cmd-filter-rule-create'),
|
||||
path('cmd-filter/<uuid:filter_pk>/rule/<uuid:pk>/update/', views.CommandFilterRuleUpdateView.as_view(), name='cmd-filter-rule-update'),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
import os
|
||||
import paramiko
|
||||
from paramiko.ssh_exception import SSHException
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from .models import Asset, SystemUser, Label
|
||||
|
||||
|
||||
def get_assets_by_id_list(id_list):
|
||||
return Asset.objects.filter(id__in=id_list)
|
||||
return Asset.objects.filter(id__in=id_list).filter(is_active=True)
|
||||
|
||||
|
||||
def get_assets_by_hostname_list(hostname_list):
|
||||
return Asset.objects.filter(hostname__in=hostname_list)
|
||||
def get_system_users_by_id_list(id_list):
|
||||
return SystemUser.objects.filter(id__in=id_list)
|
||||
|
||||
|
||||
def get_assets_by_fullname_list(hostname_list):
|
||||
return Asset.get_queryset_by_fullname_list(hostname_list)
|
||||
|
||||
|
||||
def get_system_user_by_name(name):
|
||||
@@ -20,6 +25,11 @@ def get_system_user_by_name(name):
|
||||
return system_user
|
||||
|
||||
|
||||
def get_system_user_by_id(id):
|
||||
system_user = get_object_or_none(SystemUser, id=id)
|
||||
return system_user
|
||||
|
||||
|
||||
class LabelFilter:
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
@@ -39,43 +49,3 @@ class LabelFilter:
|
||||
for kwargs in conditions:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
|
||||
def test_gateway_connectability(gateway):
|
||||
"""
|
||||
Test system cant connect his assets or not.
|
||||
:param gateway:
|
||||
:return:
|
||||
"""
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
proxy_command = [
|
||||
"ssh", "{}@{}".format(gateway.username, gateway.ip),
|
||||
"-p", str(gateway.port), "-W", "127.0.0.1:{}".format(gateway.port),
|
||||
]
|
||||
|
||||
if gateway.password:
|
||||
proxy_command.insert(0, "sshpass -p '{}'".format(gateway.password))
|
||||
if gateway.private_key:
|
||||
proxy_command.append("-i {}".format(gateway.private_key_file))
|
||||
|
||||
try:
|
||||
sock = paramiko.ProxyCommand(" ".join(proxy_command))
|
||||
except paramiko.ProxyCommandFailure as e:
|
||||
return False, str(e)
|
||||
|
||||
try:
|
||||
client.connect("127.0.0.1", port=gateway.port,
|
||||
username=gateway.username,
|
||||
password=gateway.password,
|
||||
key_filename=gateway.private_key_file,
|
||||
sock=sock,
|
||||
timeout=5
|
||||
)
|
||||
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
|
||||
paramiko.AuthenticationException, TimeoutError) as e:
|
||||
return False, str(e)
|
||||
finally:
|
||||
client.close()
|
||||
return True, None
|
||||
|
||||
@@ -4,3 +4,4 @@ from .system_user import *
|
||||
from .admin_user import *
|
||||
from .label import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import AdminUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
__all__ = [
|
||||
'AdminUserCreateView', 'AdminUserDetailView',
|
||||
|
||||
@@ -8,8 +8,8 @@ import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
@@ -25,11 +25,12 @@ from django.shortcuts import redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger, is_uuid
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -49,6 +50,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset list'),
|
||||
'labels': Label.objects.all().order_by('name'),
|
||||
'nodes': Node.objects.all().order_by('-key'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -72,14 +74,6 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
# def form_valid(self, form):
|
||||
# print("form valid")
|
||||
# asset = form.save()
|
||||
# asset.created_by = self.request.user.username or 'Admin'
|
||||
# asset.date_created = timezone.now()
|
||||
# asset.save()
|
||||
# return super().form_valid(form)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
node_id = self.request.GET.get("node_id")
|
||||
@@ -102,29 +96,12 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
# class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
# paginate_by = settings.DISPLAY_PER_PAGE
|
||||
# model = Asset
|
||||
# context_object_name = 'asset_modal_list'
|
||||
# template_name = 'assets/_asset_list_modal.html'
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# assets = Asset.objects.all()
|
||||
# assets_id = self.request.GET.get('assets_id', '')
|
||||
# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||
# context = {
|
||||
# 'all_assets': assets_id_list,
|
||||
# 'assets': assets
|
||||
# }
|
||||
# kwargs.update(context)
|
||||
# return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
model = Asset
|
||||
form_class = forms.AssetBulkUpdateForm
|
||||
template_name = 'assets/asset_bulk_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
success_message = _("Bulk update asset success")
|
||||
id_list = None
|
||||
form = None
|
||||
|
||||
@@ -146,6 +123,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, self.success_message)
|
||||
return redirect(self.success_url)
|
||||
else:
|
||||
return self.get(request, form=form, *args, **kwargs)
|
||||
@@ -185,7 +163,7 @@ class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
||||
class AssetDetailView(DetailView):
|
||||
class AssetDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
@@ -202,7 +180,7 @@ class AssetDetailView(DetailView):
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
class AssetExportView(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
||||
@@ -210,7 +188,7 @@ class AssetExportView(View):
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
'date_created', 'org_id'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
@@ -233,8 +211,17 @@ class AssetExportView(View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
node_id = json.loads(request.body).get('node_id', None)
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
|
||||
if not assets_id:
|
||||
print(node_id)
|
||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
||||
assets = node.get_all_assets()
|
||||
for asset in assets:
|
||||
assets_id.append(asset.id)
|
||||
|
||||
spm = uuid.uuid4().hex
|
||||
cache.set(spm, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
|
||||
@@ -276,24 +263,30 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
if set(row) == {''}:
|
||||
continue
|
||||
|
||||
asset_dict = dict(zip(attr, row))
|
||||
id_ = asset_dict.pop('id', 0)
|
||||
for k, v in asset_dict.items():
|
||||
asset_dict_raw = dict(zip(attr, row))
|
||||
asset_dict = dict()
|
||||
for k, v in asset_dict_raw.items():
|
||||
v = v.strip()
|
||||
if k == 'is_active':
|
||||
v = True if v in ['TRUE', 1, 'true'] else False
|
||||
v = False if v in ['False', 0, 'false'] else True
|
||||
elif k == 'admin_user':
|
||||
v = get_object_or_none(AdminUser, name=v)
|
||||
elif k in ['port', 'cpu_count', 'cpu_cores']:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
v = 0
|
||||
v = ''
|
||||
elif k == 'domain':
|
||||
v = get_object_or_none(Domain, name=v)
|
||||
asset_dict[k] = v
|
||||
elif k == 'platform':
|
||||
v = v.lower().capitalize()
|
||||
if v != '':
|
||||
asset_dict[k] = v
|
||||
|
||||
asset = get_object_or_none(Asset, id=id_) if is_uuid(id_) else None
|
||||
asset = None
|
||||
asset_id = asset_dict.pop('id', None)
|
||||
if asset_id:
|
||||
asset = get_object_or_none(Asset, id=asset_id)
|
||||
if not asset:
|
||||
try:
|
||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
||||
@@ -308,7 +301,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
else:
|
||||
for k, v in asset_dict.items():
|
||||
if v:
|
||||
if v != '':
|
||||
setattr(asset, k, v)
|
||||
try:
|
||||
asset.save()
|
||||
|
||||
168
apps/assets/views/cmd_filter.py
Normal file
168
apps/assets/views/cmd_filter.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.views.generic import TemplateView, CreateView, \
|
||||
UpdateView, DeleteView, DetailView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
from ..forms import CommandFilterForm, CommandFilterRuleForm
|
||||
|
||||
|
||||
__all__ = (
|
||||
"CommandFilterListView", "CommandFilterCreateView",
|
||||
"CommandFilterUpdateView",
|
||||
"CommandFilterRuleListView", "CommandFilterRuleCreateView",
|
||||
"CommandFilterRuleUpdateView", "CommandFilterDetailView",
|
||||
)
|
||||
|
||||
|
||||
class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create command filter'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update command filter'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
system_users_remain = SystemUser.objects\
|
||||
.exclude(cmd_filters=self.object)\
|
||||
.exclude(protocol='rdp')
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter detail'),
|
||||
'system_users_remain': system_users_remain
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_rule_list.html'
|
||||
model = CommandFilter
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=self.model.objects.all())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter rule list'),
|
||||
'object': self.get_object()
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
'pk': self.cmd_filter.id
|
||||
})
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
form['filter'].initial = self.cmd_filter
|
||||
form['filter'].field.widget.attrs['readonly'] = 1
|
||||
return form
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
filter_pk = self.kwargs.get('filter_pk')
|
||||
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create command filter rule'),
|
||||
'object': self.cmd_filter,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
'pk': self.cmd_filter.id
|
||||
})
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
form['filter'].initial = self.cmd_filter
|
||||
form['filter'].field.widget.attrs['readonly'] = 1
|
||||
return form
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
filter_pk = self.kwargs.get('filter_pk')
|
||||
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update command filter rule'),
|
||||
'object': self.cmd_filter,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import Domain, Gateway
|
||||
@@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
domain = self.object.domain
|
||||
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
print(form.cleaned_data)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import Label
|
||||
from ..forms import LabelForm
|
||||
@@ -36,6 +36,7 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = create_success_msg
|
||||
disable_name = ['draw', 'search', 'limit', 'offset', '_']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
@@ -45,6 +46,16 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
name = form.cleaned_data.get('name')
|
||||
if name in self.disable_name:
|
||||
msg = _(
|
||||
'Tips: Avoid using label names reserved internally: {}'
|
||||
).format(', '.join(self.disable_name))
|
||||
form.add_error("name", msg)
|
||||
return self.form_invalid(form)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Label
|
||||
|
||||
@@ -9,8 +9,8 @@ from django.views.generic.detail import DetailView
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm
|
||||
from ..models import SystemUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from ..models import SystemUser, Node, CommandFilter
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -73,7 +73,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user detail'),
|
||||
'nodes_remain': Node.objects.exclude(systemuser=self.object)
|
||||
'cmd_filters_remain': CommandFilter.objects.exclude(system_users=self.object)
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -91,9 +91,11 @@ class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
|
||||
context_object_name = 'system_user'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('System user asset'),
|
||||
'nodes_remain': nodes_remain
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
0
apps/audits/__init__.py
Normal file
0
apps/audits/__init__.py
Normal file
3
apps/audits/admin.py
Normal file
3
apps/audits/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
14
apps/audits/api.py
Normal file
14
apps/audits/api.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from .models import FTPLog
|
||||
from .serializers import FTPLogSerializer
|
||||
|
||||
|
||||
class FTPLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = FTPLog.objects.all()
|
||||
serializer_class = FTPLogSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user