mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 16:42:34 +00:00
Compare commits
1007 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5185ebd57 | ||
|
|
1ba9351957 | ||
|
|
505b801423 | ||
|
|
35a0b42bea | ||
|
|
4f289963d1 | ||
|
|
1631c32868 | ||
|
|
9721b805f3 | ||
|
|
d2b1b19404 | ||
|
|
49632241b6 | ||
|
|
b4f23f9731 | ||
|
|
1cdcc66dba | ||
|
|
0a1a260a22 | ||
|
|
0e377cd0e9 | ||
|
|
3f3cc6359a | ||
|
|
eb0a6f00b5 | ||
|
|
a87610a8d8 | ||
|
|
e3630a9961 | ||
|
|
6ba78e2cf3 | ||
|
|
63a06b5dd8 | ||
|
|
ec60697912 | ||
|
|
d6a8c04d45 | ||
|
|
d46f5858f8 | ||
|
|
a8491eafea | ||
|
|
0d046d8356 | ||
|
|
9c55450a9e | ||
|
|
00e986a64e | ||
|
|
dc4bf669b0 | ||
|
|
059a8de44a | ||
|
|
aa25b7745c | ||
|
|
1097b11115 | ||
|
|
9f67daeb1e | ||
|
|
2065692199 | ||
|
|
33e342f03f | ||
|
|
7a6027f35a | ||
|
|
e46a6f1d12 | ||
|
|
0f3996369b | ||
|
|
0847539e02 | ||
|
|
f576f2eda2 | ||
|
|
21ac3eaf8b | ||
|
|
b237cbb20f | ||
|
|
9b5b48dd1a | ||
|
|
b7eac837f7 | ||
|
|
217bb81722 | ||
|
|
b791073802 | ||
|
|
45cb39e971 | ||
|
|
2df1dd2bb1 | ||
|
|
4cd3dd3670 | ||
|
|
b18ca8c94f | ||
|
|
75fb37d247 | ||
|
|
f863ed0f4f | ||
|
|
3eaf4cd142 | ||
|
|
7859499c97 | ||
|
|
e8ceb58292 | ||
|
|
84610f2a2c | ||
|
|
d906df5b00 | ||
|
|
22f362aab3 | ||
|
|
4942900886 | ||
|
|
1e505d3d0f | ||
|
|
1eca517978 | ||
|
|
38acce7460 | ||
|
|
3855fecc69 | ||
|
|
466b922ea0 | ||
|
|
9e52579ca6 | ||
|
|
1ca1e519b6 | ||
|
|
54a9070c58 | ||
|
|
e108aae3c0 | ||
|
|
74ae7d138e | ||
|
|
20ce5d11a6 | ||
|
|
cbe919c4b3 | ||
|
|
497cba6b13 | ||
|
|
e2849be72c | ||
|
|
a025930957 | ||
|
|
990c78e7cc | ||
|
|
0ef12906d3 | ||
|
|
61a37731ec | ||
|
|
d3217b6a67 | ||
|
|
04266cc20b | ||
|
|
4f36cf7dd1 | ||
|
|
490041587b | ||
|
|
3a3da94468 | ||
|
|
b7ad6cfe62 | ||
|
|
4463e7545d | ||
|
|
d0eafc8b8e | ||
|
|
8b98c20d68 | ||
|
|
caa5060ecd | ||
|
|
aabcf7f31c | ||
|
|
40d48cdfe4 | ||
|
|
8196537878 | ||
|
|
33a00f043b | ||
|
|
f235e20153 | ||
|
|
cf2455c084 | ||
|
|
fc1068a9dc | ||
|
|
35a0ca1875 | ||
|
|
56519354b6 | ||
|
|
78e4e13fb9 | ||
|
|
699b8d9980 | ||
|
|
ba9581801c | ||
|
|
0a5fdf4ea1 | ||
|
|
3849fa2b15 | ||
|
|
0952cbc7c6 | ||
|
|
bb06c39dd4 | ||
|
|
d60dc31443 | ||
|
|
76b3cd8edd | ||
|
|
638ba31694 | ||
|
|
c31b169cae | ||
|
|
fc167526ae | ||
|
|
55eff5eab9 | ||
|
|
f5a7f4e086 | ||
|
|
f7b0932cdd | ||
|
|
ba89ce8fb9 | ||
|
|
9d62deeabe | ||
|
|
459b41f327 | ||
|
|
3062e3f64a | ||
|
|
c1362ca4e2 | ||
|
|
9d24912ad9 | ||
|
|
db290609a8 | ||
|
|
4bc5eced6c | ||
|
|
b82a66c83d | ||
|
|
bf7079df9e | ||
|
|
f137c5740e | ||
|
|
ee47905966 | ||
|
|
f6cd193f9e | ||
|
|
a31775dd23 | ||
|
|
30ba1e5886 | ||
|
|
f97bfa7bf1 | ||
|
|
ace028fa7f | ||
|
|
69f6401e87 | ||
|
|
bd4d974df1 | ||
|
|
6e7446f530 | ||
|
|
afe9471aa2 | ||
|
|
4d56b84861 | ||
|
|
8fede58c64 | ||
|
|
370904212f | ||
|
|
ae03a5aeb7 | ||
|
|
24a38841dd | ||
|
|
bbc6156bd7 | ||
|
|
f387df41d7 | ||
|
|
ceb8b2f5b3 | ||
|
|
877781a6ca | ||
|
|
612d5efd1b | ||
|
|
7ea03801d0 | ||
|
|
ce8f4b4a48 | ||
|
|
15179d2450 | ||
|
|
9aae106970 | ||
|
|
c82044f6bc | ||
|
|
e4e6f59589 | ||
|
|
c8aa9d006f | ||
|
|
311538dcf8 | ||
|
|
324cf2469f | ||
|
|
01745ead1f | ||
|
|
4e705a52eb | ||
|
|
9bb58afee1 | ||
|
|
b45b33380c | ||
|
|
c86a036ac6 | ||
|
|
8694511d86 | ||
|
|
58c4a46f6e | ||
|
|
dfd26d88d4 | ||
|
|
dcf6959cff | ||
|
|
924affd978 | ||
|
|
ad6d233c11 | ||
|
|
d84ab1d215 | ||
|
|
a5fc04e0ce | ||
|
|
7f71513085 | ||
|
|
6004ef3f0d | ||
|
|
e76392a169 | ||
|
|
53f0b2e9b0 | ||
|
|
de79e36251 | ||
|
|
c84e984eae | ||
|
|
6d65c967b1 | ||
|
|
8199ea84f4 | ||
|
|
ce1b0da09d | ||
|
|
cd6bb848e9 | ||
|
|
34040fcd59 | ||
|
|
1969fb79fe | ||
|
|
904f64604b | ||
|
|
9b3509208d | ||
|
|
6700dc969f | ||
|
|
21714cc411 | ||
|
|
1a247d60e7 | ||
|
|
adf8b1f7aa | ||
|
|
69f640daa4 | ||
|
|
a43314f5be | ||
|
|
599e8a7e37 | ||
|
|
0e00451e1f | ||
|
|
866e5d2011 | ||
|
|
232674b1c1 | ||
|
|
ddf60d2512 | ||
|
|
3e6e0153cf | ||
|
|
6b984aac53 | ||
|
|
6d3ee8116e | ||
|
|
904a0f67dd | ||
|
|
81e1ce2688 | ||
|
|
7c422d2ed6 | ||
|
|
2a5129c481 | ||
|
|
28cdfca14b | ||
|
|
202aba048b | ||
|
|
3c2a4703bc | ||
|
|
4904aac2df | ||
|
|
303f88d6ee | ||
|
|
cccc74279d | ||
|
|
16db2abca5 | ||
|
|
859f2d9795 | ||
|
|
4fd9957bec | ||
|
|
0ac6e6ba2c | ||
|
|
785cc04126 | ||
|
|
f269eae774 | ||
|
|
6f19fcb702 | ||
|
|
b7b6218306 | ||
|
|
5cd809b48a | ||
|
|
5a1b894138 | ||
|
|
4d402617b6 | ||
|
|
666ef366e7 | ||
|
|
28d029a553 | ||
|
|
00763e986a | ||
|
|
f9a7cca478 | ||
|
|
d09b34e232 | ||
|
|
2737675c36 | ||
|
|
7591f40b2c | ||
|
|
c4af6fa72d | ||
|
|
19be7ac580 | ||
|
|
49404f763d | ||
|
|
87f2a67789 | ||
|
|
041edb6177 | ||
|
|
df2fad76c7 | ||
|
|
94020a8fbb | ||
|
|
cb1e19d28f | ||
|
|
0980dffb47 | ||
|
|
4051225ecb | ||
|
|
507518da04 | ||
|
|
d3bdbc0b81 | ||
|
|
3b56027edc | ||
|
|
8285610097 | ||
|
|
6acac9cb3d | ||
|
|
9d78f26807 | ||
|
|
9d53ba22e1 | ||
|
|
0d0cf04543 | ||
|
|
56a47b6ba3 | ||
|
|
1dbcf4e3ab | ||
|
|
95fcd60f64 | ||
|
|
178055eb57 | ||
|
|
01a101a710 | ||
|
|
eee6dd1436 | ||
|
|
27693c6288 | ||
|
|
55b55f6162 | ||
|
|
384cdfbc19 | ||
|
|
c8d007f9d7 | ||
|
|
d8069f47f2 | ||
|
|
d664018bd7 | ||
|
|
0f32e78891 | ||
|
|
6e061d2da5 | ||
|
|
b2717133ee | ||
|
|
2a0e68c58f | ||
|
|
176052e8e9 | ||
|
|
d026b31c9f | ||
|
|
50c1b3ed4a | ||
|
|
131e588d82 | ||
|
|
4bf0dfcf7b | ||
|
|
49a166552e | ||
|
|
0e1d3f93ff | ||
|
|
35403086ab | ||
|
|
2fde6cfe24 | ||
|
|
ed1dc7a984 | ||
|
|
9ab3f0441f | ||
|
|
d6567f0e57 | ||
|
|
84bd465b30 | ||
|
|
d0af8eba32 | ||
|
|
b2a8415f77 | ||
|
|
545d4fa691 | ||
|
|
560df5027a | ||
|
|
e3db7462f7 | ||
|
|
b55d137e7f | ||
|
|
7c10f8743f | ||
|
|
c5d1ed126e | ||
|
|
6b02cdfc37 | ||
|
|
340c615efe | ||
|
|
8e51f97dc7 | ||
|
|
76a08c9039 | ||
|
|
b9b8c35a81 | ||
|
|
e8fba2ec44 | ||
|
|
61df6f55b9 | ||
|
|
ef02b1f83a | ||
|
|
0798e3c466 | ||
|
|
a990098744 | ||
|
|
dab692c0eb | ||
|
|
8e93bfecb0 | ||
|
|
e5953e1932 | ||
|
|
67b21f0489 | ||
|
|
e15c9e6588 | ||
|
|
94d6525548 | ||
|
|
a14d6b298d | ||
|
|
e7725e6910 | ||
|
|
23bf2b0f8e | ||
|
|
ac9178cb93 | ||
|
|
7ff39259af | ||
|
|
b2aef87fdd | ||
|
|
e1f1bed9c9 | ||
|
|
08945f0a19 | ||
|
|
a1b80f5f0b | ||
|
|
7773c30240 | ||
|
|
231c907c64 | ||
|
|
db8882a2b9 | ||
|
|
e5285f312b | ||
|
|
332be54b46 | ||
|
|
fe7c3c29ad | ||
|
|
0e9ebed19d | ||
|
|
4a3327bc4b | ||
|
|
5d47bebb6b | ||
|
|
2ece3545ed | ||
|
|
a9a1bae805 | ||
|
|
f35c02b346 | ||
|
|
32df515f4b | ||
|
|
71750970b2 | ||
|
|
a85099ee60 | ||
|
|
a5b9b4e1d2 | ||
|
|
b3079a4a9b | ||
|
|
9a22874305 | ||
|
|
179018bf67 | ||
|
|
583214e91a | ||
|
|
fb44ef0986 | ||
|
|
90b77fdb08 | ||
|
|
a609f17078 | ||
|
|
068a280350 | ||
|
|
1293d72189 | ||
|
|
164c5ebabd | ||
|
|
b56d73ba9e | ||
|
|
dbdcdb722d | ||
|
|
517a27ea33 | ||
|
|
ab6c88823d | ||
|
|
1ff9f0eaa6 | ||
|
|
b95f8a7d6b | ||
|
|
29ff0efdc1 | ||
|
|
985bd6fc82 | ||
|
|
374039d287 | ||
|
|
c7ac93fcc1 | ||
|
|
ac7e3e7f97 | ||
|
|
4e0b25ae0f | ||
|
|
559f4d2f5c | ||
|
|
f40f6bc61e | ||
|
|
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 | ||
|
|
e9247dd578 | ||
|
|
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 | ||
|
|
2d10e13057 | ||
|
|
1d7ba3e204 | ||
|
|
d966e22cf9 | ||
|
|
6a88fd2d60 | ||
|
|
b63999f385 | ||
|
|
4fd83bd5be | ||
|
|
82d06351e7 | ||
|
|
aa8bece724 | ||
|
|
241bdff7c8 | ||
|
|
168335a381 | ||
|
|
121726b731 | ||
|
|
0b812a03c6 | ||
|
|
79ae6efafb | ||
|
|
d2f108eeec | ||
|
|
c48beb10af | ||
|
|
ea9264ec49 | ||
|
|
5b65ed8a19 | ||
|
|
951ac252fe | ||
|
|
24c4e1df50 | ||
|
|
d247e49b70 | ||
|
|
a4c843ff13 | ||
|
|
ec6103448e | ||
|
|
0ca14463cd | ||
|
|
df80e8047a | ||
|
|
09fc2776df | ||
|
|
e4c2affb5f | ||
|
|
6fae4d5dee | ||
|
|
0c80e3e815 | ||
|
|
d32f070b5c | ||
|
|
b959f1f68b | ||
|
|
e1cab35db0 | ||
|
|
8014cc48b6 | ||
|
|
829f57e2d7 | ||
|
|
5f0b4a4b63 | ||
|
|
c5af4d47eb | ||
|
|
c37bfb682a | ||
|
|
3aaea6cc31 | ||
|
|
f85e5b6f75 | ||
|
|
d598571dc1 | ||
|
|
e873be95d5 | ||
|
|
dbaa4ab502 | ||
|
|
ac1e319cd9 | ||
|
|
a39424ac09 | ||
|
|
75319b99ae | ||
|
|
7f4f67aa8d | ||
|
|
fe1862120f | ||
|
|
759760e7d9 | ||
|
|
15b74da57c | ||
|
|
6f29cf5ddd | ||
|
|
0bba840e4d | ||
|
|
2156e0f51a | ||
|
|
2fc9c04228 | ||
|
|
d8e614c54d | ||
|
|
2ab26e25cc | ||
|
|
f195b309d4 | ||
|
|
5e41c5cadc | ||
|
|
d92d09bd80 | ||
|
|
227804b7ab | ||
|
|
eeae989c06 | ||
|
|
c67a9eb845 | ||
|
|
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]
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,7 +17,7 @@ dump.rdb
|
||||
.idea/
|
||||
db.sqlite3
|
||||
config.py
|
||||
migrations/
|
||||
config.yml
|
||||
*.log
|
||||
host_rsa_key
|
||||
*.bat
|
||||
@@ -32,3 +32,5 @@ django.db
|
||||
celerybeat-schedule.db
|
||||
data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
logs/*
|
||||
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
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 && \
|
||||
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
|
||||
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
|
||||
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \
|
||||
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
|
||||
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
RUN echo > config.yml
|
||||
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"]
|
||||
211
README.md
211
README.md
@@ -1,58 +1,213 @@
|
||||
## Jumpserver
|
||||
## 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/)
|
||||
|
||||
|
||||
----
|
||||
|
||||
Jumpserver是全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的专业运维审计系统。
|
||||
|
||||
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
|
||||
Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发限制。
|
||||
Jumpserver 采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发限制。
|
||||
|
||||
改变世界,从一点点开始。
|
||||
|
||||
----
|
||||
- [English Version](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
|
||||
|
||||
|
||||
### 功能
|
||||
- 统一认证
|
||||
- 资产管理
|
||||
- 统一授权
|
||||
- 审计
|
||||
- 支持LDAP认证
|
||||
- Web terminal
|
||||
- SSH Server
|
||||
- 支持Windows RDP
|
||||
----
|
||||
|
||||
<table class="subscription-level-table">
|
||||
<tr class="subscription-level-tr-border">
|
||||
<th style="background-color: #1ab394;color: #ffffff;" colspan="3">Jumpserver提供的堡垒机必备功能</th>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="4">身份验证 Authentication</td>
|
||||
<td class="features-second-td-background-style" rowspan="3" >登录认证
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资源统一登录和认证
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">LDAP认证
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">支持OpenID,实现单点登录
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">多因子认证
|
||||
</td>
|
||||
<td class="features-third-td-background-style">MFA(Google Authenticator)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="9">账号管理 Account</td>
|
||||
<td class="features-second-td-background-style" rowspan="2">集中账号管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">管理用户管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">系统用户管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style" rowspan="4">统一密码管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资产密码托管
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">自动生成密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">密码自动推送
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">密码过期设置
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style" rowspan="2">批量密码变更(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">定期批量修改密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">生成随机密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">多云环境的资产纳管(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">对私有云、公有云资产统一纳管
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="9">授权控制 Authorization</td>
|
||||
<td class="features-second-td-background-style" rowspan="3">资产授权管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资产树
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">资产或资产组灵活授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">节点内资产自动继承授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">RemoteApp(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">实现更细粒度的应用级授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">组织管理(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">实现多租户管理,权限隔离
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">多维度授权
|
||||
</td>
|
||||
<td class="features-third-td-background-style">可对用户、用户组或系统角色授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">指令限制
|
||||
</td>
|
||||
<td class="features-third-td-background-style">限制特权指令使用,支持黑白名单
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">统一文件传输
|
||||
</td>
|
||||
<td class="features-third-td-background-style">SFTP 文件上传/下载
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">文件管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">Web SFTP 文件管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="6">安全审计 Audit</td>
|
||||
<td class="features-second-td-background-style" rowspan="2">会话管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">在线会话管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">历史会话管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style" rowspan="2">录像管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">Linux 录像支持
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">Windows 录像支持
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">指令审计
|
||||
</td>
|
||||
<td class="features-third-td-background-style">指令记录
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">文件传输审计
|
||||
</td>
|
||||
<td class="features-third-td-background-style">上传/下载记录审计
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### 开始使用
|
||||
----
|
||||
|
||||
快速开始文档 [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)
|
||||
- Step by Step 安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
||||
- 也可以查看我们完整文档 [文档](http://docs.jumpserver.org)
|
||||
|
||||
### Demo 和 截图
|
||||
### Demo、视频 和 截图
|
||||
----
|
||||
|
||||
我们提供了DEMO和截图可以让你快速了解Jumpserver
|
||||
我们提供了 Demo 、演示视频和截图可以让你快速了解 Jumpserver
|
||||
|
||||
[DEMO](http://demo.jumpserver.org)
|
||||
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
- [Demo](https://demo.jumpserver.org/auth/login/?next=/)
|
||||
- [视频](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
|
||||
- [截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
### SDK
|
||||
----
|
||||
|
||||
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
|
||||
我们还编写了一些SDK,供你的其它系统快速和 Jumpserver API 交互
|
||||
|
||||
- [python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
|
||||
- [java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
|
||||
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2018 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
58
README_EN.md
Normal file
58
README_EN.md
Normal file
@@ -0,0 +1,58 @@
|
||||
## Jumpserver
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
|
||||
----
|
||||
|
||||
- [中文版](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
|
||||
|
||||
Jumpserver is the first fully open source bastion in the world, based on the GNU GPL v2.0 open source protocol. Jumpserver is a professional operation and maintenance audit system conforms to 4A specifications.
|
||||
|
||||
Jumpserver is developed using Python / Django, conforms to the Web 2.0 specification, and is equipped with the industry-leading Web Terminal solution which have beautiful interface and great user experience.
|
||||
|
||||
Jumpserver adopts a distributed architecture to support multi-branch deployment across multiple areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
|
||||
|
||||
Change the world, starting from little things.
|
||||
|
||||
----
|
||||
|
||||
### Features
|
||||
|
||||

|
||||
|
||||
### Start
|
||||
|
||||
Quick start [Docker Install](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
|
||||
Step by Step deployment. [Docs](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
Full documentation [Docs](http://docs.jumpserver.org)
|
||||
|
||||
### Demo、Video 和 Snapshot
|
||||
|
||||
We provide online demo, demo video and screenshots to get you started quickly.
|
||||
|
||||
[Demo](https://demo.jumpserver.org/auth/login/?next=/)
|
||||
[Video](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
|
||||
[Snapshot](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
|
||||
We provide the SDK for your other systems to quickly interact with the Jumpserver API.
|
||||
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver other components use this SDK to complete the interaction.
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK thanks to 恺珺 for provide Java SDK
|
||||
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
__version__ = "0.5.0"
|
||||
#
|
||||
|
||||
0
apps/applications/__init__.py
Normal file
0
apps/applications/__init__.py
Normal file
3
apps/applications/admin.py
Normal file
3
apps/applications/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
1
apps/applications/api/__init__.py
Normal file
1
apps/applications/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
31
apps/applications/api/remote_app.py
Normal file
31
apps/applications/api/remote_app.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from ..hands import IsOrgAdmin, IsAppUser
|
||||
from ..models import RemoteApp
|
||||
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppViewSet(BulkModelViewSet):
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
queryset = RemoteApp.objects.all()
|
||||
serializer_class = RemoteAppSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
||||
queryset = RemoteApp.objects.all()
|
||||
permission_classes = (IsAppUser, )
|
||||
serializer_class = RemoteAppConnectionInfoSerializer
|
||||
7
apps/applications/apps.py
Normal file
7
apps/applications/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApplicationsConfig(AppConfig):
|
||||
name = 'applications'
|
||||
68
apps/applications/const.py
Normal file
68
apps/applications/const.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
# RemoteApp
|
||||
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
|
||||
|
||||
REMOTE_APP_TYPE_CHROME = 'chrome'
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
|
||||
REMOTE_APP_TYPE_CUSTOM = 'custom'
|
||||
|
||||
REMOTE_APP_TYPE_CHOICES = (
|
||||
(
|
||||
_('Browser'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
|
||||
)
|
||||
),
|
||||
(
|
||||
_('Database tools'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
|
||||
)
|
||||
),
|
||||
(
|
||||
_('Virtualization tools'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'),
|
||||
)
|
||||
),
|
||||
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
|
||||
|
||||
)
|
||||
|
||||
# Fields attribute write_only default => False
|
||||
|
||||
REMOTE_APP_TYPE_CHROME_FIELDS = [
|
||||
{'name': 'chrome_target'},
|
||||
{'name': 'chrome_username'},
|
||||
{'name': 'chrome_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
|
||||
{'name': 'mysql_workbench_ip'},
|
||||
{'name': 'mysql_workbench_name'},
|
||||
{'name': 'mysql_workbench_username'},
|
||||
{'name': 'mysql_workbench_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
|
||||
{'name': 'vmware_target'},
|
||||
{'name': 'vmware_username'},
|
||||
{'name': 'vmware_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_CUSTOM_FIELDS = [
|
||||
{'name': 'custom_cmdline'},
|
||||
{'name': 'custom_target'},
|
||||
{'name': 'custom_username'},
|
||||
{'name': 'custom_password', 'write_only': True}
|
||||
]
|
||||
|
||||
REMOTE_APP_TYPE_MAP_FIELDS = {
|
||||
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
|
||||
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
|
||||
}
|
||||
1
apps/applications/forms/__init__.py
Normal file
1
apps/applications/forms/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
132
apps/applications/forms/remote_app.py
Normal file
132
apps/applications/forms/remote_app.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from assets.models import Asset, SystemUser
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppCreateUpdateForm',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppTypeChromeForm(forms.ModelForm):
|
||||
chrome_target = forms.CharField(
|
||||
max_length=128, label=_('Target URL'), required=False
|
||||
)
|
||||
chrome_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
chrome_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm):
|
||||
mysql_workbench_ip = forms.CharField(
|
||||
max_length=128, label=_('Database IP'), required=False
|
||||
)
|
||||
mysql_workbench_name = forms.CharField(
|
||||
max_length=128, label=_('Database name'), required=False
|
||||
)
|
||||
mysql_workbench_username = forms.CharField(
|
||||
max_length=128, label=_('Database username'), required=False
|
||||
)
|
||||
mysql_workbench_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Database password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeVMwareForm(forms.ModelForm):
|
||||
vmware_target = forms.CharField(
|
||||
max_length=128, label=_('Target address'), required=False
|
||||
)
|
||||
vmware_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
vmware_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeCustomForm(forms.ModelForm):
|
||||
custom_cmdline = forms.CharField(
|
||||
max_length=128, label=_('Operating parameter'), required=False
|
||||
)
|
||||
custom_target = forms.CharField(
|
||||
max_length=128, label=_('Target address'), required=False
|
||||
)
|
||||
custom_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
custom_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeForms(
|
||||
RemoteAppTypeChromeForm,
|
||||
RemoteAppTypeMySQLWorkbenchForm,
|
||||
RemoteAppTypeVMwareForm,
|
||||
RemoteAppTypeCustomForm
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# 过滤RDP资产和系统用户
|
||||
super().__init__(*args, **kwargs)
|
||||
field_asset = self.fields['asset']
|
||||
field_asset.queryset = field_asset.queryset.filter(
|
||||
protocol=Asset.PROTOCOL_RDP
|
||||
)
|
||||
field_system_user = self.fields['system_user']
|
||||
field_system_user.queryset = field_system_user.queryset.filter(
|
||||
protocol=SystemUser.PROTOCOL_RDP
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'name', 'asset', 'system_user', 'type', 'path', 'comment'
|
||||
]
|
||||
widgets = {
|
||||
'asset': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Asset')
|
||||
}),
|
||||
'system_user': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('System user')
|
||||
})
|
||||
}
|
||||
|
||||
def _clean_params(self):
|
||||
app_type = self.data.get('type')
|
||||
fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, [])
|
||||
params = {}
|
||||
for field in fields:
|
||||
name = field['name']
|
||||
value = self.cleaned_data[name]
|
||||
params.update({name: value})
|
||||
return params
|
||||
|
||||
def _save_params(self, instance):
|
||||
params = self._clean_params()
|
||||
instance.params = params
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=commit)
|
||||
instance = self._save_params(instance)
|
||||
return instance
|
||||
16
apps/applications/hands.py
Normal file
16
apps/applications/hands.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
jumpserver.__app__.hands.py
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This app depends other apps api, function .. should be import or write mack here.
|
||||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by Jumpserver Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
42
apps/applications/migrations/0001_initial.py
Normal file
42
apps/applications/migrations/0001_initial.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-20 11:04
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RemoteApp',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
|
||||
('path', models.CharField(max_length=128, verbose_name='App path')),
|
||||
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'RemoteApp',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='remoteapp',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
]
|
||||
0
apps/applications/migrations/__init__.py
Normal file
0
apps/applications/migrations/__init__.py
Normal file
1
apps/applications/models/__init__.py
Normal file
1
apps/applications/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
89
apps/applications/models/remote_app.py
Normal file
89
apps/applications/models/remote_app.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from common.fields.model import EncryptJsonDictTextField
|
||||
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteApp',
|
||||
]
|
||||
|
||||
|
||||
class RemoteApp(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
asset = models.ForeignKey(
|
||||
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
)
|
||||
system_user = models.ForeignKey(
|
||||
'assets.SystemUser', on_delete=models.CASCADE,
|
||||
verbose_name=_('System user')
|
||||
)
|
||||
type = models.CharField(
|
||||
default=const.REMOTE_APP_TYPE_CHROME,
|
||||
choices=const.REMOTE_APP_TYPE_CHOICES,
|
||||
max_length=128, verbose_name=_('App type')
|
||||
)
|
||||
path = models.CharField(
|
||||
max_length=128, blank=False, null=False,
|
||||
verbose_name=_('App path')
|
||||
)
|
||||
params = EncryptJsonDictTextField(
|
||||
max_length=4096, default={}, blank=True, null=True,
|
||||
verbose_name=_('Parameters')
|
||||
)
|
||||
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')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("RemoteApp")
|
||||
unique_together = [('org_id', 'name')]
|
||||
ordering = ('name', )
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
"""
|
||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||
"""
|
||||
_parameters = list()
|
||||
_parameters.append(self.type)
|
||||
path = '\"%s\"' % self.path
|
||||
_parameters.append(path)
|
||||
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]:
|
||||
value = self.params.get(field['name'])
|
||||
if value is None:
|
||||
continue
|
||||
_parameters.append(value)
|
||||
_parameters = ' '.join(_parameters)
|
||||
return _parameters
|
||||
|
||||
@property
|
||||
def asset_info(self):
|
||||
return {
|
||||
'id': self.asset.id,
|
||||
'hostname': self.asset.hostname
|
||||
}
|
||||
|
||||
@property
|
||||
def system_user_info(self):
|
||||
return {
|
||||
'id': self.system_user.id,
|
||||
'name': self.system_user.name
|
||||
}
|
||||
1
apps/applications/serializers/__init__.py
Normal file
1
apps/applications/serializers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
103
apps/applications/serializers/remote_app.py
Normal file
103
apps/applications/serializers/remote_app.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from .. import const
|
||||
from ..models import RemoteApp
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppParamsDictField(serializers.DictField):
|
||||
"""
|
||||
RemoteApp field => params
|
||||
"""
|
||||
@staticmethod
|
||||
def filter_attribute(attribute, instance):
|
||||
"""
|
||||
过滤掉params字段值中write_only特性的key-value值
|
||||
For example, the chrome_password field is not returned when serializing
|
||||
{
|
||||
'chrome_target': 'http://www.jumpserver.org/',
|
||||
'chrome_username': 'admin',
|
||||
'chrome_password': 'admin',
|
||||
}
|
||||
"""
|
||||
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
|
||||
if field.get('write_only', False):
|
||||
attribute.pop(field['name'], None)
|
||||
return attribute
|
||||
|
||||
def get_attribute(self, instance):
|
||||
"""
|
||||
序列化时调用
|
||||
"""
|
||||
attribute = super().get_attribute(instance)
|
||||
attribute = self.filter_attribute(attribute, instance)
|
||||
return attribute
|
||||
|
||||
@staticmethod
|
||||
def filter_value(dictionary, value):
|
||||
"""
|
||||
过滤掉不属于当前app_type所包含的key-value值
|
||||
"""
|
||||
app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
|
||||
fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
|
||||
fields_names = [field['name'] for field in fields]
|
||||
no_need_keys = [k for k in value.keys() if k not in fields_names]
|
||||
for k in no_need_keys:
|
||||
value.pop(k)
|
||||
return value
|
||||
|
||||
def get_value(self, dictionary):
|
||||
"""
|
||||
反序列化时调用
|
||||
"""
|
||||
value = super().get_value(dictionary)
|
||||
value = self.filter_value(dictionary, value)
|
||||
return value
|
||||
|
||||
|
||||
class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
params = RemoteAppParamsDictField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
|
||||
'comment', 'created_by', 'date_created', 'asset_info',
|
||||
'system_user_info', 'get_type_display',
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'asset_info',
|
||||
'system_user_info', 'get_type_display'
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||
parameter_remote_app = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'system_user', 'parameter_remote_app',
|
||||
]
|
||||
read_only_fields = ['parameter_remote_app']
|
||||
|
||||
@staticmethod
|
||||
def get_parameter_remote_app(obj):
|
||||
parameter = {
|
||||
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
|
||||
'working_directory': '',
|
||||
'parameters': obj.parameters,
|
||||
}
|
||||
return parameter
|
||||
@@ -0,0 +1,123 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<form id="appForm" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.asset layout="horizontal" %}
|
||||
{% bootstrap_field form.system_user layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.path layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
||||
{# chrome #}
|
||||
<div class="chrome-fields">
|
||||
{% bootstrap_field form.chrome_target layout="horizontal" %}
|
||||
{% bootstrap_field form.chrome_username layout="horizontal" %}
|
||||
{% bootstrap_field form.chrome_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# mysql workbench #}
|
||||
<div class="mysql_workbench-fields">
|
||||
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# vmware #}
|
||||
<div class="vmware_client-fields">
|
||||
{% bootstrap_field form.vmware_target layout="horizontal" %}
|
||||
{% bootstrap_field form.vmware_username layout="horizontal" %}
|
||||
{% bootstrap_field form.vmware_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# custom #}
|
||||
<div class="custom-fields">
|
||||
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_target layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_username layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
var app_type_id = '#' + '{{ form.type.id_for_label }}';
|
||||
var app_path_id = '#' + '{{ form.path.id_for_label }}';
|
||||
var all_type_fields = [
|
||||
'.chrome-fields',
|
||||
'.mysql_workbench-fields',
|
||||
'.vmware_client-fields',
|
||||
'.custom-fields'
|
||||
];
|
||||
var app_type_map_default_fields_value = {
|
||||
'chrome': {
|
||||
'app_path': 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
||||
},
|
||||
'mysql_workbench': {
|
||||
'app_path': 'C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe'
|
||||
},
|
||||
'vmware_client': {
|
||||
'app_path': 'C:\\Program Files (x86)\\VMware\\Infrastructure\\Virtual Infrastructure Client\\Launcher\\VpxClient.exe'
|
||||
},
|
||||
'custom': {'app_path': ''}
|
||||
};
|
||||
function getAppType(){
|
||||
return $(app_type_id+ " option:selected").val();
|
||||
}
|
||||
function initialDefaultValue(){
|
||||
var app_type = getAppType();
|
||||
var app_path = $(app_path_id).val();
|
||||
if(app_path){
|
||||
app_type_map_default_fields_value[app_type]['app_path'] = app_path
|
||||
}
|
||||
}
|
||||
function setDefaultValue(){
|
||||
// 设置类型相关字段的默认值
|
||||
var app_type = getAppType();
|
||||
var app_path = app_type_map_default_fields_value[app_type]['app_path'];
|
||||
$(app_path_id).val(app_path)
|
||||
}
|
||||
function hiddenFields(){
|
||||
var app_type = getAppType();
|
||||
$.each(all_type_fields, function(index, value){
|
||||
$(value).addClass('hidden')
|
||||
});
|
||||
$('.' + app_type + '-fields').removeClass('hidden');
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: true
|
||||
});
|
||||
initialDefaultValue();
|
||||
hiddenFields();
|
||||
setDefaultValue();
|
||||
})
|
||||
.on('change', app_type_id, function(){
|
||||
hiddenFields();
|
||||
setDefaultValue();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
109
apps/applications/templates/applications/remote_app_detail.html
Normal file
109
apps/applications/templates/applications/remote_app_detail.html
Normal file
@@ -0,0 +1,109 @@
|
||||
{% 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 'applications:remote-app-detail' pk=remote_app.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'applications:remote-app-update' pk=remote_app.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-application">
|
||||
<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>{{ remote_app.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>{{ remote_app.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset' %}:</td>
|
||||
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'System user' %}:</td>
|
||||
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'App type' %}:</td>
|
||||
<td><b>{{ remote_app.get_type_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'App path' %}:</td>
|
||||
<td><b>{{ remote_app.path }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ remote_app.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ remote_app.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ remote_app.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on('click', '.btn-delete-application', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ remote_app.name }}";
|
||||
var rid = "{{ remote_app.id }}";
|
||||
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||
var redirect_url = "{% url 'applications:remote-app-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %}
|
||||
<b><a href="https://github.com/jumpserver/Jmservisor/releases" target="view_window" >{% trans 'Download application loader' %}</a></b>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'applications:remote-app-create' %}" class="btn btn-sm btn-primary"> {% trans "Create RemoteApp" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="remote_app_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 'App type' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</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: $('#remote_app_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
{% url 'applications:remote-app-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) {
|
||||
var hostname = htmlEscape(cellData.hostname);
|
||||
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData.name);
|
||||
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "applications:remote-app-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-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-applications:remote-app-list" %}',
|
||||
columns: [
|
||||
{data: "id"},
|
||||
{data: "name" },
|
||||
{data: "get_type_display", orderable: false},
|
||||
{data: "asset_info", orderable: false},
|
||||
{data: "system_user_info", orderable: false},
|
||||
{data: "comment"},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#remote_app_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var rid = $this.data('rid');
|
||||
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,79 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mail-box-header">
|
||||
<table class="table table-striped table-bordered table-hover " id="remote_app_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 'App type' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var inited = false;
|
||||
var remote_app_table, url;
|
||||
|
||||
function initTable() {
|
||||
if (inited){
|
||||
return
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
url = '{% url "api-perms:my-remote-apps" %}';
|
||||
var options = {
|
||||
ele: $('#remote_app_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData);
|
||||
$(td).html(name)
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var hostname = htmlEscape(cellData.hostname);
|
||||
$(td).html(hostname);
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData.name);
|
||||
$(td).html(name);
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
$(td).html(conn_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: url,
|
||||
columns: [
|
||||
{data: "id"},
|
||||
{data: "name"},
|
||||
{data: "get_type_display", orderable: false},
|
||||
{data: "asset_info", orderable: false},
|
||||
{data: "system_user_info", orderable: false},
|
||||
{data: "comment", orderable: false},
|
||||
{data: "id", orderable: false}
|
||||
]
|
||||
};
|
||||
remote_app_table = jumpserver.initServerSideDataTable(options);
|
||||
return remote_app_table
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
3
apps/applications/tests.py
Normal file
3
apps/applications/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
apps/applications/urls/__init__.py
Normal file
7
apps/applications/urls/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
]
|
||||
20
apps/applications/urls/api_urls.py
Normal file
20
apps/applications/urls/api_urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
|
||||
|
||||
urlpatterns = [
|
||||
path('remote-apps/<uuid:pk>/connection-info/',
|
||||
api.RemoteAppConnectionInfoApi.as_view(),
|
||||
name='remote-app-connection-info')
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
16
apps/applications/urls/views_urls.py
Normal file
16
apps/applications/urls/views_urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# coding:utf-8
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
urlpatterns = [
|
||||
# RemoteApp
|
||||
path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'),
|
||||
path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
|
||||
path('remote-app/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
|
||||
path('remote-app/<uuid:pk>/', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
|
||||
# User RemoteApp view
|
||||
path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
|
||||
|
||||
]
|
||||
1
apps/applications/views/__init__.py
Normal file
1
apps/applications/views/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
99
apps/applications/views/remote_app.py
Normal file
99
apps/applications/views/remote_app.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import forms
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView',
|
||||
'RemoteAppDetailView', 'UserRemoteAppListView',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'applications/remote_app_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('RemoteApp list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return create_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
|
||||
def get_initial(self):
|
||||
return {k: v for k, v in self.object.params.items()}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return update_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppDetailView(AdminUserRequiredMixin, DetailView):
|
||||
template_name = 'applications/remote_app_detail.html'
|
||||
model = RemoteApp
|
||||
context_object_name = 'remote_app'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('RemoteApp detail'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'applications/user_remote_app_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'action': _('My RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -3,3 +3,6 @@ from .asset import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .asset_user import *
|
||||
|
||||
@@ -14,37 +14,55 @@
|
||||
# limitations under the License.
|
||||
|
||||
from django.db import transaction
|
||||
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.mixins import IDInFilterMixin
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
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
|
||||
from ..tasks import test_admin_user_connectivity_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi', 'AdminUserTestConnectiveApi'
|
||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
|
||||
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
|
||||
'AdminUserAssetsListView',
|
||||
]
|
||||
|
||||
|
||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class AdminUserViewSet(IDInCacheFilterMixin, 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 = (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()
|
||||
@@ -65,12 +83,30 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
|
||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
test_admin_user_connectability_manual.delay(admin_user)
|
||||
return Response({"msg": "Task created"})
|
||||
task = test_admin_user_connectivity_manual.delay(admin_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AdminUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(AdminUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
admin_user = self.get_object()
|
||||
return admin_user.get_related_assets()
|
||||
|
||||
@@ -1,33 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
import random
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.core.cache import cache
|
||||
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.mixins import IDInCacheFilterMixin
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectability_manual
|
||||
test_asset_connectivity_manual
|
||||
from ..utils import LabelFilter
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
|
||||
'AssetViewSet', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
|
||||
]
|
||||
|
||||
|
||||
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
@@ -37,43 +46,85 @@ 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')
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
def set_assets_node(self, assets):
|
||||
if not isinstance(assets, list):
|
||||
assets = [assets]
|
||||
node = Node.root()
|
||||
node_id = self.request.query_params.get('node_id')
|
||||
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()
|
||||
node = get_object_or_none(Node, pk=node_id)
|
||||
node.assets.add(*assets)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
assets = serializer.save()
|
||||
self.set_assets_node(assets)
|
||||
|
||||
def filter_node(self, queryset):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
if not node_id:
|
||||
return queryset
|
||||
|
||||
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() and show_current_asset:
|
||||
queryset = queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
)
|
||||
elif node.is_root() and not show_current_asset:
|
||||
pass
|
||||
elif not node.is_root() and show_current_asset:
|
||||
queryset = queryset.filter(nodes=node)
|
||||
else:
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_admin_user_id(self, queryset):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
if not admin_user_id:
|
||||
return queryset
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
return queryset
|
||||
|
||||
class UserAssetListView(generics.ListAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_node(queryset)
|
||||
queryset = self.filter_admin_user_id(queryset)
|
||||
return queryset
|
||||
|
||||
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]
|
||||
)
|
||||
queryset = super().get_queryset().distinct()
|
||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AssetBulkUpdateSelectAPI(APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
assets_id = request.data.get('assets_id', '')
|
||||
if assets_id:
|
||||
spm = uuid.uuid4().hex
|
||||
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
|
||||
cache.set(key, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
|
||||
return Response({'url': url})
|
||||
error = _('Please select assets that need to be updated')
|
||||
return Response({'error': error}, status=400)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
@@ -82,31 +133,43 @@ 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')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
summary = update_asset_hardware_info_manual(asset)[1]
|
||||
logger.debug("Refresh summary: {}".format(summary))
|
||||
if summary.get('dark'):
|
||||
return Response(summary['dark'].values(), status=501)
|
||||
else:
|
||||
return Response({"msg": "ok"})
|
||||
task = update_asset_hardware_info_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
ok, msg = test_asset_connectability_manual(asset)
|
||||
if ok:
|
||||
return Response({"msg": "pong"})
|
||||
task = test_asset_connectivity_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.GatewayWithAuthSerializer
|
||||
|
||||
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({"error": msg}, status=502)
|
||||
return Response({"msg": "Not have gateway"}, status=404)
|
||||
101
apps/assets/api/asset_user.py
Normal file
101
apps/assets/api/asset_user.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status, generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..models import Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_asset_users_connectivity_manual
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
|
||||
]
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AssetUserViewSet(viewsets.GenericViewSet):
|
||||
pagination_class = LimitOffsetPagination
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser, )
|
||||
http_method_names = ['get', 'post']
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def get_queryset(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
queryset = AssetUserManager.filter(username=username, asset=asset)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = sorted(
|
||||
queryset,
|
||||
key=lambda q: (q.asset.hostname, q.connectivity, q.username)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
serializer_class = serializers.AssetUserAuthInfoSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
status_code = status.HTTP_200_OK
|
||||
if not instance:
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
return Response(serializer.data, status=status_code)
|
||||
|
||||
def get_object(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
try:
|
||||
instance = AssetUserManager.get(username, asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
else:
|
||||
return instance
|
||||
|
||||
|
||||
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
asset_users = AssetUserManager.filter(username=username, asset=asset)
|
||||
return asset_users
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_users = self.get_asset_users()
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
61
apps/assets/api/domain.py
Normal file
61
apps/assets/api/domain.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||
from ..models import Domain, Gateway
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
|
||||
|
||||
class DomainViewSet(BulkModelViewSet):
|
||||
queryset = Domain.objects.all()
|
||||
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'):
|
||||
return serializers.DomainWithGatewaySerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def get_permissions(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
self.permission_classes = (IsOrgAdminOrAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class GatewayViewSet(BulkModelViewSet):
|
||||
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||
search_fields = filter_fields
|
||||
queryset = Gateway.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.GatewaySerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Gateway
|
||||
object = None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(Gateway.objects.all())
|
||||
local_port = self.request.data.get('port') or self.object.port
|
||||
ok, e = self.object.test_connective(local_port=local_port)
|
||||
if ok:
|
||||
return Response("ok")
|
||||
else:
|
||||
return Response({"failed": e}, status=404)
|
||||
@@ -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,32 +13,36 @@
|
||||
# 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 common.utils import get_logger, get_object_or_none
|
||||
from ..hands import IsSuperUser
|
||||
from common.tree import TreeNodeSerializer
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi'
|
||||
'TestNodeConnectiveApi', 'NodeListAsTreeApi',
|
||||
'NodeChildrenAsTreeApi', 'RefreshAssetsAmount',
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(BulkModelViewSet):
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
filter_fields = ('value', 'key', )
|
||||
search_fields = filter_fields
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
@@ -46,42 +50,174 @@ 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 NodeListAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
获取节点列表树
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
"""
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = TreeNodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = [node.as_tree_node() for node in Node.objects.all()]
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
queryset = self.refresh_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
def refresh_nodes(queryset):
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
节点子节点作为树返回,
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = TreeNodeSerializer
|
||||
node = None
|
||||
is_root = False
|
||||
|
||||
def get_queryset(self):
|
||||
node_key = self.request.query_params.get('key')
|
||||
if node_key:
|
||||
self.node = Node.objects.get(key=node_key)
|
||||
queryset = self.node.get_children(with_self=False)
|
||||
else:
|
||||
self.is_root = True
|
||||
self.node = Node.root()
|
||||
queryset = list(self.node.get_children(with_self=True))
|
||||
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
|
||||
queryset.extend(list(nodes_invalid))
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
queryset = sorted(queryset)
|
||||
return queryset
|
||||
|
||||
def filter_assets(self, queryset):
|
||||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.node.get_assets()
|
||||
for asset in assets:
|
||||
queryset.append(asset.as_tree_node(self.node))
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = self.filter_assets(queryset)
|
||||
queryset = self.filter_refresh_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
def filter_refresh_nodes(self, queryset):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if not request.data.get("value"):
|
||||
request.data["value"] = _("New node {}").format(
|
||||
Node.root().get_next_child_key().split(":")[-1]
|
||||
)
|
||||
request.data["value"] = instance.get_next_child_preset_name()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
value = request.data.get("value")
|
||||
node = instance.create_child(value=value)
|
||||
return Response(
|
||||
{"id": node.id, "key": node.key, "value": node.value},
|
||||
status=201,
|
||||
)
|
||||
_id = request.data.get('id') or None
|
||||
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, _id=_id)
|
||||
return Response(self.serializer_class(instance=node).data, 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 = Node.root()
|
||||
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")
|
||||
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))
|
||||
return queryset
|
||||
|
||||
|
||||
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 +229,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 +247,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,31 +255,56 @@ 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 = _("Refresh node assets hardware info: {}".format(node.name))
|
||||
task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
return Response({"msg": "Task created"})
|
||||
assets = node.get_all_assets()
|
||||
# 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))
|
||||
test_asset_connectability_util.delay(assets, task_name=task_name)
|
||||
return Response({"msg": "Task created"})
|
||||
assets = node.get_all_assets()
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class RefreshAssetsAmount(APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.model.expire_nodes_assets_amount()
|
||||
return Response("Ok")
|
||||
|
||||
@@ -13,49 +13,76 @@
|
||||
# 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 common.mixins import IDInCacheFilterMixin
|
||||
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_connectivity_manual, push_system_user_a_asset_manual, \
|
||||
test_system_user_connectivity_a_asset
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
|
||||
]
|
||||
|
||||
|
||||
class SystemUserViewSet(BulkModelViewSet):
|
||||
class SystemUserViewSet(IDInCacheFilterMixin, 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 update(self, request, *args, **kwargs):
|
||||
password = request.data.pop("password", None)
|
||||
private_key = request.data.pop("private_key", None)
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.clear_auth()
|
||||
return Response(status=204)
|
||||
|
||||
if password or private_key:
|
||||
instance.set_auth(password=password, private_key=private_key)
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Get system user with asset auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
|
||||
def get_object(self):
|
||||
instance = super().get_object()
|
||||
aid = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, pk=aid)
|
||||
instance.load_specific_asset_auth(asset)
|
||||
return instance
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
@@ -63,12 +90,15 @@ 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()
|
||||
push_system_user_to_assets_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
||||
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})
|
||||
|
||||
|
||||
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
@@ -76,9 +106,65 @@ 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()
|
||||
test_system_user_connectability_manual.delay(system_user)
|
||||
return Response({"msg": "Task created"})
|
||||
task = test_system_user_connectivity_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
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,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
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 SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
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_connectivity_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
|
||||
|
||||
0
apps/assets/backends/__init__.py
Normal file
0
apps/assets/backends/__init__.py
Normal file
60
apps/assets/backends/base.py
Normal file
60
apps/assets/backends/base.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
NotSupportError = NotSupportError
|
||||
MSG_NOT_EXIST = '{} Object matching query does not exist'
|
||||
MSG_MULTIPLE = '{} get() returned more than one object ' \
|
||||
'-- it returned {}!'
|
||||
|
||||
@classmethod
|
||||
def get(cls, username, asset):
|
||||
instances = cls.filter(username, asset)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
cls.raise_does_not_exist(cls.__name__)
|
||||
else:
|
||||
cls.raise_multiple_return(cls.__name__, len(instances))
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param asset: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def create(cls, **kwargs):
|
||||
"""
|
||||
:param kwargs:
|
||||
{
|
||||
name, username, asset, comment, password, public_key, private_key,
|
||||
(org_id)
|
||||
}
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def raise_does_not_exist(cls, name):
|
||||
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
|
||||
|
||||
@classmethod
|
||||
def raise_multiple_return(cls, name, length):
|
||||
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))
|
||||
2
apps/assets/backends/external/__init__.py
vendored
Normal file
2
apps/assets/backends/external/__init__.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
31
apps/assets/backends/external/db.py
vendored
Normal file
31
apps/assets/backends/external/db.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
from ..base import BaseBackend
|
||||
|
||||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
if latest:
|
||||
queryset = queryset.latest_version()
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
auth_info = {
|
||||
'password': kwargs.pop('password', ''),
|
||||
'public_key': kwargs.pop('public_key', ''),
|
||||
'private_key': kwargs.pop('private_key', '')
|
||||
}
|
||||
obj = AuthBook.objects.create(**kwargs)
|
||||
obj.set_auth(**auth_info)
|
||||
return obj
|
||||
16
apps/assets/backends/external/utils.py
vendored
Normal file
16
apps/assets/backends/external/utils.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
# from django.conf import settings
|
||||
|
||||
from .db import AuthBookBackend
|
||||
# from .vault import VaultBackend
|
||||
|
||||
|
||||
def get_backend():
|
||||
default_backend = AuthBookBackend
|
||||
|
||||
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
|
||||
# return VaultBackend
|
||||
|
||||
return default_backend
|
||||
19
apps/assets/backends/external/vault.py
vendored
Normal file
19
apps/assets/backends/external/vault.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..base import BaseBackend
|
||||
|
||||
|
||||
class VaultBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def get(cls, username, asset):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
pass
|
||||
4
apps/assets/backends/internal/__init__.py
Normal file
4
apps/assets/backends/internal/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
38
apps/assets/backends/internal/admin_user.py
Normal file
38
apps/assets/backends/internal/admin_user.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class AdminUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets(cls, asset):
|
||||
if not asset:
|
||||
assets = Asset.objects.all().prefetch_related('admin_user')
|
||||
else:
|
||||
assets = [asset]
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
instances = []
|
||||
assets = cls._get_assets(asset)
|
||||
for asset in assets:
|
||||
if username is not None and asset.admin_user.username != username:
|
||||
continue
|
||||
instance = construct_authbook_object(asset.admin_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
||||
32
apps/assets/backends/internal/asset_user.py
Normal file
32
apps/assets/backends/internal/asset_user.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
from .system_user import SystemUserBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
admin_user_instances = AdminUserBackend.filter(username, asset)
|
||||
system_user_instances = SystemUserBackend.filter(username, asset)
|
||||
instances = cls._merge_instances(admin_user_instances, system_user_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, admin_user_instances, system_user_instances):
|
||||
admin_user_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in admin_user_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in system_user_instances
|
||||
if instance.keyword not in admin_user_instances_keyword_list
|
||||
]
|
||||
admin_user_instances.extend(instances)
|
||||
return admin_user_instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
||||
75
apps/assets/backends/internal/system_user.py
Normal file
75
apps/assets/backends/internal/system_user.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class SystemUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
@classmethod
|
||||
def _filter_system_users_by_username(cls, system_users, username):
|
||||
_system_users = cls._distinct_system_users_by_username(system_users)
|
||||
if username is not None:
|
||||
_system_users = [su for su in _system_users if username == su.username]
|
||||
return _system_users
|
||||
|
||||
@classmethod
|
||||
def _construct_authbook_objects(cls, system_users, asset):
|
||||
instances = []
|
||||
for system_user in system_users:
|
||||
instance = construct_authbook_object(system_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets_with_system_users(cls, asset=None):
|
||||
"""
|
||||
{ 'asset': set(<SystemUser>, <SystemUser>, ...) }
|
||||
"""
|
||||
if not asset:
|
||||
_assets = Asset.objects.all().prefetch_related('systemuser_set')
|
||||
else:
|
||||
_assets = [asset]
|
||||
|
||||
assets = {asset: set(asset.systemuser_set.all()) for asset in _assets}
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
"""
|
||||
:return: [<AuthBook>, <AuthBook>, ...]
|
||||
"""
|
||||
instances = []
|
||||
assets = cls._get_assets_with_system_users(asset)
|
||||
for _asset, _system_users in assets.items():
|
||||
_system_users = cls._filter_system_users_by_username(_system_users, username)
|
||||
_instances = cls._construct_authbook_objects(_system_users, _asset)
|
||||
instances.extend(_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise Exception("Not support create")
|
||||
|
||||
|
||||
26
apps/assets/backends/internal/utils.py
Normal file
26
apps/assets/backends/internal/utils.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
|
||||
def construct_authbook_object(asset_user, asset):
|
||||
"""
|
||||
作用: 将<AssetUser>对象构造成为<AuthBook>对象并返回
|
||||
|
||||
:param asset_user: <AdminUser>或<SystemUser>对象
|
||||
:param asset: <Asset>对象
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
fields = [
|
||||
'id', 'name', 'username', 'comment', 'org_id',
|
||||
'_password', '_private_key', '_public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
|
||||
obj = AuthBook(asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(asset_user, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
||||
40
apps/assets/backends/multi.py
Normal file
40
apps/assets/backends/multi.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
from .external.utils import get_backend
|
||||
from .internal.asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AssetUserManager(BaseBackend):
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
external_backend = get_backend()
|
||||
internal_backend = AssetUserBackend
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
external_instance = list(cls.external_backend.filter(username, asset))
|
||||
internal_instance = list(cls.internal_backend.filter(username, asset))
|
||||
instances = cls._merge_instances(external_instance, internal_instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
instance = cls.external_backend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, external_instances, internal_instances):
|
||||
external_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in external_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in internal_instances
|
||||
if instance.keyword not in external_instances_keyword_list
|
||||
]
|
||||
external_instances.extend(instances)
|
||||
return external_instances
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
{
|
||||
'name': "setup",
|
||||
@@ -32,7 +35,23 @@ TEST_SYSTEM_USER_CONN_TASKS = [
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
TASK_OPTIONS = {
|
||||
'timeout': 10,
|
||||
'forks': 10,
|
||||
}
|
||||
|
||||
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
||||
|
||||
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .user import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -3,19 +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', 'protocol',
|
||||
|
||||
]
|
||||
widgets = {
|
||||
@@ -26,57 +30,65 @@ 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',
|
||||
'port': '* required',
|
||||
'admin_user': _(
|
||||
'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', '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',
|
||||
'port': '* required',
|
||||
'cluster': '* required',
|
||||
'admin_user': _(
|
||||
'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',
|
||||
required=True,
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
@@ -85,34 +97,29 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects.all(),
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Admin user')
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'admin_user', 'labels', 'nodes', 'platform'
|
||||
'assets', 'port', 'admin_user', 'labels', 'platform',
|
||||
'protocol', 'domain',
|
||||
]
|
||||
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')}
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 重写其他字段为不再required
|
||||
for name, field in self.fields.items():
|
||||
if name != 'assets':
|
||||
field.required = False
|
||||
|
||||
def save(self, commit=True):
|
||||
changed_fields = []
|
||||
for field in self._meta.fields:
|
||||
@@ -123,14 +130,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'
|
||||
}),
|
||||
}
|
||||
75
apps/assets/forms/domain.py
Normal file
75
apps/assets/forms/domain.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
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
|
||||
|
||||
__all__ = ['DomainForm', 'GatewayForm']
|
||||
|
||||
|
||||
class DomainForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
queryset=Asset.objects.all(), label=_('Asset'), required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ['name', 'comment', 'assets']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None):
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# 前端渲染优化, 防止过多资产
|
||||
assets_field = self.fields.get('assets')
|
||||
if not self.data:
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
assets_field.queryset = instance.assets.all()
|
||||
else:
|
||||
assets_field.queryset = Asset.objects.none()
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=commit)
|
||||
assets = self.cleaned_data['assets']
|
||||
instance.assets.set(assets)
|
||||
return instance
|
||||
|
||||
|
||||
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||
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')
|
||||
protocol_field = self.fields.get('protocol')
|
||||
protocol_field.choices = [Gateway.PROTOCOL_CHOICES[0]]
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
instance = super().save()
|
||||
password = self.cleaned_data.get('password')
|
||||
private_key, public_key = super().gen_keys()
|
||||
instance.set_auth(password=password, private_key=private_key)
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Gateway
|
||||
fields = [
|
||||
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
|
||||
'private_key_file', 'is_active', 'comment',
|
||||
]
|
||||
help_texts = {
|
||||
'protocol': _("SSH gateway support proxy SSH,RDP,VNC")
|
||||
}
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
@@ -26,6 +26,15 @@ class LabelForm(forms.ModelForm):
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# 前端渲染优化, 防止过多资产
|
||||
assets_field = self.fields.get('assets')
|
||||
if not self.data:
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
assets_field.queryset = instance.assets.all()
|
||||
else:
|
||||
assets_field.queryset = Asset.objects.none()
|
||||
|
||||
def save(self, commit=True):
|
||||
label = super().save(commit=commit)
|
||||
assets = self.cleaned_data['assets']
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
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__ = [
|
||||
'FileForm', 'SystemUserForm', 'AdminUserForm',
|
||||
'FileForm', 'SystemUserForm', 'AdminUserForm', 'PasswordAndKeyAuthForm',
|
||||
]
|
||||
|
||||
|
||||
@@ -34,8 +35,12 @@ class PasswordAndKeyAuthForm(forms.ModelForm):
|
||||
if private_key_file:
|
||||
key_string = private_key_file.read()
|
||||
private_key_file.seek(0)
|
||||
key_string = key_string.decode()
|
||||
|
||||
if not validate_ssh_private_key(key_string, password):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
msg = _('Invalid private key, Only support '
|
||||
'RSA/DSA format key')
|
||||
raise forms.ValidationError(msg)
|
||||
return private_key_file
|
||||
|
||||
def validate_password_key(self):
|
||||
@@ -79,13 +84,9 @@ class AdminUserForm(PasswordAndKeyAuthForm):
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
}
|
||||
|
||||
|
||||
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,26 @@ 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.LOGIN_MANUAL or \
|
||||
protocol in [SystemUser.PROTOCOL_RDP,
|
||||
SystemUser.PROTOCOL_TELNET,
|
||||
SystemUser.PROTOCOL_VNC]:
|
||||
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,27 +122,38 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
if not self.instance and not auto_generate:
|
||||
super().validate_password_key()
|
||||
|
||||
def clean_username(self):
|
||||
username = self.data.get('username')
|
||||
login_mode = self.data.get('login_mode')
|
||||
protocol = self.data.get('protocol')
|
||||
|
||||
if username:
|
||||
return username
|
||||
if login_mode == SystemUser.LOGIN_AUTO and \
|
||||
protocol != SystemUser.PROTOCOL_VNC:
|
||||
msg = _('* Automatic login mode must fill in the username.')
|
||||
raise forms.ValidationError(msg)
|
||||
return username
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'nodes', 'priority',
|
||||
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'nodes': forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Nodes')
|
||||
}
|
||||
),
|
||||
'cmd_filters': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Command filter')
|
||||
}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'nodes': _('If auto push checked, system user will be create at node assets'),
|
||||
'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.'),
|
||||
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
|
||||
}
|
||||
|
||||
@@ -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'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,158 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
|
||||
|
||||
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'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
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={('name', 'value')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
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',
|
||||
),
|
||||
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, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
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'),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
]
|
||||
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'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,220 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# assets.migrations.0017_auto_20180702_1415
|
||||
|
||||
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):
|
||||
|
||||
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
|
||||
|
||||
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'),
|
||||
),
|
||||
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(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], 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.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
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='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'),
|
||||
),
|
||||
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='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'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
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='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(
|
||||
code=migrate_win_to_ssh_protocol,
|
||||
),
|
||||
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.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
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')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
||||
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, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=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, db_index=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, db_index=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'),
|
||||
),
|
||||
]
|
||||
23
apps/assets/migrations/0024_auto_20181219_1614.py
Normal file
23
apps/assets/migrations/0024_auto_20181219_1614.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.1.4 on 2018-12-19 08:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0023_auto_20181016_1650'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
21
apps/assets/migrations/0025_auto_20190221_1902.py
Normal file
21
apps/assets/migrations/0025_auto_20190221_1902.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 11:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0024_auto_20181219_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilter',
|
||||
options={'verbose_name': 'Command filter'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilterrule',
|
||||
options={'ordering': ('-priority', 'action'), 'verbose_name': 'Command filter rule'},
|
||||
),
|
||||
]
|
||||
43
apps/assets/migrations/0026_auto_20190325_2035.py
Normal file
43
apps/assets/migrations/0026_auto_20190325_2035.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-25 12:35
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0025_auto_20190221_1902'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuthBook',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], 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')),
|
||||
('comment', models.TextField(blank=True, 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, null=True, verbose_name='Created by')),
|
||||
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
|
||||
('version', models.IntegerField(default=1, verbose_name='Version')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'AuthBook',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='node',
|
||||
options={'ordering': ['key'], 'verbose_name': 'Node'},
|
||||
),
|
||||
]
|
||||
18
apps/assets/migrations/0027_auto_20190521_1703.py
Normal file
18
apps/assets/migrations/0027_auto_20190521_1703.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-21 09:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
]
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/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 *
|
||||
from .authbook import *
|
||||
from applications.models.remote_app import *
|
||||
|
||||
@@ -4,15 +4,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 .cluster import Cluster
|
||||
from .group import AssetGroup
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -31,12 +33,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'),
|
||||
@@ -44,12 +55,28 @@ class Asset(models.Model):
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
|
||||
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'))
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
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'))
|
||||
|
||||
@@ -68,11 +95,11 @@ class Asset(models.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'))
|
||||
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'))
|
||||
@@ -83,8 +110,17 @@ class Asset(models.Model):
|
||||
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)()
|
||||
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.hostname
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
@@ -95,49 +131,109 @@ 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", "Other"):
|
||||
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:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def is_connective(self):
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return True
|
||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||
if val == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.REACHABLE
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cache.set(key, value, 3600*2)
|
||||
|
||||
def get_auth_info(self):
|
||||
if self.admin_user:
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
return {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
'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_json(self):
|
||||
return {
|
||||
info = {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
if self.domain and self.domain.gateway_set.all():
|
||||
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
|
||||
return info
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory, First using asset user,
|
||||
otherwise using cluster admin user
|
||||
|
||||
Ansible use it create inventory
|
||||
Todo: May be move to ops implements it
|
||||
"""
|
||||
data = self.to_json()
|
||||
if self.admin_user:
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
admin_user = self.admin_user
|
||||
data.update({
|
||||
'username': admin_user.username,
|
||||
@@ -148,8 +244,38 @@ class Asset(models.Model):
|
||||
})
|
||||
return data
|
||||
|
||||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
if self.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif self.platform.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
data = {
|
||||
'id': str(self.id),
|
||||
'name': self.hostname,
|
||||
'title': self.ip,
|
||||
'pId': parent_node.key,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'platform': self.platform,
|
||||
'protocol': self.protocol,
|
||||
}
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
class Meta:
|
||||
unique_together = ('ip', 'port')
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
||||
@classmethod
|
||||
@@ -157,20 +283,25 @@ 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)]
|
||||
asset.groups = [choice(AssetGroup.objects.all()) for i in range(3)]
|
||||
logger.debug('Generate fake asset : %s' % asset.ip)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
93
apps/assets/models/authbook.py
Normal file
93
apps/assets/models/authbook.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgManager
|
||||
|
||||
from .base import AssetUser
|
||||
from ..const import ASSET_USER_CONN_CACHE_KEY
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
||||
class AuthBookQuerySet(models.QuerySet):
|
||||
|
||||
def latest_version(self):
|
||||
return self.filter(is_latest=True)
|
||||
|
||||
|
||||
class AuthBookManager(OrgManager):
|
||||
pass
|
||||
|
||||
|
||||
class AuthBook(AssetUser):
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
|
||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
||||
def _set_latest(self):
|
||||
self._remove_pre_obj_latest()
|
||||
self.is_latest = True
|
||||
self.save()
|
||||
|
||||
def _get_pre_obj(self):
|
||||
pre_obj = self.__class__.objects.filter(
|
||||
username=self.username, asset=self.asset).latest_version().first()
|
||||
return pre_obj
|
||||
|
||||
def _remove_pre_obj_latest(self):
|
||||
pre_obj = self._get_pre_obj()
|
||||
if pre_obj:
|
||||
pre_obj.is_latest = False
|
||||
pre_obj.save()
|
||||
|
||||
def _set_version(self):
|
||||
pre_obj = self._get_pre_obj()
|
||||
if pre_obj:
|
||||
self.version = pre_obj.version + 1
|
||||
else:
|
||||
self.version = 1
|
||||
self.save()
|
||||
|
||||
def set_version_and_latest(self):
|
||||
self._set_version()
|
||||
self._set_latest()
|
||||
|
||||
@property
|
||||
def _conn_cache_key(self):
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
value = cache.get(self._conn_cache_key, self.UNKNOWN)
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
_connectivity = self.UNKNOWN
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.UNREACHABLE
|
||||
|
||||
for host in value.get('contacted', []):
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.REACHABLE
|
||||
|
||||
cache.set(self._conn_cache_key, _connectivity, 3600)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
return {'username': self.username, 'asset': self.asset}
|
||||
|
||||
def __str__(self):
|
||||
return '{}@{}'.format(self.username, self.asset)
|
||||
|
||||
172
apps/assets/models/base.py
Normal file
172
apps/assets/models/base.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import os
|
||||
import uuid
|
||||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.db import models
|
||||
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, get_logger
|
||||
)
|
||||
from common.validators import alphanumeric
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
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'))
|
||||
comment = models.TextField(blank=True, 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, null=True, verbose_name=_('Created by'))
|
||||
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_str = signer.unsign(self._private_key)
|
||||
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
key = signer.unsign(self._public_key)
|
||||
if key:
|
||||
return key
|
||||
else:
|
||||
return None
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self._password = signer.sign(password)
|
||||
update_fields.append('_password')
|
||||
if private_key:
|
||||
self._private_key = signer.sign(private_key)
|
||||
update_fields.append('_private_key')
|
||||
if public_key:
|
||||
self._public_key = signer.sign(public_key)
|
||||
update_fields.append('_public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def get_auth(self, asset=None):
|
||||
pass
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
from ..backends.multi import AssetUserManager
|
||||
try:
|
||||
other = AssetUserManager.get(username=self.username, asset=asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
else:
|
||||
self._merge_auth(other)
|
||||
|
||||
def _merge_auth(self, other):
|
||||
if not other:
|
||||
return
|
||||
if other.password:
|
||||
self.password = other.password
|
||||
if other.public_key:
|
||||
self.public_key = other.public_key
|
||||
if other.private_key:
|
||||
self.private_key = other.private_key
|
||||
|
||||
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.username
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'public_key': self.public_key,
|
||||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@@ -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:
|
||||
|
||||
91
apps/assets/models/cmd_filter.py
Normal file
91
apps/assets/models/cmd_filter.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- 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 Meta:
|
||||
verbose_name = _("Command filter")
|
||||
|
||||
|
||||
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')
|
||||
verbose_name = _("Command filter rule")
|
||||
|
||||
@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)
|
||||
96
apps/assets/models/domain.py
Normal file
96
apps/assets/models/domain.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
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(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
|
||||
|
||||
def has_gateway(self):
|
||||
return self.gateway_set.filter(is_active=True).exists()
|
||||
|
||||
@property
|
||||
def gateways(self):
|
||||
return self.gateway_set.filter(is_active=True)
|
||||
|
||||
def random_gateway(self):
|
||||
return random.choice(self.gateways)
|
||||
|
||||
|
||||
class Gateway(AssetUser):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
)
|
||||
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=PROTOCOL_SSH, verbose_name=_("Protocol"))
|
||||
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, local_port=None):
|
||||
if local_port is None:
|
||||
local_port = self.port
|
||||
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)
|
||||
|
||||
try:
|
||||
sock = proxy.get_transport().open_channel(
|
||||
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
|
||||
)
|
||||
client.connect("127.0.0.1", port=local_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,33 +2,122 @@
|
||||
#
|
||||
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.utils.translation import ugettext
|
||||
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
|
||||
_assets_amount = None
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
ordering = ['key']
|
||||
|
||||
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(':')]
|
||||
self_parent_key = self_key[:-1]
|
||||
other_parent_key = other_key[:-1]
|
||||
|
||||
if self_parent_key == other_parent_key:
|
||||
return self.name > other.name
|
||||
return self_key > other_key
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
"""
|
||||
获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
|
||||
:return:
|
||||
"""
|
||||
if self._assets_amount is not None:
|
||||
return self._assets_amount
|
||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||
cached = cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
assets_amount = self.get_all_assets().count()
|
||||
cache.set(cache_key, assets_amount, 3600)
|
||||
return assets_amount
|
||||
|
||||
@assets_amount.setter
|
||||
def assets_amount(self, value):
|
||||
self._assets_amount = value
|
||||
|
||||
def expire_assets_amount(self):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
|
||||
cache.delete_many(cache_keys)
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_assets_amount(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_assets_amount()
|
||||
return
|
||||
key = cls._assets_amount_cache_key.format('*')
|
||||
cache.delete_pattern(key)
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
if self == self.__class__.root():
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
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
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
return value
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_full_value(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_full_value()
|
||||
return
|
||||
key = cls._full_value_cache_key.format('*')
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
@@ -40,80 +129,184 @@ class Node(models.Model):
|
||||
self.save()
|
||||
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
|
||||
def get_next_child_preset_name(self):
|
||||
name = ugettext("New node")
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_children()
|
||||
if child.value.startswith(name)
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
def get_children(self):
|
||||
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
|
||||
def create_child(self, value, _id=None):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
|
||||
return child
|
||||
|
||||
def get_all_children(self):
|
||||
return self.__class__.objects.filter(key__startswith='{}:'.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, 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
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
args = []
|
||||
kwargs = {}
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
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)]
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
parent_keys.append(":".join(key_list))
|
||||
key_list.pop()
|
||||
return parent_keys
|
||||
|
||||
@property
|
||||
def ancestor_with_node(self):
|
||||
ancestor = self.ancestor
|
||||
ancestor.insert(0, self)
|
||||
def get_ancestor(self, with_self=False):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
|
||||
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"}
|
||||
)
|
||||
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'}
|
||||
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
return obj
|
||||
|
||||
def as_tree_node(self):
|
||||
from common.tree import TreeNode
|
||||
from ..serializers import NodeSerializer
|
||||
name = '{} ({})'.format(self.value, self.assets_amount)
|
||||
node_serializer = NodeSerializer(instance=self)
|
||||
data = {
|
||||
'id': self.key,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': self.parent_key,
|
||||
'isParent': True,
|
||||
'open': self.is_root(),
|
||||
'meta': {
|
||||
'node': node_serializer.data,
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
@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))
|
||||
|
||||
@@ -2,138 +2,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import os
|
||||
import logging
|
||||
import uuid
|
||||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||
from .utils import private_key_validator
|
||||
from common.utils import get_signer
|
||||
from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
||||
from .base import AssetUser
|
||||
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser',]
|
||||
__all__ = ['AdminUser', 'SystemUser']
|
||||
logger = logging.getLogger(__name__)
|
||||
signer = get_signer()
|
||||
|
||||
|
||||
class AssetUser(models.Model):
|
||||
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'))
|
||||
_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'))
|
||||
comment = models.TextField(blank=True, 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, null=True, verbose_name=_('Created by'))
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
raise AttributeError("Using set_auth do that")
|
||||
# self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
raise AttributeError("Using set_auth do that")
|
||||
# self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_str = signer.unsign(self._private_key)
|
||||
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
key = signer.unsign(self._public_key)
|
||||
if key:
|
||||
return key
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self._password = signer.sign(password)
|
||||
update_fields.append('_password')
|
||||
if private_key:
|
||||
self._private_key = signer.sign(private_key)
|
||||
update_fields.append('_private_key')
|
||||
if public_key:
|
||||
self._public_key = signer.sign(public_key)
|
||||
update_fields.append('_public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def auto_gen_auth(self):
|
||||
password = str(uuid.uuid4())
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=self.name, password=password
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'public_key': self.public_key,
|
||||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class AdminUser(AssetUser):
|
||||
"""
|
||||
A privileged user that ansible can use it to push system user and so on
|
||||
@@ -146,6 +31,7 @@ class AdminUser(AssetUser):
|
||||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', max_length=128)
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -182,8 +68,26 @@ class AdminUser(AssetUser):
|
||||
def assets_amount(self):
|
||||
return self.get_related_assets().count()
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
from .asset import Asset
|
||||
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
}
|
||||
for asset_id, hostname in assets:
|
||||
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(key, Asset.UNKNOWN)
|
||||
if value == Asset.REACHABLE:
|
||||
data['reachable'].append(hostname)
|
||||
elif value == Asset.UNREACHABLE:
|
||||
data['unreachable'].append(hostname)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Admin user")
|
||||
|
||||
@classmethod
|
||||
@@ -208,22 +112,39 @@ class AdminUser(AssetUser):
|
||||
|
||||
|
||||
class SystemUser(AssetUser):
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
|
||||
LOGIN_AUTO = 'auto'
|
||||
LOGIN_MANUAL = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(LOGIN_AUTO, _('Automatic login')),
|
||||
(LOGIN_MANUAL, _('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=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
|
||||
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
@@ -235,34 +156,94 @@ 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_related_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_connective(self):
|
||||
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
|
||||
return _result
|
||||
def connectivity(self):
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(cache_key, None)
|
||||
if not value or 'unreachable' not in value:
|
||||
return {'unreachable': [], 'reachable': []}
|
||||
else:
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
data = self.connectivity
|
||||
unreachable = data['unreachable']
|
||||
reachable = data['reachable']
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host not in unreachable:
|
||||
unreachable.append(host)
|
||||
if host in reachable:
|
||||
reachable.remove(host)
|
||||
for host in value.get('contacted'):
|
||||
if host not in reachable:
|
||||
reachable.append(host)
|
||||
if host in unreachable:
|
||||
unreachable.remove(host)
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
cache.set(cache_key, data, 3600)
|
||||
|
||||
@property
|
||||
def unreachable_assets(self):
|
||||
return list(self.assets_connective.get('dark', {}).keys())
|
||||
def assets_unreachable(self):
|
||||
return self.connectivity.get('unreachable')
|
||||
|
||||
@property
|
||||
def reachable_assets(self):
|
||||
return self.assets_connective.get('contacted', [])
|
||||
def assets_reachable(self):
|
||||
return self.connectivity.get('reachable')
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
|
||||
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.SYSTEM_USER_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.SYSTEM_USER_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
|
||||
@@ -284,6 +265,3 @@ class SystemUser(AssetUser):
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ __all__ = ['init_model', 'generate_fake']
|
||||
|
||||
|
||||
def init_model():
|
||||
from . import Cluster, SystemUser, AdminUser, AssetGroup, Asset
|
||||
for cls in [Cluster, SystemUser, AdminUser, AssetGroup, Asset]:
|
||||
from . import SystemUser, AdminUser, Asset
|
||||
for cls in [SystemUser, AdminUser, Asset]:
|
||||
if hasattr(cls, 'initial'):
|
||||
cls.initial()
|
||||
|
||||
|
||||
def generate_fake():
|
||||
from . import Cluster, SystemUser, AdminUser, AssetGroup, Asset
|
||||
for cls in [Cluster, SystemUser, AdminUser, AssetGroup, Asset]:
|
||||
from . import SystemUser, AdminUser, Asset
|
||||
for cls in [SystemUser, AdminUser, Asset]:
|
||||
if hasattr(cls, 'generate_fake'):
|
||||
cls.generate_fake()
|
||||
|
||||
|
||||
@@ -6,3 +6,6 @@ from .admin_user import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .asset_user import *
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user